├── .cfignore ├── .gitignore ├── .jshintrc ├── .project ├── .settings ├── com.eclipsesource.jshint.ui.prefs └── org.eclipse.wst.jsdt.core.prefs ├── LICENSE ├── README.md ├── app.js ├── manifest.yml ├── package.json ├── public ├── images │ ├── add.png │ ├── detail.png │ ├── info.png │ ├── loading.gif │ ├── newapp-icon.png │ ├── remove.png │ └── sprite.png ├── scripts │ ├── index.js │ └── util.js ├── style │ └── style.css └── uploads │ └── uploadedFileName ├── routes ├── index.js └── user.js └── views └── index.html /.cfignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | vcap-local.json 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | vcap-local.json 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // -------------------------------------------------------------------- 3 | // JSHint Nodeclipse Configuration v0.11 4 | // Strict Edition with some relaxations and switch to Node.js, no `use strict` 5 | // by Ory Band, Michael Haschke, Paul Verest 6 | // https://github.com/Nodeclipse/nodeclipse-1/blob/master/org.nodeclipse.ui/templates/common-templates/.jshintrc 7 | // JSHint Documentation is at http://www.jshint.com/docs/options/ 8 | // JSHint Integration v0.9.9 comes with JSHInt 2.1.10 , see https://github.com/eclipsesource/jshint-eclipse 9 | // -------------------------------------------------------------------- 10 | // from https://gist.github.com/haschek/2595796 11 | // 12 | // This is a options template for [JSHint][1], using [JSHint example][2] 13 | // and [Ory Band's example][3] as basis and setting config values to 14 | // be most strict: 15 | // 16 | // * set all enforcing options to true 17 | // * set all relaxing options to false 18 | // * set all environment options to false, except the node value 19 | // * set all JSLint legacy options to false 20 | // 21 | // [1]: http://www.jshint.com/ 22 | // [2]: https://github.com/jshint/node-jshint/blob/master/example/config.json //404 23 | // [3]: https://github.com/oryband/dotfiles/blob/master/jshintrc 24 | // 25 | // @author http://michael.haschke.biz/ 26 | // @license http://unlicense.org/ 27 | 28 | // == Enforcing Options =============================================== 29 | // 30 | // These options tell JSHint to be more strict towards your code. Use 31 | // them if you want to allow only a safe subset of JavaScript, very 32 | // useful when your codebase is shared with a big number of developers 33 | // with different skill levels. Was all true. 34 | 35 | "bitwise" : true, // Prohibit bitwise operators (&, |, ^, etc.). 36 | "curly" : true, // Require {} for every new block or scope. 37 | "eqeqeq" : true, // Require triple equals i.e. `===`. 38 | "forin" : true, // Tolerate `for in` loops without `hasOwnPrototype`. 39 | "immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` 40 | "latedef" : true, // Prohibit variable use before definition. 41 | "newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`. 42 | "noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`. 43 | "noempty" : true, // Prohibit use of empty blocks. 44 | "nonew" : true, // Prohibit use of constructors for side-effects. 45 | "plusplus" : false, // Prohibit use of `++` & `--`. //coding style related only 46 | "regexp" : true, // Prohibit `.` and `[^...]` in regular expressions. 47 | "undef" : true, // Require all non-global variables be declared before they are used. 48 | "strict" : false, // Require `use strict` pragma in every file. 49 | "trailing" : true, // Prohibit trailing whitespaces. 50 | 51 | // == Relaxing Options ================================================ 52 | // 53 | // These options allow you to suppress certain types of warnings. Use 54 | // them only if you are absolutely positive that you know what you are 55 | // doing. Was all false. 56 | "asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons). 57 | "boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments. 58 | "debug" : false, // Allow debugger statements e.g. browser breakpoints. 59 | "eqnull" : false, // Tolerate use of `== null`. 60 | "es5" : true, // Allow EcmaScript 5 syntax. // es5 is default https://github.com/jshint/jshint/issues/1411 61 | "esnext" : false, // Allow ES.next (ECMAScript 6) specific features such as `const` and `let`. 62 | "evil" : false, // Tolerate use of `eval`. 63 | "expr" : false, // Tolerate `ExpressionStatement` as Programs. 64 | "funcscope" : false, // Tolerate declarations of variables inside of control structures while accessing them later from the outside. 65 | "globalstrict" : false, // Allow global "use strict" (also enables 'strict'). 66 | "iterator" : false, // Allow usage of __iterator__ property. 67 | "lastsemic" : false, // Tolerat missing semicolons when the it is omitted for the last statement in a one-line block. 68 | "laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons. 69 | "laxcomma" : true, // Suppress warnings about comma-first coding style. 70 | "loopfunc" : false, // Allow functions to be defined within loops. 71 | "multistr" : false, // Tolerate multi-line strings. 72 | "onecase" : false, // Tolerate switches with just one case. 73 | "proto" : false, // Tolerate __proto__ property. This property is deprecated. 74 | "regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`. 75 | "scripturl" : false, // Tolerate script-targeted URLs. 76 | "smarttabs" : false, // Tolerate mixed tabs and spaces when the latter are used for alignmnent only. 77 | "shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`. 78 | "sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`. 79 | "supernew" : false, // Tolerate `new function () { ... };` and `new Object;`. 80 | "validthis" : false, // Tolerate strict violations when the code is running in strict mode and you use this in a non-constructor function. 81 | 82 | // == Environments ==================================================== 83 | // 84 | // These options pre-define global variables that are exposed by 85 | // popular JavaScript libraries and runtime environments—such as 86 | // browser or node.js. 87 | "browser" : false, // Standard browser globals e.g. `window`, `document`. 88 | "couch" : false, // Enable globals exposed by CouchDB. 89 | "devel" : false, // Allow development statements e.g. `console.log();`. 90 | "dojo" : false, // Enable globals exposed by Dojo Toolkit. 91 | "jquery" : false, // Enable globals exposed by jQuery JavaScript library. 92 | "mootools" : false, // Enable globals exposed by MooTools JavaScript framework. 93 | "node" : true, // Enable globals available when code is running inside of the NodeJS runtime environment. 94 | "nonstandard" : false, // Define non-standard but widely adopted globals such as escape and unescape. 95 | "prototypejs" : false, // Enable globals exposed by Prototype JavaScript framework. 96 | "rhino" : false, // Enable globals available when your code is running inside of the Rhino runtime environment. 97 | "wsh" : false, // Enable globals available when your code is running as a script for the Windows Script Host. 98 | 99 | // == JSLint Legacy =================================================== 100 | // 101 | // These options are legacy from JSLint. Aside from bug fixes they will 102 | // not be improved in any way and might be removed at any point. 103 | "nomen" : false, // Prohibit use of initial or trailing underbars in names. 104 | "onevar" : false, // Allow only one `var` statement per function. 105 | "passfail" : false, // Stop on first error. 106 | "white" : false, // Check against strict whitespace and indentation rules. 107 | 108 | // == Undocumented Options ============================================ 109 | // 110 | // While I've found these options in [example1][2] and [example2][3] 111 | // they are not described in the [JSHint Options documentation][4]. 112 | // 113 | // [4]: http://www.jshint.com/options/ 114 | 115 | "maxerr" : 100, // Maximum errors before stopping. 116 | "predef" : [ // Extra globals. 117 | //"exampleVar", 118 | //"anotherCoolGlobal", 119 | //"iLoveDouglas" 120 | ] 121 | //, "indent" : 2 // Specify indentation spacing 122 | } 123 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | nodejscloudantbp 4 | 5 | 6 | 7 | 8 | 9 | com.eclipsesource.jshint.ui.builder 10 | 11 | 12 | 13 | 14 | 15 | org.nodeclipse.ui.NodeNature 16 | org.eclipse.wst.jsdt.core.jsNature 17 | 18 | 19 | -------------------------------------------------------------------------------- /.settings/com.eclipsesource.jshint.ui.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | excluded=node_lib//*:node_modules//*:bower_components//* 3 | included=//*.jjs\://*.js\://*.jshintrc\://*.json\://*.mjs\://*.njs\://*.pjs://*.vjs 4 | projectSpecificOptions=true 5 | -------------------------------------------------------------------------------- /.settings/org.eclipse.wst.jsdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | semanticValidation=disabled 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node.js Cloudant Sample 2 | 3 | This application demonstrates how to use the Bluemix Cloudant NoSQL DB service. It helps users organize their favorite files. The UI talks to a RESTful Express CRUD backend API. 4 | 5 | [![Deploy to Bluemix](https://bluemix.net/deploy/button.png)](https://bluemix.net/deploy?repository=https://github.com/IBM-Bluemix/nodejs-cloudant) 6 | 7 | ## Run the app locally 8 | 9 | 1. [Install Node.js][] 10 | + cd into this project's root directory 11 | + Copy the value for the VCAP_SERVICES envirionment variable from the application running in Bluemix and paste it in a `vcap-local.json` file 12 | + Run `npm install` to install the app's dependencies 13 | + Run `npm start` to start the app 14 | + Access the running app in a browser at 15 | 16 | [Install Node.js]: https://nodejs.org/en/download/ 17 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 5 | var express = require('express'), 6 | routes = require('./routes'), 7 | user = require('./routes/user'), 8 | http = require('http'), 9 | path = require('path'), 10 | fs = require('fs'); 11 | 12 | var app = express(); 13 | 14 | var db; 15 | 16 | var cloudant; 17 | 18 | var fileToUpload; 19 | 20 | var dbCredentials = { 21 | dbName: 'my_sample_db' 22 | }; 23 | 24 | var bodyParser = require('body-parser'); 25 | var methodOverride = require('method-override'); 26 | var logger = require('morgan'); 27 | var errorHandler = require('errorhandler'); 28 | var multipart = require('connect-multiparty') 29 | var multipartMiddleware = multipart(); 30 | 31 | // all environments 32 | app.set('port', process.env.PORT || 3000); 33 | app.set('views', __dirname + '/views'); 34 | app.set('view engine', 'ejs'); 35 | app.engine('html', require('ejs').renderFile); 36 | app.use(logger('dev')); 37 | app.use(bodyParser.urlencoded({ 38 | extended: true 39 | })); 40 | app.use(bodyParser.json()); 41 | app.use(methodOverride()); 42 | app.use(express.static(path.join(__dirname, 'public'))); 43 | app.use('/style', express.static(path.join(__dirname, '/views/style'))); 44 | 45 | // development only 46 | if ('development' == app.get('env')) { 47 | app.use(errorHandler()); 48 | } 49 | 50 | function getDBCredentialsUrl(jsonData) { 51 | var vcapServices = JSON.parse(jsonData); 52 | // Pattern match to find the first instance of a Cloudant service in 53 | // VCAP_SERVICES. If you know your service key, you can access the 54 | // service credentials directly by using the vcapServices object. 55 | for (var vcapService in vcapServices) { 56 | if (vcapService.match(/cloudant/i)) { 57 | return vcapServices[vcapService][0].credentials.url; 58 | } 59 | } 60 | } 61 | 62 | function initDBConnection() { 63 | //When running on Bluemix, this variable will be set to a json object 64 | //containing all the service credentials of all the bound services 65 | if (process.env.VCAP_SERVICES) { 66 | dbCredentials.url = getDBCredentialsUrl(process.env.VCAP_SERVICES); 67 | } else { //When running locally, the VCAP_SERVICES will not be set 68 | 69 | // When running this app locally you can get your Cloudant credentials 70 | // from Bluemix (VCAP_SERVICES in "cf env" output or the Environment 71 | // Variables section for an app in the Bluemix console dashboard). 72 | // Once you have the credentials, paste them into a file called vcap-local.json. 73 | // Alternately you could point to a local database here instead of a 74 | // Bluemix service. 75 | // url will be in this format: https://username:password@xxxxxxxxx-bluemix.cloudant.com 76 | dbCredentials.url = getDBCredentialsUrl(fs.readFileSync("vcap-local.json", "utf-8")); 77 | } 78 | 79 | cloudant = require('cloudant')(dbCredentials.url); 80 | 81 | // check if DB exists if not create 82 | cloudant.db.create(dbCredentials.dbName, function(err, res) { 83 | if (err) { 84 | console.log('Could not create new db: ' + dbCredentials.dbName + ', it might already exist.'); 85 | } 86 | }); 87 | 88 | db = cloudant.use(dbCredentials.dbName); 89 | } 90 | 91 | initDBConnection(); 92 | 93 | app.get('/', routes.index); 94 | 95 | function createResponseData(id, name, value, attachments) { 96 | 97 | var responseData = { 98 | id: id, 99 | name: sanitizeInput(name), 100 | value: sanitizeInput(value), 101 | attachements: [] 102 | }; 103 | 104 | 105 | attachments.forEach(function(item, index) { 106 | var attachmentData = { 107 | content_type: item.type, 108 | key: item.key, 109 | url: '/api/favorites/attach?id=' + id + '&key=' + item.key 110 | }; 111 | responseData.attachements.push(attachmentData); 112 | 113 | }); 114 | return responseData; 115 | } 116 | 117 | function sanitizeInput(str) { 118 | return String(str).replace(/&(?!amp;|lt;|gt;)/g, '&').replace(//g, '>'); 119 | } 120 | 121 | var saveDocument = function(id, name, value, response) { 122 | 123 | if (id === undefined) { 124 | // Generated random id 125 | id = ''; 126 | } 127 | 128 | db.insert({ 129 | name: name, 130 | value: value 131 | }, id, function(err, doc) { 132 | if (err) { 133 | console.log(err); 134 | response.sendStatus(500); 135 | } else 136 | response.sendStatus(200); 137 | response.end(); 138 | }); 139 | 140 | } 141 | 142 | app.get('/api/favorites/attach', function(request, response) { 143 | var doc = request.query.id; 144 | var key = request.query.key; 145 | 146 | db.attachment.get(doc, key, function(err, body) { 147 | if (err) { 148 | response.status(500); 149 | response.setHeader('Content-Type', 'text/plain'); 150 | response.write('Error: ' + err); 151 | response.end(); 152 | return; 153 | } 154 | 155 | response.status(200); 156 | response.setHeader("Content-Disposition", 'inline; filename="' + key + '"'); 157 | response.write(body); 158 | response.end(); 159 | return; 160 | }); 161 | }); 162 | 163 | app.post('/api/favorites/attach', multipartMiddleware, function(request, response) { 164 | 165 | console.log("Upload File Invoked.."); 166 | console.log('Request: ' + JSON.stringify(request.headers)); 167 | 168 | var id; 169 | 170 | db.get(request.query.id, function(err, existingdoc) { 171 | 172 | var isExistingDoc = false; 173 | if (!existingdoc) { 174 | id = '-1'; 175 | } else { 176 | id = existingdoc.id; 177 | isExistingDoc = true; 178 | } 179 | 180 | var name = sanitizeInput(request.query.name); 181 | var value = sanitizeInput(request.query.value); 182 | 183 | var file = request.files.file; 184 | var newPath = './public/uploads/' + file.name; 185 | 186 | var insertAttachment = function(file, id, rev, name, value, response) { 187 | 188 | fs.readFile(file.path, function(err, data) { 189 | if (!err) { 190 | 191 | if (file) { 192 | 193 | db.attachment.insert(id, file.name, data, file.type, { 194 | rev: rev 195 | }, function(err, document) { 196 | if (!err) { 197 | console.log('Attachment saved successfully.. '); 198 | 199 | db.get(document.id, function(err, doc) { 200 | console.log('Attachements from server --> ' + JSON.stringify(doc._attachments)); 201 | 202 | var attachements = []; 203 | var attachData; 204 | for (var attachment in doc._attachments) { 205 | if (attachment == value) { 206 | attachData = { 207 | "key": attachment, 208 | "type": file.type 209 | }; 210 | } else { 211 | attachData = { 212 | "key": attachment, 213 | "type": doc._attachments[attachment]['content_type'] 214 | }; 215 | } 216 | attachements.push(attachData); 217 | } 218 | var responseData = createResponseData( 219 | id, 220 | name, 221 | value, 222 | attachements); 223 | console.log('Response after attachment: \n' + JSON.stringify(responseData)); 224 | response.write(JSON.stringify(responseData)); 225 | response.end(); 226 | return; 227 | }); 228 | } else { 229 | console.log(err); 230 | } 231 | }); 232 | } 233 | } 234 | }); 235 | } 236 | 237 | if (!isExistingDoc) { 238 | existingdoc = { 239 | name: name, 240 | value: value, 241 | create_date: new Date() 242 | }; 243 | 244 | // save doc 245 | db.insert({ 246 | name: name, 247 | value: value 248 | }, '', function(err, doc) { 249 | if (err) { 250 | console.log(err); 251 | } else { 252 | 253 | existingdoc = doc; 254 | console.log("New doc created .."); 255 | console.log(existingdoc); 256 | insertAttachment(file, existingdoc.id, existingdoc.rev, name, value, response); 257 | 258 | } 259 | }); 260 | 261 | } else { 262 | console.log('Adding attachment to existing doc.'); 263 | console.log(existingdoc); 264 | insertAttachment(file, existingdoc._id, existingdoc._rev, name, value, response); 265 | } 266 | 267 | }); 268 | 269 | }); 270 | 271 | app.post('/api/favorites', function(request, response) { 272 | 273 | console.log("Create Invoked.."); 274 | console.log("Name: " + request.body.name); 275 | console.log("Value: " + request.body.value); 276 | 277 | // var id = request.body.id; 278 | var name = sanitizeInput(request.body.name); 279 | var value = sanitizeInput(request.body.value); 280 | 281 | saveDocument(null, name, value, response); 282 | 283 | }); 284 | 285 | app.delete('/api/favorites', function(request, response) { 286 | 287 | console.log("Delete Invoked.."); 288 | var id = request.query.id; 289 | // var rev = request.query.rev; // Rev can be fetched from request. if 290 | // needed, send the rev from client 291 | console.log("Removing document of ID: " + id); 292 | console.log('Request Query: ' + JSON.stringify(request.query)); 293 | 294 | db.get(id, { 295 | revs_info: true 296 | }, function(err, doc) { 297 | if (!err) { 298 | db.destroy(doc._id, doc._rev, function(err, res) { 299 | // Handle response 300 | if (err) { 301 | console.log(err); 302 | response.sendStatus(500); 303 | } else { 304 | response.sendStatus(200); 305 | } 306 | }); 307 | } 308 | }); 309 | 310 | }); 311 | 312 | app.put('/api/favorites', function(request, response) { 313 | 314 | console.log("Update Invoked.."); 315 | 316 | var id = request.body.id; 317 | var name = sanitizeInput(request.body.name); 318 | var value = sanitizeInput(request.body.value); 319 | 320 | console.log("ID: " + id); 321 | 322 | db.get(id, { 323 | revs_info: true 324 | }, function(err, doc) { 325 | if (!err) { 326 | console.log(doc); 327 | doc.name = name; 328 | doc.value = value; 329 | db.insert(doc, doc.id, function(err, doc) { 330 | if (err) { 331 | console.log('Error inserting data\n' + err); 332 | return 500; 333 | } 334 | return 200; 335 | }); 336 | } 337 | }); 338 | }); 339 | 340 | app.get('/api/favorites', function(request, response) { 341 | 342 | console.log("Get method invoked.. ") 343 | 344 | db = cloudant.use(dbCredentials.dbName); 345 | var docList = []; 346 | var i = 0; 347 | db.list(function(err, body) { 348 | if (!err) { 349 | var len = body.rows.length; 350 | console.log('total # of docs -> ' + len); 351 | if (len == 0) { 352 | // push sample data 353 | // save doc 354 | var docName = 'sample_doc'; 355 | var docDesc = 'A sample Document'; 356 | db.insert({ 357 | name: docName, 358 | value: 'A sample Document' 359 | }, '', function(err, doc) { 360 | if (err) { 361 | console.log(err); 362 | } else { 363 | 364 | console.log('Document : ' + JSON.stringify(doc)); 365 | var responseData = createResponseData( 366 | doc.id, 367 | docName, 368 | docDesc, []); 369 | docList.push(responseData); 370 | response.write(JSON.stringify(docList)); 371 | console.log(JSON.stringify(docList)); 372 | console.log('ending response...'); 373 | response.end(); 374 | } 375 | }); 376 | } else { 377 | 378 | body.rows.forEach(function(document) { 379 | 380 | db.get(document.id, { 381 | revs_info: true 382 | }, function(err, doc) { 383 | if (!err) { 384 | if (doc['_attachments']) { 385 | 386 | var attachments = []; 387 | for (var attribute in doc['_attachments']) { 388 | 389 | if (doc['_attachments'][attribute] && doc['_attachments'][attribute]['content_type']) { 390 | attachments.push({ 391 | "key": attribute, 392 | "type": doc['_attachments'][attribute]['content_type'] 393 | }); 394 | } 395 | console.log(attribute + ": " + JSON.stringify(doc['_attachments'][attribute])); 396 | } 397 | var responseData = createResponseData( 398 | doc._id, 399 | doc.name, 400 | doc.value, 401 | attachments); 402 | 403 | } else { 404 | var responseData = createResponseData( 405 | doc._id, 406 | doc.name, 407 | doc.value, []); 408 | } 409 | 410 | docList.push(responseData); 411 | i++; 412 | if (i >= len) { 413 | response.write(JSON.stringify(docList)); 414 | console.log('ending response...'); 415 | response.end(); 416 | } 417 | } else { 418 | console.log(err); 419 | } 420 | }); 421 | 422 | }); 423 | } 424 | 425 | } else { 426 | console.log(err); 427 | } 428 | }); 429 | 430 | }); 431 | 432 | 433 | http.createServer(app).listen(app.get('port'), '0.0.0.0', function() { 434 | console.log('Express server listening on port ' + app.get('port')); 435 | }); 436 | -------------------------------------------------------------------------------- /manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | declared-services: 3 | sample-nodejs-cloudant-cloudantNoSQLDB: 4 | label: cloudantNoSQLDB 5 | plan: Lite 6 | applications: 7 | - name: sample-nodejs-cloudant 8 | random-route: true 9 | memory: 256M 10 | services: 11 | - sample-nodejs-cloudant-cloudantNoSQLDB 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudant_boilerplate_nodejs", 3 | "version": "0.0.2", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app.js" 7 | }, 8 | "dependencies": { 9 | "express": "4.15.x", 10 | "ejs": "2.5.x", 11 | "cloudant": "1.7.x", 12 | "body-parser": "1.17.x", 13 | "method-override": "2.3.x", 14 | "morgan": "1.8.x", 15 | "errorhandler": "1.5.x", 16 | "connect-multiparty": "2.0.x" 17 | }, 18 | "repository": {}, 19 | "engines": { 20 | "node": "6.x" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /public/images/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/nodejs-cloudant/cd3d615b476e6cdc68eb176b1ce1ea4587195849/public/images/add.png -------------------------------------------------------------------------------- /public/images/detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/nodejs-cloudant/cd3d615b476e6cdc68eb176b1ce1ea4587195849/public/images/detail.png -------------------------------------------------------------------------------- /public/images/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/nodejs-cloudant/cd3d615b476e6cdc68eb176b1ce1ea4587195849/public/images/info.png -------------------------------------------------------------------------------- /public/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/nodejs-cloudant/cd3d615b476e6cdc68eb176b1ce1ea4587195849/public/images/loading.gif -------------------------------------------------------------------------------- /public/images/newapp-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/nodejs-cloudant/cd3d615b476e6cdc68eb176b1ce1ea4587195849/public/images/newapp-icon.png -------------------------------------------------------------------------------- /public/images/remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/nodejs-cloudant/cd3d615b476e6cdc68eb176b1ce1ea4587195849/public/images/remove.png -------------------------------------------------------------------------------- /public/images/sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/nodejs-cloudant/cd3d615b476e6cdc68eb176b1ce1ea4587195849/public/images/sprite.png -------------------------------------------------------------------------------- /public/scripts/index.js: -------------------------------------------------------------------------------- 1 | // index.js 2 | 3 | var REST_DATA = 'api/favorites'; 4 | var KEY_ENTER = 13; 5 | var defaultItems = [ 6 | 7 | ]; 8 | 9 | function encodeUriAndQuotes(untrustedStr) { 10 | return encodeURI(String(untrustedStr)).replace(/'/g, '%27').replace(')', '%29'); 11 | } 12 | 13 | function loadItems() { 14 | xhrGet(REST_DATA, function(data) { 15 | 16 | //stop showing loading message 17 | stopLoadingMessage(); 18 | 19 | var receivedItems = data || []; 20 | var items = []; 21 | var i; 22 | // Make sure the received items have correct format 23 | for (i = 0; i < receivedItems.length; ++i) { 24 | var item = receivedItems[i]; 25 | if (item && 'id' in item) { 26 | items.push(item); 27 | } 28 | } 29 | var hasItems = items.length; 30 | if (!hasItems) { 31 | items = defaultItems; 32 | } 33 | for (i = 0; i < items.length; ++i) { 34 | addItem(items[i], !hasItems); 35 | } 36 | if (!hasItems) { 37 | var table = document.getElementById('notes'); 38 | var nodes = []; 39 | for (i = 0; i < table.rows.length; ++i) { 40 | nodes.push(table.rows[i].firstChild.firstChild); 41 | } 42 | 43 | function save() { 44 | if (nodes.length) { 45 | saveChange(nodes.shift(), save); 46 | } 47 | } 48 | save(); 49 | } 50 | }, function(err) { 51 | console.error(err); 52 | }); 53 | } 54 | 55 | function startProgressIndicator(row) { 56 | row.innerHTML = "Uploading file... "; 57 | } 58 | 59 | function removeProgressIndicator(row) { 60 | row.innerHTML = "uploaded..."; 61 | } 62 | 63 | function addNewRow(table) { 64 | var newRow = document.createElement('tr'); 65 | table.appendChild(newRow); 66 | return table.lastChild; 67 | } 68 | 69 | function uploadFile(node) { 70 | 71 | var file = node.previousSibling.files[0]; 72 | 73 | //if file not selected, throw error 74 | if (!file) { 75 | alert("File not selected for upload... \t\t\t\t \n\n - Choose a file to upload. \n - Then click on Upload button."); 76 | return; 77 | } 78 | 79 | var row = node.parentNode.parentNode.parentNode; 80 | 81 | var form = new FormData(); 82 | form.append("file", file); 83 | 84 | var id = row.getAttribute('data-id'); 85 | 86 | var queryParams = "id=" + (id == null ? -1 : id); 87 | queryParams += "&name=" + row.firstChild.firstChild.value; 88 | queryParams += "&value=" + row.firstChild.nextSibling.firstChild.value; 89 | 90 | 91 | var table = row.firstChild.nextSibling.firstChild; 92 | var newRow = addNewRow(table); 93 | 94 | startProgressIndicator(newRow); 95 | 96 | xhrAttach(REST_DATA + "/attach?" + queryParams, form, function(item) { 97 | console.log('Item id - ' + item.id); 98 | console.log('attached: ', item); 99 | row.setAttribute('data-id', item.id); 100 | removeProgressIndicator(row); 101 | setRowContent(item, row); 102 | }, function(err) { 103 | console.error(err); 104 | }); 105 | 106 | } 107 | 108 | var attachButton = "
"; 109 | 110 | function setRowContent(item, row) { 111 | var innerHTML = ""; 112 | 113 | var valueTextArea = ""; 114 | if (item.value) { 115 | valueTextArea = ""; 116 | } 117 | 118 | innerHTML += valueTextArea; 119 | 120 | 121 | var attachments = item.attachements; 122 | if (attachments && attachments.length > 0) { 123 | innerHTML += "
"; 124 | for (var i = 0; i < attachments.length; ++i) { 125 | var attachment = attachments[i]; 126 | 127 | if (attachment.content_type.indexOf("image/") == 0) { 128 | innerHTML += "
" + attachment.key + "
"; 129 | 130 | } else if (attachment.content_type.indexOf("audio/") == 0) { 131 | innerHTML += "
" + attachment.key + "
"; 132 | 133 | } else if (attachment.content_type.indexOf("video/") == 0) { 134 | innerHTML += "
" + attachment.key + "
"; 135 | 136 | } else if (attachment.content_type.indexOf("text/") == 0 || attachment.content_type.indexOf("application/") == 0) { 137 | innerHTML += ""; 138 | } 139 | 140 | } 141 | innerHTML += "
"; 142 | 143 | } 144 | 145 | row.innerHTML = innerHTML + attachButton + ""; 146 | 147 | } 148 | 149 | function addItem(item, isNew) { 150 | 151 | var row = document.createElement('tr'); 152 | row.className = "tableRows"; 153 | var id = item && item.id; 154 | if (id) { 155 | row.setAttribute('data-id', id); 156 | } 157 | 158 | 159 | 160 | if (item) // if not a new row 161 | { 162 | setRowContent(item, row); 163 | } else //if new row 164 | { 165 | row.innerHTML = "" + attachButton + "" + 166 | ""; 167 | } 168 | 169 | var table = document.getElementById('notes'); 170 | table.lastChild.appendChild(row); 171 | row.isNew = !item || isNew; 172 | 173 | if (row.isNew) { 174 | var textarea = row.firstChild.firstChild; 175 | textarea.focus(); 176 | } 177 | 178 | } 179 | 180 | function deleteItem(deleteBtnNode) { 181 | var row = deleteBtnNode.parentNode.parentNode; 182 | var attribId = row.getAttribute('data-id'); 183 | if (attribId) { 184 | xhrDelete(REST_DATA + '?id=' + row.getAttribute('data-id'), function() { 185 | row.parentNode.removeChild(row); 186 | }, function(err) { 187 | console.error(err); 188 | }); 189 | } else if (attribId == null) { 190 | row.parentNode.removeChild(row); 191 | } 192 | } 193 | 194 | function onKey(evt) { 195 | 196 | if (evt.keyCode == KEY_ENTER && !evt.shiftKey) { 197 | 198 | evt.stopPropagation(); 199 | evt.preventDefault(); 200 | var nameV, valueV; 201 | var row; 202 | 203 | if (evt.target.id == "nameText") { 204 | row = evt.target.parentNode.parentNode; 205 | nameV = evt.target.value; 206 | valueV = row.firstChild.nextSibling.firstChild.value; 207 | 208 | } else { 209 | row = evt.target.parentNode.parentNode; 210 | nameV = row.firstChild.firstChild.value; 211 | valueV = evt.target.value; 212 | } 213 | 214 | var data = { 215 | name: nameV, 216 | value: valueV 217 | }; 218 | 219 | if (row.isNew) { 220 | delete row.isNew; 221 | xhrPost(REST_DATA, data, function(item) { 222 | row.setAttribute('data-id', item.id); 223 | }, function(err) { 224 | console.error(err); 225 | }); 226 | } else { 227 | data.id = row.getAttribute('data-id'); 228 | xhrPut(REST_DATA, data, function() { 229 | console.log('updated: ', data); 230 | }, function(err) { 231 | console.error(err); 232 | }); 233 | } 234 | 235 | 236 | if (row.nextSibling) { 237 | row.nextSibling.firstChild.firstChild.focus(); 238 | } else { 239 | addItem(); 240 | } 241 | } 242 | } 243 | 244 | function saveChange(contentNode, callback) { 245 | var row = contentNode.parentNode.parentNode; 246 | 247 | var data = { 248 | name: row.firstChild.firstChild.value, 249 | value: row.firstChild.nextSibling.firstChild.value 250 | }; 251 | 252 | if (row.isNew) { 253 | delete row.isNew; 254 | xhrPost(REST_DATA, data, function(item) { 255 | row.setAttribute('data-id', item.id); 256 | callback && callback(); 257 | }, function(err) { 258 | console.error(err); 259 | }); 260 | } else { 261 | data.id = row.getAttribute('data-id'); 262 | xhrPut(REST_DATA, data, function() { 263 | console.log('updated: ', data); 264 | }, function(err) { 265 | console.error(err); 266 | }); 267 | } 268 | } 269 | 270 | function toggleServiceInfo() { 271 | var node = document.getElementById('vcapservices'); 272 | node.style.display = node.style.display == 'none' ? '' : 'none'; 273 | } 274 | 275 | function toggleAppInfo() { 276 | var node = document.getElementById('appinfo'); 277 | node.style.display = node.style.display == 'none' ? '' : 'none'; 278 | } 279 | 280 | 281 | function showLoadingMessage() { 282 | document.getElementById('loadingImage').innerHTML = "Loading data " + ""; 283 | } 284 | 285 | function stopLoadingMessage() { 286 | document.getElementById('loadingImage').innerHTML = ""; 287 | } 288 | 289 | showLoadingMessage(); 290 | //updateServiceInfo(); 291 | loadItems(); 292 | -------------------------------------------------------------------------------- /public/scripts/util.js: -------------------------------------------------------------------------------- 1 | //utilities 2 | function createXHR(){ 3 | if(typeof XMLHttpRequest != 'undefined'){ 4 | return new XMLHttpRequest(); 5 | }else{ 6 | try{ 7 | return new ActiveXObject('Msxml2.XMLHTTP'); 8 | }catch(e){ 9 | try{ 10 | return new ActiveXObject('Microsoft.XMLHTTP'); 11 | }catch(e){} 12 | } 13 | } 14 | return null; 15 | } 16 | function xhrGet(url, callback, errback){ 17 | var xhr = new createXHR(); 18 | xhr.open("GET", url, true); 19 | xhr.onreadystatechange = function(){ 20 | if(xhr.readyState == 4){ 21 | if(xhr.status == 200){ 22 | callback(parseJson(xhr.responseText)); 23 | }else{ 24 | errback('service not available'); 25 | } 26 | } 27 | }; 28 | 29 | xhr.timeout = 100000; 30 | xhr.ontimeout = errback; 31 | xhr.send(); 32 | } 33 | function xhrPut(url, data, callback, errback){ 34 | var xhr = new createXHR(); 35 | xhr.open("PUT", url, true); 36 | xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 37 | xhr.onreadystatechange = function(){ 38 | if(xhr.readyState == 4){ 39 | if(xhr.status == 200){ 40 | callback(); 41 | }else{ 42 | errback('service not available'); 43 | } 44 | } 45 | }; 46 | xhr.timeout = 100000; 47 | xhr.ontimeout = errback; 48 | xhr.send(objectToQuery(data)); 49 | } 50 | 51 | function xhrAttach(url, data, callback, errback) 52 | { 53 | var xhr = new createXHR(); 54 | xhr.open("POST", url, true); 55 | //xhr.setRequestHeader("Content-type", "multipart/form-data"); 56 | xhr.onreadystatechange = function(){ 57 | if(xhr.readyState == 4){ 58 | if(xhr.status == 200){ 59 | callback(parseJson(xhr.responseText)); 60 | }else{ 61 | errback('service not available'); 62 | } 63 | } 64 | }; 65 | xhr.timeout = 1000000; 66 | xhr.ontimeout = errback; 67 | xhr.send(data); 68 | } 69 | 70 | function xhrPost(url, data, callback, errback){ 71 | var xhr = new createXHR(); 72 | xhr.open("POST", url, true); 73 | xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 74 | xhr.onreadystatechange = function(){ 75 | if(xhr.readyState == 4){ 76 | if(xhr.status == 200){ 77 | callback(parseJson(xhr.responseText)); 78 | }else{ 79 | errback('service not available'); 80 | } 81 | } 82 | }; 83 | xhr.timeout = 100000; 84 | xhr.ontimeout = errback; 85 | xhr.send(objectToQuery(data)); 86 | } 87 | 88 | function xhrDelete(url, callback, errback){ 89 | var xhr = new createXHR(); 90 | xhr.open("DELETE", url, true); 91 | xhr.onreadystatechange = function(){ 92 | if(xhr.readyState == 4){ 93 | if(xhr.status == 200){ 94 | callback(); 95 | }else{ 96 | errback('service not available'); 97 | } 98 | } 99 | }; 100 | xhr.timeout = 100000; 101 | xhr.ontimeout = errback; 102 | xhr.send(); 103 | } 104 | 105 | function parseJson(str){ 106 | return window.JSON ? JSON.parse(str) : eval('(' + str + ')'); 107 | } 108 | 109 | function objectToQuery(map){ 110 | var enc = encodeURIComponent, pairs = []; 111 | for(var name in map){ 112 | var value = map[name]; 113 | var assign = enc(name) + "="; 114 | if(value && (value instanceof Array || typeof value == 'array')){ 115 | for(var i = 0, len = value.length; i < len; ++i){ 116 | pairs.push(assign + enc(value[i])); 117 | } 118 | }else{ 119 | pairs.push(assign + enc(value)); 120 | } 121 | } 122 | return pairs.join("&"); 123 | } 124 | 125 | -------------------------------------------------------------------------------- /public/style/style.css: -------------------------------------------------------------------------------- 1 | /* style.css 2 | * This file provides css styles. 3 | */ 4 | 5 | body,html { 6 | background-color: #f5f7fa; 7 | width: 100%; 8 | height: 100%; 9 | margin: 0 auto; 10 | font-family: Lato,sans-serif; 11 | color: #283D56; 12 | } 13 | 14 | /* header footer common */ 15 | header, footer { 16 | width: 100%; 17 | text-align: center; 18 | } 19 | 20 | .container { 21 | height: 100%; 22 | overflow: auto; 23 | display: flex; 24 | flex-direction: column; 25 | } 26 | 27 | .banner { 28 | background: #5586D6; 29 | color: #fff; 30 | padding: 10px; 31 | } 32 | 33 | /* positions */ 34 | header { 35 | padding-top: 20px; 36 | font-size: 1.3em; 37 | } 38 | footer { 39 | padding: 10px; 40 | font-size: .8rem; 41 | } 42 | .title, .tips { 43 | height: 30px; 44 | padding-top: 20px; 45 | position: relative; 46 | } 47 | .title { 48 | border-radius: 40px 40px 0 0; 49 | } 50 | .tips { 51 | border-radius: 0 0 40px 40px; 52 | } 53 | 54 | table { 55 | table-layout: fixed; 56 | word-wrap: break-word; 57 | position: relative; 58 | border-collapse: collapse; 59 | width: 100%; 60 | } 61 | .content { 62 | font-size: .8rem; 63 | width: 30%; 64 | vertical-align: top; 65 | } 66 | .contentName { 67 | padding: 10px; 68 | padding-left: 20px; 69 | width: 25%; 70 | font-weight:bold; 71 | vertical-align: top; 72 | } 73 | .contentDetails { 74 | padding: 10px; 75 | } 76 | .contentAction { 77 | width: 10%; 78 | padding-right: 20px; 79 | text-align: right; 80 | } 81 | 82 | .nameText { 83 | font-weight: bold; 84 | } 85 | .records textarea { 86 | height: 1.2em; 87 | font-size: 1em; 88 | resize: none; 89 | width: 100%; 90 | border: none; 91 | overflow: hidden; 92 | background-color: transparent; 93 | font-size: .9em; 94 | } 95 | 96 | /* buttons */ 97 | .detailBtn { 98 | position: absolute; 99 | left: 20px; 100 | top: 10px; 101 | background: none; 102 | border: none; 103 | cursor: pointer; 104 | } 105 | .addBtn { 106 | background: none; 107 | border: none; 108 | cursor: pointer; 109 | } 110 | .detailBtn img{ 111 | width: 20px; 112 | height: 20px; 113 | } 114 | .deleteBtn{ 115 | background-image: url("../images/sprite.png"); 116 | background-position: -5px -568px; 117 | width: 18px; 118 | height: 18px; 119 | display:inline-block; 120 | } 121 | 122 | tr:hover .deleteBtn { 123 | 124 | cursor: pointer; 125 | } 126 | 127 | 128 | .tableRows{ 129 | border: 2px solid #DFE3E6 130 | } 131 | 132 | 133 | 134 | .fieldname { 135 | text-align: right; 136 | border-right: 1px solid #fff; 137 | } 138 | 139 | .errorMsg { 140 | color: red; 141 | font-weight:normal; 142 | } 143 | .newappIcon { 144 | display: block; 145 | max-width: 100px; 146 | max-height: 100px; 147 | margin: 0 auto; 148 | padding: 10px; 149 | } 150 | 151 | .appTitle { 152 | padding: 15px; 153 | margin: 0 auto; 154 | text-align: center; 155 | font-size: 1.5rem; 156 | } 157 | 158 | .description { 159 | padding-left: 50px; 160 | padding-right: 50px; 161 | text-align: center; 162 | } 163 | 164 | .infoImg { 165 | cursor: pointer; 166 | } 167 | .flexBox { 168 | display: flex; 169 | flex-wrap: wrap; 170 | } 171 | .contentTiles { 172 | padding: 10px; 173 | font-size: .8rem; 174 | } 175 | .uploadBox { 176 | margin: 0 auto; 177 | text-align: center; 178 | } 179 | .appinfo { 180 | padding: 10px; 181 | } 182 | -------------------------------------------------------------------------------- /public/uploads/uploadedFileName: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM-Cloud/nodejs-cloudant/cd3d615b476e6cdc68eb176b1ce1ea4587195849/public/uploads/uploadedFileName -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * GET home page. 4 | */ 5 | 6 | exports.index = function(req, res){ 7 | res.render('index.html', { title: 'Cloudant Boiler Plate' }); 8 | }; -------------------------------------------------------------------------------- /routes/user.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * GET users listing. 4 | */ 5 | 6 | exports.list = function(req, res){ 7 | res.send("respond with a resource"); 8 | }; -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Favorites Organizer powered by Cloudant 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 18 | 19 | 20 |
21 |
22 |
23 |
24 |
25 | 28 | 31 |
32 | Help 33 |
34 | 43 |
44 |




45 |
46 |
47 | 48 | 49 |
50 |


51 |
52 | 53 | 54 | --------------------------------------------------------------------------------