Changeset 525
- Timestamp:
- 03/19/14 21:33:13 (11 years ago)
- Location:
- Dev/trunk/src
- Files:
-
- 12 added
- 2 deleted
- 24 edited
Legend:
- Unmodified
- Added
- Removed
-
Dev/trunk/src/client/qed-client/model/classes/_Class.js
r510 r525 71 71 } else if ( lang.isObject(obj) ) { 72 72 objectFuns.forEach(obj,function(v,prop){ 73 if ( v === null || 74 v === "" || 75 (typeof v === "number" && isNaN(v)) ) { 73 if ( ( v === null || 74 v === "" || 75 (typeof v === "number" && isNaN(v)) ) && 76 prop !== 'subcode' ) // HACK : this hardcoded exclusion for subcode is quite nasty 77 { 76 78 delete obj[prop]; 77 79 } else { -
Dev/trunk/src/client/qed-client/model/widgets/QuestionEditorPreviewItem.js
r513 r525 149 149 if ( this.innerWidget !== null ) { 150 150 if ( this._editing === true ) { 151 this._onChange(); 152 // how to force event on widget here? 151 // because the widget doesn't generate an event 152 // unless it loses focus, we set the value here 153 // just to be sure not to lose any user input 154 this._setValueInternal(this.innerWidget.get('value')); 153 155 } 154 156 this.removeChild(this.innerWidget); -
Dev/trunk/src/client/qed-client/model/widgets/questions/ScaleInputWidget.js
r511 r525 22 22 this.minNode.innerHTML = this.minLabel || ""; 23 23 this.maxNode.innerHTML = this.maxLabel || ""; 24 if ( this.naLabel !== null) {24 if ( this.naLabel ) { 25 25 this.naNode.innerHTML = this.naLabel; 26 26 } … … 56 56 className: 'max' 57 57 }, tr); 58 if ( this.naLabel !== null && this.naLabel !== "") {58 if ( this.naLabel ) { 59 59 td = domConstruct.create("td", {}, tr); 60 60 radio = new RadioButton({ -
Dev/trunk/src/client/qed-client/model/widgets/questions/templates/MultipleChoiceInputConfigWidget.html
r511 r525 3 3 <label class="qedLabel" for="subcode">Subcode</label> 4 4 <input data-dojo-type="dijit/form/ValidationTextBox" 5 data-dojo-props=" required:true,placeHolder:'Subcode'"5 data-dojo-props="placeHolder:'Subcode'" 6 6 class="subcode" 7 7 name="subcode"/> -
Dev/trunk/src/client/qed-client/model/widgets/questions/templates/NumberInputConfigWidget.html
r510 r525 5 5 data-dojo-attach-point="subcodeBox" 6 6 data-dojo-type="dijit/form/ValidationTextBox" 7 data-dojo-props=" required: true,placeholder: 'Subcode'"7 data-dojo-props="placeholder: 'Subcode'" 8 8 name="subcode"></div> 9 9 </div> -
Dev/trunk/src/client/qed-client/model/widgets/questions/templates/ScaleInputConfigRowWidget.html
r506 r525 2 2 <th class="subcode"> 3 3 <div data-dojo-type="dijit/form/ValidationTextBox" 4 data-dojo-props=" required: true,placeholder: 'Subcode'"4 data-dojo-props="placeholder: 'Subcode'" 5 5 name="subcode"></div> 6 6 </th> -
Dev/trunk/src/client/qed-client/model/widgets/questions/templates/StringInputConfigWidget.html
r510 r525 5 5 data-dojo-attach-point="subcodeBox" 6 6 data-dojo-type="dijit/form/ValidationTextBox" 7 data-dojo-props=" required: true,placeholder: 'Subcode'"7 data-dojo-props="placeholder: 'Subcode'" 8 8 name="subcode"></div> 9 9 </div> -
Dev/trunk/src/client/qed-client/model/widgets/questions/templates/TextInputConfigWidget.html
r510 r525 5 5 data-dojo-attach-point="subcodeBox" 6 6 data-dojo-type="dijit/form/ValidationTextBox" 7 data-dojo-props=" required: true,placeholder: 'Subcode'"7 data-dojo-props="placeholder: 'Subcode'" 8 8 name="subcode"></div> 9 9 </div> -
Dev/trunk/src/client/qed-client/widgets/ListWidget.js
r513 r525 98 98 _getValueAttr: function() { 99 99 return array.map( 100 this._getDescendantFormWidgets(), 101 function(child) { return child.get('value'); } 102 ,this); 100 this.source.getAllNodes(), 101 lang.hitch(this,function(node){ 102 var widget = registry.byNode(node); 103 if ( widget && 'value' in widget ) { 104 return widget.get('value'); 105 } else { 106 return this.source.getItem(node.id).data; 107 } 108 })); 103 109 }, 104 110 _setValueAttr: function(value,priorityChange) { -
Dev/trunk/src/client/qed-client/widgets/_ComplexValueMixin.js
r513 r525 64 64 if (typeof widgets[0].checked === 'boolean') { 65 65 array.forEach(widgets, function(w){ 66 w.set('value', array.indexOf(values, w._get('value')) !== -1, priorityChange); 66 w.set('value', 67 array.indexOf(values,w._get('value'))!==-1, 68 priorityChange); 67 69 }); 68 70 } else if (widgets[0].multiple) { … … 90 92 _setDisabledAttr: function(disabled) { 91 93 this.inherited(arguments); 94 this._set('disabled',disabled); 92 95 array.forEach( 93 96 this._getDescendantFormWidgets(), … … 98 101 _setReadOnlyAttr: function(readOnly) { 99 102 this.inherited(arguments); 103 this._set('readOnly',readOnly); 100 104 array.forEach( 101 105 this._getDescendantFormWidgets(), -
Dev/trunk/src/package.json
r489 r525 8 8 "passport": "~0.1.18", 9 9 "passport-local": "~0.1.6", 10 "q": "~0.9.7",11 10 "request": "~2.21.0", 12 11 "ya-csv": "~0.9.3", -
Dev/trunk/src/server/app.js
r523 r525 5 5 , path = require("path") 6 6 , proxy = require("./util/simple-http-proxy") 7 , C SV = require("ya-csv")8 , CouchDB = require('./util/couch').CouchDB7 , CouchDB = require('./util/couch') 8 , HTTPResult = require('./util/http-result') 9 9 , _ = require("underscore") 10 , validator = require("./util/validator")11 , HTTPResult = require("./util/http-result")12 , etags = require("./util/etags")13 , cryptoken = require('./util/crypto-token')14 , Q = require('q')15 10 ; 16 11 17 exports. App = function(env) {12 exports.app = function(env) { 18 13 19 14 assertSetting("couchServerURL", env, _.isString); … … 27 22 .then(function(schemaInfo){ 28 23 if ( schemaInfo.version !== schema.version ) { 29 return Q.reject(new Error("Found schema version "+schemaInfo.version+ 30 ", expected "+schema.version)); 24 return HTTPResult.fail( 25 new Error("Found schema version "+schemaInfo.version+ 26 ", expected "+schema.version)); 31 27 } else if ( schemaInfo.viewsVersion !== views.schemaInfo.viewsVersion ) { 32 return Q.reject(new Error("Found views version "+schemaInfo.viewsVersion+ 33 ", expected "+views.schemaInfo.viewsVersion)); 28 return HTTPResult.fail( 29 new Error("Found views version "+schemaInfo.viewsVersion+ 30 ", expected "+views.schemaInfo.viewsVersion)); 34 31 } else { 35 32 return configureApp(env,couch,schema); … … 40 37 41 38 function configureApp(env,couch,schema) { 39 40 var apiUtil = require('./api/util')(couch,schema); 42 41 43 42 function clientPath(relativePath) { … … 91 90 92 91 // static resources 93 app.get('/', function(req uest, response){94 res ponse.sendfile(clientPath('index.html'));92 app.get('/', function(req, res){ 93 res.sendfile(clientPath('index.html')); 95 94 }); 96 app.get('/*.html', function(req uest, response) {97 res ponse.sendfile(clientPath(request.path));95 app.get('/*.html', function(req, res) { 96 res.sendfile(clientPath(req.path)); 98 97 }); 99 98 _.each(['/dojo', '/dijit', '/dojox', '/qed', '/qed-client'], function(dir){ … … 122 121 }); 123 122 124 var JSON_MIME = 'application/json'; 125 var CSV_MIME = 'application/json'; 126 function ensureMIME(mimeType) { 127 return function(req,res,next) { 128 if (!req.accepts(mimeType)) { 129 res.send(406); 130 } else { 131 res.set({ 132 'Content-Type': mimeType 133 }); 134 next(); 135 } 136 }; 137 } 138 139 function identity(obj) { return obj; } 140 function stripAndReturnPrivates(obj) { 141 var priv = {}; 142 _.each(obj||{},function(val,key){ 143 if (key.substring(0,1) === '_') { 144 priv[key] = val; 145 delete obj[key]; 146 } 147 }); 148 return priv; 149 } 150 function areDocsUnique(docs) { 151 return _.chain(docs) 152 .map(function(doc){ return doc._id; }) 153 .uniq() 154 .value().length === docs.length; 155 } 156 function areDocsPublished(docs) { 157 return _.every(docs, isDocPublished); 158 } 159 function isDocPublished(doc) { 160 return doc.publicationDate; 161 } 123 app.use('/api/questions', ensureAuthenticated); 124 app.use('/api/questions', 125 require('./api/questions')(couch,schema).app); 162 126 163 function handleUnknownResponse(status,error) { 164 return new HTTPResult(500,{error: error.reason}); 165 } 166 function handleUnknownError(error) { 167 return new HTTPResult(500, {error: "Unknown error", innerError: error}); 168 } 169 function handleRowValues(result) { 170 return _.map(result.rows, function(item) { return item.value; }); 171 } 172 function handleRowDocs(result) { 173 return _.map(result.rows, function(item) { return item.doc; }); 174 } 175 function handleRowFirstDoc(result) { 176 if ( result.rows.length > 0 ) { 177 return result.rows[0].doc; 178 } else { 179 return new HTTPResult(404,{error:"No document found."}); 180 } 181 } 182 function handleRowFirstValue(result) { 183 if ( result.rows.length > 0 ) { 184 return result.rows[0].value; 185 } else { 186 return new HTTPResult(404,{error:"No document found."}); 187 } 188 } 189 function handleRowDictOfDocs(result) { 190 return _.reduce(result.rows, function(dict,item) { 191 dict[item.key] = item.doc; 192 return dict; 193 }, {}); 194 } 195 function handleRowDictOfValues(result) { 196 return _.reduce(result.rows, function(dict,item) { 197 dict[item.key] = item.value; 198 return dict; 199 }, {}); 200 } 127 app.use('/api/categories', ensureAuthenticated); 128 app.use('/api/categories', 129 require('./api/categories')(couch,schema).app); 201 130 202 function getDocumentsOfType (type) { 203 var url = '_design/default/_view/by_type?key='+JSON.stringify(type); 204 return HTTPResult.fromResponsePromise(couch.get(url).response, 205 handleUnknownError) 206 .handle({ 207 200: handleRowValues, 208 404: function() { return {error: "Cannot find collection of type "+type}; }, 209 default: handleUnknownResponse 210 }); 211 } 212 function getDocument(id,rev,type) { 213 var opts = {headers:{}}; 214 if (rev) { 215 opts.headers['If-Non-Match'] = '"'+rev+'"'; 216 } 217 return HTTPResult.fromResponsePromise(couch.get(id,opts).response, 218 handleUnknownError) 219 .handle({ 220 200: function(doc){ 221 if ( doc.type !== type ) { 222 return new HTTPResult(404,{error:"Document not found."}); 223 } else { 224 var priv = stripAndReturnPrivates(doc); 225 if ( priv._rev !== rev ) { 226 doc._id = priv._id; 227 doc._rev = priv._rev; 228 return doc; 229 } else { 230 return new HTTPResult(304); 231 } 232 } 233 }, 234 304: identity, 235 default: handleUnknownResponse 236 }); 237 } 238 function putDocument(id,rev,type,doc) { 239 var priv = stripAndReturnPrivates(doc); 240 var valid; 241 if ( doc.type === type && (valid = validator(doc, schema)).valid ) { 242 var opts = rev ? {headers:{'If-Match':'"'+rev+'"'}} : {}; 243 return HTTPResult.fromResponsePromise(couch.put(id,doc,opts).response, 244 handleUnknownError) 245 .handle({ 246 201: function(res){ 247 doc._id = res.id; 248 doc._rev = res.rev; 249 return doc; 250 }, 251 409: function(error) { 252 return {error: error.reason}; 253 }, 254 default: handleUnknownResponse 255 }); 256 } else { 257 return new HTTPResult(400,{error: "Document failed schema verification.", valid: valid}); 258 } 259 } 260 function deleteDocument(id,rev) { 261 if ( rev ) { 262 var opts = {headers:{'If-Match':'"'+rev+'"'}}; 263 return HTTPResult.fromResponsePromise(couch.delete(id,opts).response, 264 handleUnknownError) 265 .handle({ 266 200: identity, 267 409: function(error) { 268 return {error: error.reason}; 269 }, 270 default: handleUnknownResponse 271 }); 272 } else { 273 return new HTTPResult(409, {error: "Cannot identify document revision to delete."}); 274 } 275 } 276 function postDocument(type,doc) { 277 var priv = stripAndReturnPrivates(doc); 278 var valid; 279 if ( doc.type === type && (valid = validator(doc, schema)).valid ) { 280 return HTTPResult.fromResponsePromise(couch.post(doc).response, 281 handleUnknownError) 282 .handle({ 283 201: function(response) { 284 doc._id = response.id; 285 doc._rev = response.rev; 286 return doc; 287 }, 288 default: handleUnknownResponse 289 }); 290 } else { 291 return new HTTPResult(400,{error: "Document failed schema verification.", valid: valid}); 292 } 293 } 131 app.use('/api/topics', ensureAuthenticated); 132 app.use('/api/topics', 133 require('./api/topics')(couch,schema).app); 294 134 295 function makeDocsGet(type) { 296 return function(req,res) { 297 getDocumentsOfType(type) 298 .handle(res.send.bind(res)); 299 }; 300 } 301 function makeDocGet_id(type) { 302 return function(req,res) { 303 var id = req.params.id; 304 var rev = etags.parse(req.header('If-Non-Match'))[0]; 305 getDocument(id,rev,type) 306 .handle({ 307 200: function(doc){ 308 res.set({ 309 'ETag': etags.format([doc._rev]) 310 }).send(200, doc); 311 }, 312 default: res.send.bind(res) 313 }); 314 }; 315 } 316 function makeDocPut_id(type) { 317 return function(req,res) { 318 var id = req.params.id; 319 var doc = req.body; 320 var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev); 321 putDocument(id,rev,type,doc) 322 .handle({ 323 201: function(doc) { 324 res.set({ 325 'ETag': etags.format([doc._rev]) 326 }).send(201, doc); 327 }, 328 default: res.send.bind(res) 329 }); 330 }; 331 } 332 function makeDocDel_id(type) { 333 return function(req,res) { 334 var id = req.params.id; 335 var doc = req.body; 336 var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev); 337 deleteDocument(id,rev) 338 .handle(res.send.bind(res)); 339 }; 340 } 341 function makeDocPost(type) { 342 return function(req,res) { 343 var doc = req.body; 344 postDocument(type,doc) 345 .handle({ 346 201: function(doc) { 347 res.set({ 348 'ETag': etags.format([doc._rev]) 349 }).send(201, doc); 350 }, 351 default: res.send.bind(res) 352 }); 353 }; 354 } 135 app.use('/api/surveys', ensureAuthenticated); 136 app.use('/api/surveys', 137 require('./api/surveys')(couch,schema).app); 355 138 356 // Questions 357 function getQuestionsWithCode(code,sub) { 358 var url = '_design/questions/_view/'+(sub||'all')+'_by_code'; 359 var query = {include_docs:true,key:code}; 360 return HTTPResult.fromResponsePromise(couch.get(url,{query:query}).response, 361 handleUnknownError) 362 .handle({ 363 200: handleRowDocs, 364 default: handleUnknownResponse 365 }); 366 } 367 function getQuestions(sub) { 368 var url = '_design/questions/_view/'+(sub||'all'); 369 var query = {include_docs:true}; 370 return HTTPResult.fromResponsePromise(couch.get(url,{query:query}).response, 371 handleUnknownError) 372 .handle({ 373 200: handleRowDocs, 374 default: handleUnknownResponse 375 }); 376 } 377 function getQuestionsAndCodes() { 378 var url = '_design/questions/_view/by_code'; 379 var query = {include_docs:true}; 380 return HTTPResult.fromResponsePromise(couch.get(url,{query:query}).response, 381 handleUnknownError) 382 .handle({ 383 200: handleRowDictOfDocs, 384 default: handleUnknownResponse 385 }); 386 } 387 function getQuestionsWithTopic(topic,sub) { 388 var url = '_design/questions/_view/'+(sub||'all')+'_topics'; 389 var query = {reduce:false,include_docs:true,key:topic}; 390 return HTTPResult.fromResponsePromise(couch.get(url,{query:query}).response, 391 handleUnknownError) 392 .handle({ 393 200: handleRowDocs, 394 default: handleUnknownResponse 395 }); 396 } 397 function getQuestionsWithCategoryAndTopic(category,topic,sub) { 398 var hasTopic = typeof topic !== 'undefined'; 399 var url = '_design/questions/_view/'+(sub||'all'); 400 var query = {reduce:false,include_docs:true, 401 startkey:hasTopic?[category,topic]:[category], 402 endkey:hasTopic?[category,topic]:[category,{}]}; 403 return HTTPResult.fromResponsePromise(couch.get(url,{query:query}).response, 404 handleUnknownError) 405 .handle({ 406 200: handleRowDocs, 407 default: handleUnknownResponse 408 }); 409 } 410 function collectFields(field,obj) { 411 if ( _.isArray(obj) ) { 412 return _.reduce(obj,function(subcodes,val,idx){ 413 return subcodes.concat(collectFields(field,val)); 414 },[]); 415 } else if ( _.isObject(obj) ) { 416 return _.reduce(obj,function(subcodes,val,key){ 417 if ( key === field ) { 418 subcodes.push(val); 419 } 420 return subcodes.concat(collectFields(field,val)); 421 },[]); 422 } else { 423 return []; 424 } 425 } 426 function areSubcodesUnique(content) { 427 var subcodes = collectFields('subcode',content); 428 return _.uniq(subcodes).length === subcodes.length; 429 } 430 app.get('/api/questions', 431 ensureAuthenticated, 432 ensureMIME(JSON_MIME), 433 function(req,res) { 434 var sub = 'published' in req.query && 'published'; 435 var hr; 436 if ( 'category' in req.query ) { 437 hr = getQuestionsWithCategoryAndTopic(req.query.category, 438 req.query.topic,sub); 439 } else if ( 'topic' in req.query ) { 440 hr = getQuestionsWithTopic(req.query.topic,sub); 441 } else if ( 'code' in req.query ) { 442 hr = getQuestionsWithCode(req.query.code,sub); 443 } else { 444 hr = getQuestions(sub); 445 } 446 hr.handle(res.send.bind(res)); 447 }); 448 app.post('/api/questions', 449 ensureAuthenticated, 450 ensureMIME(JSON_MIME), 451 function (req,res) { 452 var doc = req.body; 453 getQuestionsWithCode(doc.code) 454 .handle({ 455 200: function(others) { 456 if ( others.length > 0 ) { 457 return new HTTPResult(403,{error:"Other question with this code already exists."}); 458 } else if ( !areSubcodesUnique(doc.content) ) { 459 return new HTTPResult(400,{error:"Subcodes are not unqiue."}); 460 } else { 461 return postDocument('Question',doc); 462 } 463 } 464 }) 465 .handle(res.send.bind(res)); 466 }); 467 app.get('/api/questions/:id', 468 ensureAuthenticated, 469 ensureMIME(JSON_MIME), 470 makeDocGet_id('Question')); 471 app.put('/api/questions/:id', 472 ensureAuthenticated, 473 ensureMIME(JSON_MIME), 474 function (req,res) { 475 var id = req.params.id; 476 var doc = req.body; 477 var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev); 478 getQuestionsWithCode(doc.code) 479 .handle({ 480 200: function(others) { 481 if ( others.length > 0 && _.some(others,function(other){ return other._id !== id; }) ) { 482 return new HTTPResult(403,{error:"Other question with this code already exists."}); 483 } else if ( !areSubcodesUnique(doc.content) ) { 484 return new HTTPResult(400,{error:"Subcodes are not unqiue."}); 485 } else { 486 return putDocument(id,rev,'Question',doc); 487 } 488 } 489 }) 490 .handle(res.send.bind(res)); 491 }); 492 app.delete('/api/questions/:id', 493 ensureAuthenticated, 494 ensureMIME(JSON_MIME), 495 makeDocDel_id('Question')); 139 app.use('/api/surveyRuns', ensureAuthenticated); 140 app.use('/api/surveyRuns', 141 require('./api/surveyRuns')(couch,schema).app); 496 142 497 // Categories and topics 498 function getTopics(sub) { 499 var url = '_design/questions/_view/'+(sub||'all')+'_topics'; 500 return HTTPResult.fromResponsePromise(couch.get(url,{query:{reduce:true,group:true}}).response, 501 handleUnknownError) 502 .handle({ 503 200: function(result) { 504 return _.map(result.rows, function(item) { return {name:item.key, count:item.value}; }); 505 }, 506 default: handleUnknownResponse 507 }); 508 } 509 function getCategories(sub) { 510 var url = '_design/questions/_view/'+(sub||'all'); 511 return HTTPResult.fromResponsePromise(couch.get(url,{query:{reduce:true,group:true,group_level:1}}).response, 512 handleUnknownError) 513 .handle({ 514 200: function(result) { 515 return _.map(result.rows, function(item) { 516 return { name:item.key[0], count:item.value }; 517 }); 518 }, 519 default: handleUnknownResponse 520 }); 521 } 522 function getTopicsWithCategory(category,sub) { 523 var url = '_design/questions/_view/'+(sub||'all'); 524 return HTTPResult.fromResponsePromise(couch.get(url,{query:{reduce:true,group:true,group_level:2,startkey:[category],endkey:[category,{}]}}).response, 525 handleUnknownError) 526 .handle({ 527 200: function(result) { 528 return _.map(result.rows, function(item) { return { name:item.key[1], count:item.value }; }); 529 }, 530 default: handleUnknownResponse 531 }); 532 } 533 app.get('/api/categories', 534 ensureAuthenticated, 535 ensureMIME(JSON_MIME), 536 function(req,res) { 537 var sub = 'published' in req.query && 'published'; 538 getCategories(sub) 539 .handle(res.send.bind(res)); 540 }); 541 app.get('/api/categories/:category/topics', 542 ensureAuthenticated, 543 ensureMIME(JSON_MIME), 544 function(req,res) { 545 var category = req.params.category; 546 var sub = 'published' in req.query && 'published'; 547 getTopicsWithCategory(category,sub) 548 .handle(res.send.bind(res)); 549 }); 550 app.get('/api/topics', 551 ensureAuthenticated, 552 ensureMIME(JSON_MIME), 553 function(req,res) { 554 var sub = 'published' in req.query && 'published'; 555 var hr; 556 if ( 'category' in req.query ) { 557 hr = getTopicsWithCategory(req.query.category,sub); 558 } else { 559 hr = getTopics(sub); 560 } 561 hr.handle(res.send.bind(res)); 562 }); 143 app.use('/api/responses', ensureAuthenticated); 144 app.use('/api/responses', 145 require('./api/responses')(couch,schema).app); 563 146 564 // Surveys 565 app.get('/api/surveys', 566 ensureAuthenticated, 567 ensureMIME(JSON_MIME), 568 function(req,res) { 569 var url; 570 if ( 'drafts' in req.query ) { 571 url = '_design/surveys/_view/drafts'; 572 } else if ( 'published' in req.query ) { 573 url = '_design/surveys/_view/published'; 574 } else { 575 url = '_design/default/_view/by_type?key='+JSON.stringify('Survey'); 576 } 577 HTTPResult.fromResponsePromise(couch.get(url).response, 578 handleUnknownError) 579 .handle({ 580 200: function(result) { 581 return _.map(result.rows, function(item) { return item.value; }); 582 }, 583 default: handleUnknownResponse 584 }) 585 .handle(res.send.bind(res)); 586 }); 587 app.post('/api/surveys', 588 ensureAuthenticated, 589 ensureMIME(JSON_MIME), 590 function(req,res) { 591 var doc = req.body; 592 var hr; 593 if ( !areDocsUnique(doc.questions) ) { 594 hr = new HTTPResult(400,{error:"Survey contains duplicate questions."}); 595 } else if ( isDocPublished(doc) && !areDocsPublished(doc.questions) ) { 596 hr = new HTTPResult(400,{error:"Cannot publish Survey with unpublished questions."}); 597 } else { 598 hr = postDocument('Survey',doc); 599 } 600 hr.handle(res.send.bind(res)); 601 }); 602 app.get('/api/surveys/:id', 603 ensureAuthenticated, 604 ensureMIME(JSON_MIME), 605 makeDocGet_id('Survey')); 606 app.put('/api/surveys/:id', 607 ensureAuthenticated, 608 ensureMIME(JSON_MIME), 609 function(req,res) { 610 var id = req.params.id; 611 var doc = req.body; 612 var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev); 613 if ( !areDocsUnique(doc.questions) ) { 614 new HTTPResult(400,{error:"Survey contains duplicate questions."}) 615 .handle(res.send.bind(res)); 616 } else if ( isDocPublished(doc) && !areDocsPublished(doc.questions) ) { 617 new HTTPResult(400,{error:"Cannot publish Survey with unpublished questions."}) 618 .handle(res.send.bind(res)); 619 } else { 620 putDocument(id,rev,'Survey',doc) 621 .handle({ 622 201: function(doc) { 623 res.set({ 624 'ETag': etags.format([doc._rev]) 625 }).send(201, doc); 626 }, 627 default: res.send.bind(res) 628 }); 629 } 630 }); 631 app.delete('/api/surveys/:id', 632 ensureAuthenticated, 633 ensureMIME(JSON_MIME), 634 makeDocDel_id('Survey')); 635 636 // SurveyRuns 637 app.get('/api/surveyRuns', 638 ensureAuthenticated, 639 ensureMIME(JSON_MIME), 640 makeDocsGet('SurveyRun')); 641 app.post('/api/surveyRuns', 642 ensureAuthenticated, 643 ensureMIME(JSON_MIME), 644 function(req,res) { 645 var doc = req.body; 646 var hr; 647 if ( !isDocPublished(doc.survey) ) { 648 hr = new HTTPResult(400,{error:"Cannot run unpublished survey."}); 649 } else { 650 hr = randomToken() 651 .handle({ 652 201: function(token) { 653 doc.secret = token; 654 return postDocument('SurveyRun',doc); 655 } 656 }); 657 } 658 hr.handle(res.send.bind(res)); 659 }); 660 app.get('/api/surveyRuns/:id', 661 ensureAuthenticated, 662 ensureMIME(JSON_MIME), 663 makeDocGet_id('SurveyRun')); 664 app.put('/api/surveyRuns/:id', 665 ensureAuthenticated, 666 ensureMIME(JSON_MIME), 667 function (req,res) { 668 var id = req.params.id; 669 var doc = req.body; 670 var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev); 671 var hr; 672 if ( !isDocPublished(doc.survey) ) { 673 hr = new HTTPResult(400,{error:"Cannot run unpublished survey."}); 674 } else if ( typeof doc.secret === 'undefined' ) { 675 hr = randomToken() 676 .handle({ 677 201: function(token) { 678 doc.secret = token; 679 return putDocument(id,rev,'SurveyRun',doc); 680 } 681 }); 682 } else { 683 hr = putDocument(id,rev,'SurveyRun',doc); 684 } 685 hr.handle(res.send.bind(res)); 686 }); 687 app.delete('/api/surveyRuns/:id', 688 ensureAuthenticated, 689 ensureMIME(JSON_MIME), 690 function(req,res) { 691 var id = req.params.id; 692 var doc = req.body; 693 var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev); 694 getResponsesBySurveyRunId(id) 695 .handle({ 696 200: function(responses) { 697 if ( responses.length > 0 ) { 698 return new HTTPResult(403,{error:"Cannot delete run that has responses."}); 699 } else { 700 return deleteDocument(id,rev); 701 } 702 } 703 }).handle(res.send.bind(res)); 704 }); 705 app.get('/api/surveyRuns/:id/responses', 706 ensureAuthenticated, 707 ensureMIME(JSON_MIME), 708 function(req,res) { 709 var id = req.params.id; 710 getResponsesBySurveyRunId(id) 711 .handle(res.send.bind(res)); 712 }); 713 app.get('/api/surveyRuns/:id/responses.csv', 714 ensureAuthenticated, 715 ensureMIME(CSV_MIME), 716 function(req, res) { 717 var id = req.params.id; 718 getResponsesBySurveyRunId(id) 719 .handle({ 720 200: function(responses) { 721 var answers = _.map(responses,function(response){ 722 return response.answers; 723 }); 724 res.set({ 725 'Content-Disposition': 'attachment; filename=surveyRun-'+id+'-responses.csv' 726 }); 727 res.status(200); 728 writeObjectsToCSVStream(answers, res); 729 res.end(); 730 }, 731 default: res.send.bind(res) 732 }); 733 }); 734 735 // Responses 736 function getResponsesBySurveyRunId(surveyRunId) { 737 var url = '_design/responses/_view/by_surveyrun?key='+JSON.stringify(surveyRunId); 738 return HTTPResult.fromResponsePromise(couch.get(url).response, 739 handleUnknownError) 740 .handle({ 741 200: handleRowValues, 742 default: handleUnknownResponse 743 }); 744 } 745 app.get('/api/responses', 746 ensureAuthenticated, 747 ensureMIME(JSON_MIME), 748 function(req,res){ 749 var hr; 750 if ( 'surveyRunId' in req.query ) { 751 hr = getResponsesBySurveyRunId(req.query.surveyRunId); 752 } else { 753 hr = getDocumentsOfType('Response'); 754 } 755 hr.handle(res.send.bind(res)); 756 }); 757 app.get('/api/responses/:id', 758 ensureAuthenticated, 759 ensureMIME(JSON_MIME), 760 makeDocGet_id('Response')); 761 app.post('/api/responses', 762 ensureAuthenticated, 763 ensureMIME(JSON_MIME), 764 function (req,res) { 765 var doc = req.body; 766 randomToken() 767 .handle({ 768 201: function(token) { 769 doc.secret = token; 770 return postDocument('Response',doc); 771 } 772 }) 773 .handle(res.send.bind(res)); 774 }); 775 app.put('/api/responses/:id', 776 ensureAuthenticated, 777 ensureMIME(JSON_MIME), 778 function (req,res) { 779 var id = req.params.id; 780 var doc = req.body; 781 var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev); 782 var hr; 783 if ( typeof doc.secret === 'undefined' ) { 784 hr = randomToken() 785 .handle({ 786 201: function(token) { 787 doc.secret = token; 788 return putDocument(id,rev,'Response',doc); 789 } 790 }); 791 } else { 792 hr = putDocument(id,rev,'Response',doc); 793 } 794 hr.handle(res.send.bind(res)); 795 }); 796 app.delete('/api/responses/:id', 797 ensureAuthenticated, 798 ensureMIME(JSON_MIME), 799 makeDocDel_id('Response')); 800 801 //respondents api 802 function isSurveyRunRunning(surveyRun) { 803 var now = new Date(); 804 var afterStart = !surveyRun.startDate || now >= new Date(surveyRun.startDate); 805 var beforeEnd = !surveyRun.endDate || now <= new Date(surveyRun.endDate); 806 return afterStart && beforeEnd; 807 } 808 app.get('/api/open/responses/:id', 809 ensureMIME(JSON_MIME), 810 function(req,res) { 811 var id = req.params.id; 812 var rev = etags.parse(req.header('If-Non-Match'))[0]; 813 var secret = req.query.secret; 814 getDocument(id,rev,'Response') 815 .handle({ 816 200: function(response) { 817 if ( response.secret === secret ) { 818 return getDocument(response.surveyRunId,[],'SurveyRun') 819 .handle({ 820 200: function(surveyRun) { 821 if ( !isSurveyRunRunning(surveyRun) ) { 822 return new HTTPResult(404,{error:"Survey is not running anymore."}); 823 } else { 824 response._surveyRun = surveyRun; 825 return response; 826 } 827 } 828 }); 829 } else { 830 return new HTTPResult(403,{error: "Wrong secret for response"}); 831 } 832 } 833 }) 834 .handle(res.send.bind(res)); 835 }); 836 app.post('/api/open/responses', 837 ensureMIME(JSON_MIME), 838 function(req,res) { 839 var secret = req.query.secret; 840 var response = req.body; 841 delete response._surveyRun; 842 getDocument(response.surveyRunId,[],'SurveyRun') 843 .handle({ 844 200: function(surveyRun) { 845 if ( surveyRun.secret !== secret ) { 846 return new HTTPResult(403,{error:"Wrong secret for surveyRun."}); 847 } else if ( !isSurveyRunRunning(surveyRun) ) { 848 return new HTTPResult(404,{error:"Survey is not running anymore."}); 849 } else if ( surveyRun.mode === 'closed' ) { 850 return new HTTPResult(403,{error:"Survey is closed and doesn't allow new responses."}); 851 } else { 852 return randomToken() 853 .handle({ 854 201: function(token) { 855 response.secret = token; 856 return postDocument('Response',response) 857 .handle({ 858 201: function(doc){ 859 doc._surveyRun = surveyRun; 860 return doc; 861 } 862 }); 863 } 864 }); 865 } 866 } 867 }) 868 .handle(res.send.bind(res)); 869 }); 870 app.put('/api/open/responses/:id', 871 ensureMIME(JSON_MIME), 872 function(req,res){ 873 var id = req.params.id; 874 var doc = req.body; 875 var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev); 876 var secret = req.query.secret || doc.secret; 877 delete doc._surveyRun; 878 getDocument(id,[],'Response') 879 .handle({ 880 200: function(prev) { 881 if ( prev.secret !== secret ) { 882 return new HTTPResult(403,{error: "Secrets are not the same."}); 883 } else if ( prev.surveyRunId !== doc.surveyRunId ) { 884 return new HTTPResult(400,{error:"Response cannot change it's surveyRunId."}); 885 } else { 886 doc.secret = secret; // restore in case it got lost or was changed 887 return getDocument(doc.surveyRunId,[],'SurveyRun') 888 .handle({ 889 200: function(surveyRun) { 890 if ( !isSurveyRunRunning(surveyRun) ) { 891 return new HTTPResult(404,{error:"Survey is not running anymore."}); 892 } else { 893 return putDocument(id,rev,'Response',doc) 894 .handle({ 895 200: function(doc) { 896 doc._surveyRun = surveyRun; 897 return new HTTPResult(201,doc); 898 } 899 }); 900 } 901 } 902 }); 903 } 904 } 905 }) 906 .handle(res.send.bind(res)); 907 }); 908 app.delete('/api/open/responses/:id', 909 ensureMIME(JSON_MIME), 910 function(req,res){ 911 var id = req.params.id; 912 var doc = req.body; 913 var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev); 914 var secret = req.query.secret || doc.secret; 915 delete doc._surveyRun; 916 getDocument(id,[],'Response') 917 .handle({ 918 200: function(prev) { 919 if ( prev.secret !== secret ) { 920 return new HTTPResult(403,{error: "Secrets are not the same."}); 921 } else { 922 return getDocument(doc.surveyRunId,[],'SurveyRun') 923 .handle({ 924 200: function(surveyRun) { 925 if ( surveyRun.respondentCanDeleteOwnResponse === true ) { 926 return deleteDocument(id,rev,doc); 927 } else { 928 return new HTTPResult(403,{error:"Not allowed to delete response."}); 929 } 930 } 931 }); 932 } 933 } 934 }) 935 .handle(res.send.bind(res)); 936 }); 937 938 // uuids 939 app.get('/api/uuids', 940 ensureAuthenticated, 941 ensureMIME(JSON_MIME), 942 function(req,res){ 943 var count = (req.query.count && parseInt(req.query.count,10)) || 1; 944 HTTPResult.fromResponsePromise(couch.uuids({query:{count:count}}).response, 945 handleUnknownError) 946 .handle({ 947 200: function(res) { 948 return res.uuids; 949 }, 950 default: handleUnknownResponse 951 }) 952 .handle(res.send.bind(res)); 953 }); 147 app.use('/api/open', 148 require('./api/open')(couch,schema).app); 954 149 955 150 app.get('/api/mode', 956 ensureMIME(JSON_MIME),151 apiUtil.ensureMIME(apiUtil.JSON_MIME), 957 152 function(req,res){ 958 153 res.send({mode:env.mode}); … … 961 156 962 157 return app; 963 }964 965 function writeObjectsToCSVStream(objects, stream, prelude) {966 var keys = _.chain(objects)967 .map(_.keys)968 .flatten()969 .uniq()970 .value();971 var idxs = {};972 _.forEach(keys, function(key,idx){973 idxs[key] = idx;974 });975 var writer = new CSV.CsvWriter(stream);976 if ( prelude ) {977 _.forEach(prelude, function(val,key){978 writer.writeRecord([key,val]);979 });980 }981 writer.writeRecord(keys);982 _.forEach(objects, function(obj){983 var row = [];984 _.forEach(obj, function(val,key){985 row[idxs[key]] = val;986 });987 writer.writeRecord(row);988 });989 }990 991 function randomToken() {992 var result = new HTTPResult();993 cryptoken(8)994 .then(function(token){995 result.set(201,token);996 }, function(ex){997 result.set(500,{error:"Cannot generate secrets.", innerError: ex});998 });999 return result;1000 158 } 1001 159 -
Dev/trunk/src/server/bin/heroku.js
r523 r525 6 6 console.log("Using CouchDB on",env.couchServerURL+env.dbName); 7 7 8 require('../app'). App(env)8 require('../app').app(env) 9 9 .then(function(app){ 10 10 app.listen(env.port, function() { -
Dev/trunk/src/server/bin/upgrade-db-1-to-2.js
r487 r525 1 1 var env = require('../env') 2 , HTTPResult = require('../util/http-result') 2 3 , upgradeCouch = require('../config/upgrade-couchdb') 3 4 , cryptoken = require('../util/crypto-token') 4 , Q = require('q')5 5 ; 6 6 upgradeCouch(env.couchServerURL,env.dbName,function(doc){ 7 if ( (doc.type === 'Response' || doc.type === 'SurveyRun') && !doc.secret ) { 8 return cryptoken(8).then(function(token){ 7 if ( (doc.type === 'Response' || doc.type === 'SurveyRun') && 8 !doc.secret ) { 9 return cryptoken() 10 .then(function(token){ 9 11 doc.secret = token; 10 12 return doc; 11 13 }); 12 14 } else { 13 return Q.reject();15 return new HTTPResult(-1); 14 16 } 15 17 }).then(function(res){ -
Dev/trunk/src/server/bin/upgrade-db-2-to-3.js
r506 r525 2 2 , upgradeCouch = require('../config/upgrade-couchdb') 3 3 , cryptoken = require('../util/crypto-token') 4 , Q = require('q')4 , HTTPResult = require('../util/http-result') 5 5 , _ = require('underscore') 6 6 ; … … 51 51 return doc; 52 52 } else { 53 return Q.reject();53 return new HTTPResult(-1); 54 54 } 55 55 }).then(function(res){ -
Dev/trunk/src/server/bin/upgrade-db-3-to-4.js
r508 r525 2 2 , upgradeCouch = require('../config/upgrade-couchdb') 3 3 , cryptoken = require('../util/crypto-token') 4 , Q = require('q')4 , HTTPResult = require('../util/http-result') 5 5 , _ = require('underscore') 6 6 ; … … 25 25 return doc; 26 26 } else { 27 return Q.reject();27 return HTTPResult.fail(); 28 28 } 29 29 }).then(function(res){ -
Dev/trunk/src/server/config/check-couchdb.js
r492 r525 1 var Q = require('q')2 , _ = require('underscore')1 var _ = require('underscore') 2 , HTTPResult = require('../util/http-result') 3 3 , validator = require('../util/validator') 4 , CouchDB = require('../util/couch').CouchDB 4 , CouchDB = require('../util/couch') 5 , objF = require('../util/object') 5 6 ; 6 7 … … 10 11 var server = new CouchDB(couchServerURL,dbName); 11 12 var designRe = /^_design\//; 12 13 var codes = {};14 function codeUnique(code) {15 if ( code in codes ) {16 return false;17 } else {18 codes[code] = true;19 return true;20 }21 }22 13 23 14 return server.get('/_all_docs') … … 32 23 .then(function(doc){ 33 24 var valid = validator(doc,schema); 34 if ( doc.type === "Question" && !codeUnique(doc.code) ) {35 valid.valid = false;36 valid.error = "Question code "+doc.code+" is not unique.";37 }38 25 result[doc._id] = valid; 39 26 return result; 40 27 }); 41 28 }); 42 }, Q.resolve({}))29 }, new HTTPResult(200,{})) 43 30 .value(); 44 }).all(); 31 }).then(function(result){ 32 return server.get('_design/questions/_view/all_variables') 33 .then(function(variables){ 34 var dups = objF.findDuplicates( 35 variables.rows, 36 function(row){ return row.key; }); 37 result._duplicateVariables = _.groupBy( 38 dups, 39 function(dup){ return dup.key; }); 40 return result; 41 }); 42 }); 45 43 }; -
Dev/trunk/src/server/config/config-couchdb.js
r519 r525 1 var Q = require('q')1 var HTTPResult = require('../util/http-result') 2 2 , _ = require('underscore') 3 , CouchDB = require('../util/couch') .CouchDB3 , CouchDB = require('../util/couch') 4 4 , util = require('util') 5 5 ; … … 62 62 } 63 63 }); 64 }, Q.resolve());64 }, new HTTPResult(200)); 65 65 }); 66 66 }; -
Dev/trunk/src/server/config/couchdb-design-docs.js
r523 r525 1 var fs = require('fs') 2 , path = require('path') 3 ; 4 5 function readModule(name) { 6 var filename; 7 if ( name.indexOf('.') === 0 ) { 8 filename = path.join(path.dirname(module.filename),name+'.js'); 9 } else { 10 var dirname = path.join(path.dirname(module.filename), 11 '../../node_modules',name); 12 filename = path.join(dirname,name+'.js'); 13 var pkgfile = path.join(dirname,'package.json'); 14 if ( fs.exists(pkgfile) ) { 15 var pkg = require(pkgfile); 16 if ( pkg.main ) { 17 filename = path.join(dirname,pkg.main); 18 } 19 } 20 } 21 var src = fs.readFileSync(filename, 'utf8'); 22 src = src.replace(/require\s*\(\s*(['\"])([^'"]*)(['\"])\s*\)/g, 23 'require($1views/lib/$2$3)'); 24 return src; 25 } 26 1 27 module.exports = { 2 28 … … 80 106 reduce: function(key, values, rereduce) { return sum(values); } 81 107 }, 108 all_variables: { 109 map: function(doc) { 110 if ( doc.type !== 'Question' ) { return; } 111 var _ = require('views/lib/underscore'); 112 var objF = require('views/lib/object'); 113 _.chain(objF.collectFields('subcode',doc.content)) 114 .map(function(subcode){ 115 return doc.code+subcode; 116 }) 117 .each(function(variable){ 118 emit(variable,doc._id); 119 }); 120 } 121 }, 122 published_variables: { 123 map: function(doc) { 124 if ( doc.type !== 'Question' || !doc.publicationDate ) { return; } 125 var _ = require('views/lib/underscore'); 126 var objF = require('views/lib/object'); 127 _.chain(objF.collectFields('subcode',doc.content)) 128 .map(function(subcode){ 129 return doc.code+subcode; 130 }) 131 .each(function(variable){ 132 emit(variable,doc._id); 133 }); 134 } 135 }, 82 136 all_by_code: { 83 137 map: function(doc){ … … 91 145 emit(doc.code,doc); 92 146 } 147 }, 148 lib: { 149 underscore: readModule('underscore'), 150 object: readModule('../util/object') 93 151 } 94 152 } -
Dev/trunk/src/server/config/couchdb-schema.json
r523 r525 11 11 "nonEmptyString": { "type": "string", "minLength": 1 }, 12 12 "codeString": { "type": "string", "pattern": "^[A-Za-z0-9]+$" }, 13 "subcodeString": { "type": "string", "pattern": "^[A-Za-z0-9]*$" }, 13 14 "schemaInfo": { 14 15 "type": "object", … … 148 149 "properties": { 149 150 "type": { "type": "string", "pattern": "^StringInput$" }, 150 "subcode": { "$ref": "#/definitions/ codeString" },151 "subcode": { "$ref": "#/definitions/subcodeString" }, 151 152 "text": { "$ref": "#/definitions/nonEmptyString" } 152 153 }, … … 159 160 "type": { "type": "string", "pattern": "^TextInput$" }, 160 161 "maxLength": { "type": "integer" }, 161 "subcode": { "$ref": "#/definitions/ codeString" },162 "subcode": { "$ref": "#/definitions/subcodeString" }, 162 163 "text": { "$ref": "#/definitions/nonEmptyString" } 163 164 }, … … 172 173 "max": { "type": "integer" }, 173 174 "places": { "type": "integer" }, 174 "subcode": { "$ref": "#/definitions/ codeString" },175 "subcode": { "$ref": "#/definitions/subcodeString" }, 175 176 "text": { "$ref": "#/definitions/nonEmptyString" } 176 177 }, … … 192 193 "minLabel": { "$ref": "#/definitions/nonEmptyString" }, 193 194 "maxLabel": { "$ref": "#/definitions/nonEmptyString" }, 194 "subcode": { "$ref": "#/definitions/ codeString" },195 "subcode": { "$ref": "#/definitions/subcodeString" }, 195 196 "text": { "$ref": "#/definitions/nonEmptyString" } 196 197 }, … … 218 219 "type": "object", 219 220 "properties": { 220 "subcode": { "$ref": "#/definitions/ codeString" }221 "subcode": { "$ref": "#/definitions/subcodeString" } 221 222 }, 222 223 "required": ["subcode"], 223 224 "additionalProperties": false 224 225 }, 225 "subcode": { "$ref": "#/definitions/ codeString" }226 "subcode": { "$ref": "#/definitions/subcodeString" } 226 227 }, 227 228 "required":["type","items","subcode"], … … 235 236 "type": "object", 236 237 "properties": { 237 "subcode": { "$ref": "#/definitions/ codeString" },238 "subcode": { "$ref": "#/definitions/subcodeString" }, 238 239 "text": { "$ref": "#/definitions/nonEmptyString" } 239 240 }, … … 244 245 "type": "object", 245 246 "properties": { 246 "subcode": { "$ref": "#/definitions/ codeString" }247 "subcode": { "$ref": "#/definitions/subcodeString" } 247 248 }, 248 249 "required": ["subcode"], -
Dev/trunk/src/server/config/upgrade-couchdb.js
r492 r525 1 var Q = require('q')1 var HTTPResult = require('../util/http-result') 2 2 , _ = require('underscore') 3 , CouchDB = require('../util/couch') .CouchDB3 , CouchDB = require('../util/couch') 4 4 ; 5 5 … … 19 19 return server.get(doc.id) 20 20 .then(function(doc){ 21 Q.when(upgrade(doc))21 HTTPResult.when(200,upgrade(doc)) 22 22 .then(function(newDoc){ 23 23 return server.put(doc._id,newDoc) … … 32 32 }); 33 33 }); 34 }, Q.resolve({}))34 }, new HTTPResult(200,{})) 35 35 .value(); 36 36 }).all(); -
Dev/trunk/src/server/util/couch.js
r522 r525 1 var CouchDB, _ref, 2 __hasProp = {}.hasOwnProperty; 3 4 var request = require('./q-request') 1 var request = require('./request') 5 2 , _ = require('underscore') 6 , Q = require('q');3 ; 7 4 8 5 function CouchDB(url, db) { … … 58 55 if (opts.headers) { _.extend(options.headers, opts.headers); } 59 56 } 60 var req = request(url, options); 61 var res = req.response 62 .then(function(res) { 63 return req.then(function(res) { 64 return JSON.parse(res); 65 }, function(err) { 66 return Q.reject(JSON.parse(err)); 57 return request(url, options) 58 .handle({ 59 '-1': _.identity, 60 default: function(status,result) { return JSON.parse(result); } 67 61 }); 68 }, function(err) {69 return Q.reject(err);70 });71 res.response = req.response72 .then(function(res) {73 return {74 statusCode: res.statusCode,75 headers: res.headers,76 body: JSON.parse(res.body)77 };78 }, function(err) {79 return Q.reject({error:err.message});80 });81 return res;82 62 } 83 63 … … 108 88 } 109 89 110 exports.CouchDB= CouchDB;90 module.exports = CouchDB; -
Dev/trunk/src/server/util/crypto-token.js
r487 r525 1 var Q = require('q')1 var HTTPResult = require('./http-result') 2 2 , crypto = require('crypto'); 3 3 4 4 module.exports = function(bytes) { 5 var q = Q.defer(); 6 crypto.randomBytes(bytes || 8, function(ex, buf) { 7 if ( ex ) { 8 q.reject(ex); 9 } else { 10 q.resolve(buf.toString('hex')); 11 } 12 }); 13 return q.promise; 5 bytes =bytes || 8; 6 var result = new HTTPResult(); 7 crypto.randomBytes(bytes, result.asCallback(201)); 8 return result 9 .then(function(buf){ 10 return buf.toString('hex'); 11 }); 14 12 }; -
Dev/trunk/src/server/util/http-result.js
r487 r525 1 // <init> :: status? -> result? -> HTTPResult 1 var _ = require('underscore') 2 ; 3 4 var when; 5 6 // type Callback = status:Number -> result:Any -> Either HTTPResult Any 7 // type Handler = { f:Callback, next:HTTPResult } 8 9 // <init> :: status:Number? -> result:Any? -> HTTPResult 10 var counter = 0; 2 11 function HTTPResult(status,result) { 12 this.id = counter++; 3 13 this.status = 0; 4 14 this.result = null; … … 9 19 } 10 20 11 HTTPResult.fromResponsePromise = function(promise,errorHandler) {12 var result = new HTTPResult();13 promise.then(function(response){14 result.set(response.statusCode,response.body);15 }, errorHandler && function(error){16 var ret = errorHandler(error);17 if ( ret.constructor === HTTPResult ) {18 ret.handle(result.set.bind(result));19 } else {20 result.set(500,ret);21 }22 });23 return result;24 };25 26 21 // isSet :: Bool 27 22 HTTPResult.prototype.isSet = function() { … … 29 24 }; 30 25 31 // set :: status -> result? -> ()26 // set :: status:Number -> result:Any? -> () 32 27 HTTPResult.prototype.set = function(status,result) { 33 if ( this.isSet() ) { throw new Error("Result was already set."); } 34 if ( typeof status !== 'number' ) { throw new Error("Status must be a number."); } 28 if ( this.isSet() ) { 29 throw new Error("Result was already set."); 30 } 31 if ( !_.isNumber(status) ) { 32 throw new Error("Status must be a number."); 33 } 35 34 this.status = status; 36 35 this.result = result; 37 for ( var i = 0; i < this._handlers.length; i++ ) { 38 this._fire(this._handlers[i]); 36 _.each(this._handlers,this._fire.bind(this)); 37 }; 38 39 // _fire :: Handler -> () 40 HTTPResult.prototype._fire = function(handler) { 41 var ret = handler.f(this.status,this.result); 42 if ( ret instanceof HTTPResult ) { 43 ret.handle(handler.next.set.bind(handler.next)); 44 } else { 45 handler.next.set(this.status,ret); 39 46 } 40 47 }; 41 48 42 // _fire :: (status -> result -> Either HTTPResult Any) -> () 43 HTTPResult.prototype._fire = function(f) { 44 var ret = f(this.status,this.result); 45 if ( ret.constructor === HTTPResult ) { 46 ret.handle(f._next.set.bind(f._next)); 47 } else { 48 f._next.set(this.status,ret); 49 } 50 }; 51 52 /* handle :: Either (status -> result -> Either HTTPResult Any) 53 * {number: (result -> Either HTTPResult Any), 54 * 'default': (status -> result -> Either HTTPResult Any)} 49 /* handle :: Either Callback 50 * {status:Number*: (result -> Either HTTPResult Any), 51 * 'default'?: Callback} 55 52 * -> HTTPResult 56 53 */ 57 54 HTTPResult.prototype.handle = function(fOrObj) { 58 var f = typeof fOrObj === 'function'?55 var f = _.isFunction(fOrObj) ? 59 56 fOrObj : 60 57 function(status,result) { … … 67 64 } 68 65 }; 69 f._next = new HTTPResult(); 66 var next = new HTTPResult(); 67 var handler = { 68 f: f, 69 next: next 70 }; 70 71 if ( this.isSet() ) { 71 this._fire( f);72 this._fire(handler); 72 73 } else { 73 this._handlers.push( f);74 this._handlers.push(handler); 74 75 } 75 return f._next; 76 return next; 77 }; 78 79 // then :: onSuccess:(result:Any -> status:Number)? -> 80 // onError:(result:Any -> status:Number)? -> HTTPResult 81 HTTPResult.prototype.then = function(onSuccess,onError) { 82 var f = function(status,result) { 83 if ( status >= 200 && status < 300 && onSuccess ) { 84 if ( onSuccess ) { 85 result = onSuccess(result,status); 86 } 87 } else { 88 if ( onError ) { 89 result = onError(result,status); 90 } 91 } 92 return result; 93 }; 94 return this.handle(f); 95 }; 96 97 HTTPResult.prototype.asCallback = function(status) { 98 status = status || 200; 99 return function(ex,result) { 100 if ( ex ) { 101 this.set(-1,ex); 102 } else { 103 this.set(status,result); 104 } 105 }.bind(this); 106 }; 107 108 // when :: Either HTTPResult Any -> HTTPResult 109 when = HTTPResult.when = function(status,valueOrResult) { 110 status = status || 200; 111 if ( valueOrResult instanceof HTTPResult ) { 112 return valueOrResult; 113 } else { 114 return new HTTPResult(status,valueOrResult); 115 } 116 }; 117 118 // ok :: result:Any? -> HTTPResult 119 HTTPResult.ok = function(result) { 120 return new HTTPResult(200,result); 121 }; 122 123 // fail :: error:Any? -> HTTPResult 124 HTTPResult.fail = function(error) { 125 return new HTTPResult(-1,error); 76 126 }; 77 127
Note: See TracChangeset
for help on using the changeset viewer.