Changeset 487 for Dev


Ignore:
Timestamp:
03/05/14 22:44:48 (11 years ago)
Author:
hendrikvanantwerpen
Message:

Completed migration to API, without CouchDB proxy.

Move to API is now completed. The full API is password protected, a very
limited API is exposed for respondents, which works with secrets that
are passed in URLs.

Serverside the HTTPResult class was introduced, which is similar to
Promises, but specifically for HTTP. It carries a status code and
response and makes it easier to extract parts of async handling in
separate functions.

Fixed a bug in our schema (it seems optional attributes don't exist but
a required list does). Verification of our schema by grunt-tv4 didn't
work yet. Our schema is organized the wrong way (this is fixable),
but the json-schema schema has problems with simple types and $refs.

Location:
Dev/trunk
Files:
471 added
3 deleted
66 edited
5 moved

Legend:

Unmodified
Added
Removed
  • Dev/trunk/Gruntfile.js

    r474 r487  
    1313                                  ,'htmlhint:lint'
    1414                                  ,'coffeelint:lint'
     15                                  //,'tv4:lint'
    1516                                  ,'less:compile'
    1617                                  ,'usebanner:generated-css'
     
    2021                                //,'amd-check' // too smart about plugins, r.js can't find them
    2122                                  ]);
    22     grunt.registerTask('build', ['clean:build'
     23    grunt.registerTask('build', ['compile'
     24                                ,'clean:build'
    2325                                ,'copy:build'
    2426                                ,'dojo:build'
     
    107109            }
    108110        },
     111        tv4: {
     112            lint: {
     113                options: {
     114                    root: grunt.file.readJSON('json-schema-draft-04.json'),
     115                    multi: true
     116                },
     117                src: [srcDir+'server/config/couchdb-schema.json']
     118            }
     119        },
    109120        usebanner: {
    110121            'generated-css': { options: { position: 'top',
     
    129140    grunt.loadNpmTasks('grunt-dojo');
    130141    grunt.loadNpmTasks('grunt-htmlhint');
     142    grunt.loadNpmTasks('grunt-tv4');
    131143    grunt.loadTasks('./grunt-tasks');
    132144
  • Dev/trunk/package.json

    r479 r487  
    1818    "grunt-amd-check": "~0.5.2",
    1919    "mocha": "~1.14.0",
    20     "should": "~2.1.0"
     20    "should": "~2.1.0",
     21    "grunt-tv4": "^0.4.0"
    2122  },
    2223  "dependencies": {}
  • Dev/trunk/src/.jshintrc-generated

    r479 r487  
    1717    "loopfunc": true,
    1818    "shadow": true,
     19    "eqnull": true,
    1920
    2021    "devel": true,
  • Dev/trunk/src/client/qed-client/app/Router.js

    r457 r487  
    7878                        route.callback(params);
    7979                    } catch(err) {
    80                         console.error("Page change failed with",err.toString());
     80                        console.error("Page change failed with",err,err.toString());
    8181                    }
    8282                    return;
  • Dev/trunk/src/client/qed-client/model/classes

    • Property svn:ignore set to

      ### begin grunt-svn-ignore managed ignores
      ### edits will be overwritten when grunt svn-ignore is run
      Question.js
      Response.js
      Survey.js
      SurveyRun.js
      ### end grunt-svn-ignore managed ignores
  • Dev/trunk/src/client/qed-client/model/classes/questions.js

    r486 r487  
    1 define(function(){
    2     return {
    3         create: function(){
    4             return { type:'Question' };
     1define([
     2    "./_Class",
     3    "dojo/_base/declare",
     4    "dojo/date/stamp"
     5], function(_Class, declare, stamp) {
     6
     7    var Questions = declare([_Class],{
     8        _collection: 'questions',
     9        _type: 'Question',
     10        create: function() {
     11            var obj = {
     12                type: this._type,
     13                categories: [],
     14                code: "",
     15                content: [],
     16                title: ""
     17            };
     18            return obj;
    519        },
    6         DisplayTitle: {
    7             get: function(q) {
    8                 return q.title || '';
     20        _deserialize: function(obj) {
     21            if (obj.publicationDate) {
     22                obj.publicationDate = stamp.fromISOString(obj.publicationDate);
    923            }
    1024        },
    11         Content: {
    12             get: function(q) {
    13                 return q.content || [];
    14             },
    15             set: function(q,content) {
    16                 q.content = content;
     25        _serialize: function(obj) {
     26            if (obj.publicationDate) {
     27                obj.publicationDate = stamp.toISOString(obj.publicationDate);
    1728            }
    1829        }
    19     };
     30    });
     31
     32    return new Questions();
     33
    2034});
  • Dev/trunk/src/client/qed-client/model/classes/responses.js

    r486 r487  
    1 define([],function(){
    2     var SurveyRun = {
    3         create: function(){
    4             return { type:'Response' };
     1define([
     2    "./_Class",
     3    "./surveyRuns",
     4    "dojo/Deferred",
     5    "dojo/_base/declare",
     6    "dojo/_base/json",
     7    "dojo/_base/lang",
     8    "dojo/_base/xhr",
     9    "dojo/date/stamp"
     10], function(_Class, surveyRuns, Deferred, declare, json, lang, xhr, stamp) {
     11
     12    var Responses = declare([_Class],{
     13        _collection: 'responses',
     14        _type: 'Response',
     15        create: function() {
     16            var obj = {
     17                type: this._type,
     18                answers: {},
     19                surveyRunId: null
     20            };
     21            return obj;
    522        },
    6         SurveyRun: {
    7             get: function(r) {
    8                 return r.surveyRunId || null;
    9             },
    10             set: function(r,sr) {
    11                 r.surveyRunId = sr;
    12                 return r;
     23        _deserialize: function(obj) {
     24            if (obj._surveyRun) {
     25                obj._surveyRun = surveyRuns._doDeserialize(obj._surveyRun);
    1326            }
     27            if (obj.publicationDate) {
     28                obj.publicationDate = stamp.fromISOString(obj.publicationDate);
     29            }
     30        },
     31        _serialize: function(obj) {
     32            if (obj._surveyRun) {
     33                obj._surveyRun = surveyRuns._doSerialize(obj._surveyRun);
     34            }
     35            if (obj.publicationDate) {
     36                obj.publicationDate = stamp.toISOString(obj.publicationDate);
     37            }
     38        },
     39        getWithSecret: function(id,secret) {
     40            var query = xhr.objectToQuery({secret:secret});
     41            return xhr('GET',{
     42                url: '/api/open/responses/' + id + '?' + query,
     43                handleAs: 'json',
     44                contentType: false
     45            }).then(lang.hitch(this,'_doDeserialize'),function(err){
     46                return new Deferred().reject(json.fromJson(err.responseText));
     47            });
     48        },
     49        postWithSecret: function(response,secret) {
     50            var query = xhr.objectToQuery({secret:secret});
     51            var body = json.toJson(this._doSerialize(response));
     52            return xhr('POST',{
     53                url: '/api/open/responses?' + query,
     54                handleAs: 'json',
     55                contentType: 'application/json',
     56                rawBody: body
     57            }).then(lang.hitch(this,'_doDeserialize'),function(err){
     58                return new Deferred().reject(json.fromJson(err.responseText));
     59            });
     60        },
     61        putWithSecret: function(response,secret) {
     62            var query = xhr.objectToQuery({secret:secret});
     63            var body = json.toJson(this._doSerialize(response));
     64            return xhr('PUT',{
     65                url: '/api/open/responses/' + this.getId(response) + '?' + query,
     66                handleAs: 'json',
     67                contentType: 'application/json',
     68                rawBody: body
     69            }).then(lang.hitch(this,'_doDeserialize'),function(err){
     70                return new Deferred().reject(json.fromJson(err.responseText));
     71            });
     72        },
     73        removeWithSecret: function(response,secret) {
     74            var query = xhr.objectToQuery({secret:secret});
     75            var rev = this.getRev(response);
     76            var body = json.toJson(this._doSerialize(response));
     77            var headers = {};
     78            if ( rev ) {
     79                headers['If-Match'] = '"'+rev+'"';
     80            }
     81            return xhr('DELETE',{
     82                url: '/api/open/responses/' + this.getId(response) + '?' + query,
     83                headers: headers,
     84                handleAs: 'json',
     85                contentType: 'application/json',
     86                rawBody: body
     87            });
    1488        }
    15     };
    16     return SurveyRun;
     89    });
     90
     91    return new Responses();
     92
    1793});
  • Dev/trunk/src/client/qed-client/model/classes/surveyRuns.js

    r486 r487  
    1 define(['dojo/_base/lang','dojo/date/locale','dojo/date/stamp'],function(lang,locale,stamp){
    2     var SurveyRun = {
    3         create: function(){
    4             return { type:'SurveyRun' };
     1define([
     2    "./_Class",
     3    "./surveys",
     4    "dojo/_base/declare",
     5    "dojo/date/stamp"
     6], function(_Class, surveys, declare, stamp) {
     7
     8    var SurveyRuns = declare([_Class],{
     9        _collection: 'surveyRuns',
     10        _type: 'SurveyRun',
     11        create: function() {
     12            var obj = {
     13                type: this._type,
     14                description: "",
     15                mode: "open",
     16                survey: null,
     17                title: ""
     18            };
     19            return obj;
    520        },
    6         StartDate: {
    7             get: function(sr) {
    8                 var d;
    9                 if ( sr.startDate ) {
    10                     d = lang.isString(sr.startDate) ? stamp.fromISOString(sr.startDate) : sr.startDate;
    11                 }
    12                 return d;
    13             },
    14             set: function(sr,d) {
    15                 if ( d ) {
    16                     sr.startDate = lang.isString(d) ? stamp.toISOString(d) : d;
    17                 }
     21        _deserialize: function(obj) {
     22            if (obj.endDate) {
     23                obj.endDate = stamp.fromISOString(obj.endDate);
     24            }
     25            if (obj.startDate) {
     26                obj.startDate = stamp.fromISOString(obj.startDate);
     27            }
     28            if (obj.survey) {
     29                obj.survey = surveys._doDeserialize(obj.survey);
    1830            }
    1931        },
    20         EndDate: {
    21             get: function(sr) {
    22                 var d;
    23                 if ( sr.endDate ) {
    24                     d = lang.isString(sr.endDate) ? stamp.fromISOString(sr.endDate) : sr.endDate;
    25                 }
    26                 return d;
    27             },
    28             set: function(sr,d) {
    29                 if ( d ) {
    30                     sr.endDate = lang.isString(d) ? stamp.toISOString(d) : d;
    31                 }
     32        _serialize: function(obj) {
     33            if (obj.endDate) {
     34                obj.endDate = stamp.toISOString(obj.endDate);
    3235            }
    33         },
    34         DisplayTitle: {
    35             get: function(sr) {
    36                 var t = "Run of '"+sr.survey.title+"'";
    37                 if ( sr.startDate ) {
    38                     t += " from "+locale.format(SurveyRun.StartDate.get(sr));
    39                 }
    40                 if ( sr.endDate ) {
    41                     t += " until "+locale.format(SurveyRun.EndDate.get(sr));
    42                 }
    43                 return t;
     36            if (obj.startDate) {
     37                obj.startDate = stamp.toISOString(obj.startDate);
    4438            }
    45         },
    46         Survey: {
    47             get: function(sr) {
    48                 return sr.survey || null;
    49             },
    50             set: function(sr,s) {
    51                 sr.survey = s;
    52                 return sr;
     39            if (obj.survey) {
     40                obj.survey = surveys._doSerialize(obj.survey);
    5341            }
    5442        }
    55     };
    56     return SurveyRun;
     43    });
     44
     45    return new SurveyRuns();
     46
    5747});
  • Dev/trunk/src/client/qed-client/model/classes/surveys.js

    r486 r487  
    1 define(function(){
    2     return {
    3         create: function(){
    4             return { type:'Survey' };
     1define([
     2    "./_Class",
     3    "dojo/_base/declare",
     4    "dojo/date/stamp",
     5    "dojo/store/JsonRest"
     6], function(_Class, declare, stamp, JsonRest) {
     7
     8    var Surveys = declare([_Class],{
     9        _collection: 'surveys',
     10        _type: 'Survey',
     11        create: function() {
     12            var obj = {
     13                type: this._type,
     14                questions: [],
     15                title: ""
     16            };
     17            return obj;
    518        },
    6         DisplayTitle: {
    7             get: function(s) {
    8                 return s.title || '';
     19        _deserialize: function(obj) {
     20            if (obj.publicationDate) {
     21                obj.publicationDate = stamp.fromISOString(obj.publicationDate);
    922            }
    1023        },
    11         Questions: {
    12             get: function(s) {
    13                 return s.questions || [];
    14             },
    15             set: function(s,questions) {
    16                 s.questions = questions;
     24        _serialize: function(obj) {
     25            if (obj.publicationDate) {
     26                obj.publicationDate = stamp.toISOString(obj.publicationDate);
    1727            }
    1828        }
    19     };
     29    });
     30
     31    return new Surveys();
     32
    2033});
  • Dev/trunk/src/client/qed-client/model/widgets/QuestionEditorToolkit.js

    r443 r487  
    11define([
    2     "../../store",
     2    "../classes/categories",
     3    "../classes/topics",
    34    "./CategoryListView",
    45    "dijit/_Container",
     
    1516    "require",
    1617    "dojo/text!./templates/QuestionEditorToolkit.html"
    17 ], function(store, CategoryListView, _Container, _TemplatedMixin, _WidgetBase, _WidgetsInTemplateMixin, Button, ComboBox, declare, lang, Source, domConstruct, Memory, require, template) {
     18], function(categories, topics, CategoryListView, _Container, _TemplatedMixin, _WidgetBase, _WidgetsInTemplateMixin, Button, ComboBox, declare, lang, Source, domConstruct, Memory, require, template) {
    1819    return declare([_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin, _Container], {
    1920
     
    107108            this.inherited(arguments);
    108109
    109             store.query("_design/questions/_view/all", {reduce:true, group:false, group_level:1})
    110             .forPairs(lang.hitch(this, function(value, key) {
    111                 this._categoryStore.put({ id: key[0] });
     110            categories.query().forEach(lang.hitch(this,function(cat){
     111                this._categoryStore.put({ id: cat });
    112112            }));
    113113
    114             store.query("_design/questions/_view/all_topics", {reduce:true, group:true})
    115             .forPairs(lang.hitch(this, function(value, key) {
    116                 this._topicStore.put({ id: key });
     114            topics.query().forEach(lang.hitch(this,function(topic){
     115                this._categoryStore.put({ id: topic });
    117116            }));
    118117        },
  • Dev/trunk/src/client/qed-client/model/widgets/SurveyRenderWidget.js

    r461 r487  
    11define([
    22    "../../widgets/_ComplexValueWidget",
    3     "../classes/Survey",
    43    "./questions/Factory",
    54    "dojo/_base/array",
     
    76    "dojo/dom-construct",
    87    "dojo/text!./templates/SurveyRenderWidget.html"
    9 ], function(_ComplexValueWidget, Survey, QuestionWidgetFactory, array, declare, domConstruct, template) {
     8], function(_ComplexValueWidget, QuestionWidgetFactory, array, declare, domConstruct, template) {
    109    return declare([_ComplexValueWidget],{
    1110        templateString: template,
     
    2221            this.survey = survey;
    2322            var f = new QuestionWidgetFactory();
    24             array.forEach(Survey.Questions.get(this.survey),function(question,question_index){
     23            array.forEach(this.survey.questions,function(question,question_index){
    2524                array.forEach(question.content || [], function(item,item_index){
    2625                    // The dot causes values to be grouped in an object!
  • Dev/trunk/src/client/qed-client/model/widgets/SurveySummary.js

    r457 r487  
    11define([
    2     "../../store",
    3     "../classes/Survey",
    42    "dijit/_TemplatedMixin",
    53    "dijit/_WidgetBase",
     
    75    "dojo/dom-attr",
    86    "dojo/text!./templates/SurveySummary.html"
    9 ], function(store, Survey, _TemplatedMixin, _WidgetBase, declare, domAttr, template) {
     7], function(_TemplatedMixin, _WidgetBase, declare, domAttr, template) {
    108    return declare([_WidgetBase,_TemplatedMixin],{
    119        templateString: template,
     
    1816        },
    1917        _setValueAttr: function(survey) {
    20             this.titleNode.innerHTML = Survey.DisplayTitle.get(survey);
    21             var id = store.getIdentity(survey);
    22             domAttr.set(this.titleNode, "href", id && ("#!/survey/"+id));
     18            this.titleNode.innerHTML = survey.title || "";
     19            domAttr.set(this.titleNode, "href", survey._id && ("#!/survey/"+survey._id));
    2320            this.descriptionNode.innerHTML = survey.description;
    24             this.questionsNode.innerHTML = (survey.questions || []).length;
     21            this.questionsNode.innerHTML = survey.questions.length;
    2522        }
    2623    });
  • Dev/trunk/src/client/qed-client/model/widgets/TabbedQuestionBrowser.js

    r477 r487  
    11define([
    2     'dojo/_base/declare',
    3     'dojo/_base/lang',
    4     'dojo/_base/window',
    5     'dijit/layout/ContentPane',
    6     'dijit/layout/TabContainer',
    7     'dojox/widget/Standby',
    8     '../../store',
    9     '../../widgets/Selector'
    10 ],function(declare,lang,win,ContentPane,TabContainer,Standby,store,Selector){
     2    "../../widgets/Selector",
     3    "../classes/categories",
     4    "../classes/questions",
     5    "../classes/topics",
     6    "dijit/layout/ContentPane",
     7    "dijit/layout/TabContainer",
     8    "dojo/_base/declare",
     9    "dojo/_base/lang",
     10    "dojo/_base/window",
     11    "dojox/widget/Standby"
     12], function(Selector, categories, questions, topics, ContentPane, TabContainer, declare, lang, win, Standby) {
    1113    return declare([TabContainer],{
    1214        tabPosition: 'left-h',
     
    3840                this._fillCategoryTab(newTab.__category);
    3941            }));
    40             store.query(this._query, {reduce:true,group:true,group_level:1})
    41             .forPairs(lang.hitch(this,function(value,key){
    42                 this._createCategoryTab(key[0],value);
     42            categories.query()
     43            .forEach(lang.hitch(this,function(cat){
     44                this._createCategoryTab(cat.name,cat.count);
    4345            }));
    4446        },
     
    6163                this._busy();
    6264                categoryMap._filled = true;
    63                 store.query(this._query, {reduce:true,group:true,group_level:2,startkey:[category],endkey:[category,{}]})
    64                 .forPairs(lang.hitch(this,function(value,key){
    65                     this._createTopicSelector(key[1],category,value);
     65                topics.query({category:category})
     66                .forEach(lang.hitch(this,function(topic){
     67                    this._createTopicSelector(topic.name,category,topic.count);
    6668                })).then(lang.hitch(this,function(){
    6769                    this._done();
     
    101103                topicMap._filled = true;
    102104                this._busy();
    103                 store.query(this._query, {
    104                     reduce:false,
    105                     include_docs:true,
    106                     key:[category,topic]
    107                 }).forEach(lang.hitch(this,function(value){
     105                questions.query({category:category,topic:topic})
     106                .forEach(lang.hitch(this,function(value){
    108107                    topicMap._widget.addItem(value);
    109108                })).then(lang.hitch(this,function(){
  • Dev/trunk/src/client/qed-client/pages/previewSurvey.js

    r443 r487  
    11define([
    22    "../app/Page",
    3     "../store",
     3    "../model/classes/surveys",
    44    "dojo/_base/array",
    55    "dojo/_base/declare",
     
    88    "require",
    99    "dojo/text!./templates/previewSurvey.html"
    10 ], function(Page, store, array, declare, lang, when, require, template) {
     10], function(Page, surveys, array, declare, lang, when, require, template) {
    1111    return declare([Page],{
    1212        contextRequire: require,
     
    1616            this.inherited(arguments);
    1717            if ( this.surveyId ) {
    18                 when(store.get(this.surveyId))
     18                when(surveys.load(this.surveyId))
    1919                .then(lang.hitch(this,function(survey){
    20                     this.titleNode.innerHTML = survey.title;
     20                    this.titleNode.innerHTML = survey.title || "";
    2121                    this.surveyWidget.set('survey',survey);
    2222                }));
  • Dev/trunk/src/client/qed-client/pages/question.js

    r443 r487  
    33    "../app/Page",
    44    "../app/Router",
    5     "../model/classes/Question",
     5    "../model/classes/questions",
    66    "../model/widgets/QuestionEditorPreview",
    77    "../model/widgets/QuestionEditorToolkit",
    8     "../store",
    98    "../widgets/_ComplexValueMixin",
    109    "dojo/_base/declare",
     
    1312    "dojo/when",
    1413    "dojo/text!./templates/question.html"
    15 ], function(Content, Page, Router, Question, QuestionEditorPreview, QuestionEditorToolkit, store, _ComplexValueMixin, declare, event, lang, when, template) {
     14], function(Content, Page, Router, questions, QuestionEditorPreview, QuestionEditorToolkit, _ComplexValueMixin, declare, event, lang, when, template) {
    1615    return declare([Page,_ComplexValueMixin], {
    1716        templateString: template,
     
    4342            }
    4443            if (this.questionId === "new") {
    45                 this.set('value', Question.create());
     44                this.set('value', questions.create());
    4645            } else {
    47                 when(store.get(this.questionId))
     46                when(questions.load(this.questionId))
    4847                .then(lang.hitch(this, function(value) {
    4948                    this.set('value', value);
     
    5453            this.value = value;
    5554            this.inherited(arguments);
    56             this.titleNode.innerHTML = Question.DisplayTitle.get(value);
     55            this.titleNode.innerHTML = value.title || "";
    5756        },
    5857        _getValueAttr: function() {
     
    6362        _onSave: function(evt) {
    6463            if ( this.validate() ) {
    65                 var value = this.get('value');
    66                 store.put(value)
     64                questions.save(this.get('value'))
    6765                .then(function() {
    6866                    Router.go('/questions');
  • Dev/trunk/src/client/qed-client/pages/questions.js

    r443 r487  
    11define([
    2     'dojo/_base/declare',
    3     'dojo/_base/Deferred',
    4     'dojo/_base/event',
    5     'dojo/_base/lang',
    6     '../store',
    7     '../app/Content',
    8     '../app/Router',
    9     '../app/Page',
    10     '../model/widgets/TabbedQuestionBrowser',
    11     'dojo/text!./templates/questions.html'
    12 ],function(declare,Deferred,event,lang,store,Content,Router,Page,TabbedQuestionBrowser,template) {
     2    "../app/Content",
     3    "../app/Page",
     4    "../app/Router",
     5    "../model/classes/questions",
     6    "../model/widgets/TabbedQuestionBrowser",
     7    "dojo/_base/Deferred",
     8    "dojo/_base/declare",
     9    "dojo/_base/event",
     10    "dojo/_base/lang",
     11    "dojo/text!./templates/questions.html"
     12], function(Content, Page, Router, questions, TabbedQuestionBrowser, Deferred, declare, event, lang, template) {
    1313    return declare([Page],{
    1414        templateString: template,
     
    4444        },
    4545        onDeleteQuestion: function(question) {
    46             store.remove(store.getIdentity(question),store.getRevision(question))
     46            questions.remove(question)
    4747            .then(function(){
    4848                Content.notify("Question deleted.");
     
    5555        },
    5656        onPublishQuestion: function(question) {
    57             question.publicationDate = store.timestamp();
    58             store.put(question)
     57            question.publicationDate = new Date();
     58            questions.save(question)
    5959            .then(function(){
    6060                Content.notify("Question published.");
  • Dev/trunk/src/client/qed-client/pages/response.js

    r478 r487  
    33    "../app/Page",
    44    "../lib/async",
    5     "../model/classes/Response",
    6     "../model/classes/Survey",
    7     "../model/classes/SurveyRun",
    8     "../store",
     5    "../model/classes/responses",
    96    "dojo/_base/declare",
    107    "dojo/_base/event",
     
    1613    "require",
    1714    "dojo/text!./templates/response.html"
    18 ], function(Content, Page, async, Response, Survey, SurveyRun, store, declare, event, json, lang, all, request, when, require, template) {
     15], function(Content, Page, async, responses, declare, event, json, lang, all, request, when, require, template) {
    1916    return declare([Page],{
    2017        contextRequire: require,
     
    2825            this.inherited(arguments);
    2926            this._disableSubmit();
    30             var surveyRunId = this.surveyRunId;
    31             var responseId = this.options && this.options.id;
    32             if ( surveyRunId && responseId ) {
    33                 this._loadSurveyAndResponse(surveyRunId,responseId)
    34                 .then(lang.hitch(this, function() {
    35                     if ( this.response.publicationDate ) {
    36                         this._showInfo("<div>You already submitted your survey and cannot change it anymore. You can still view your answers here.</div>");
    37                         this._disableSubmit();
    38                     } else {
    39                         this._enableSubmit();
    40                     }
    41                 }), lang.hitch(this,function() {
    42                     this._showInfo("<div>The url seems to be incorrect, no survey found.</div>");
    43                 }));
     27            if ( !this.response ) {
     28                this._showInfo("<div>The url seems to be incorrect, no response found.</div>");
    4429            } else {
    45                 throw new Error("No valid uid or survey passed!");
     30                this.titleNode.innerHTML = this.response._surveyRun.survey.title || "";
     31                this.surveyWidget.set('survey', this.response._surveyRun.survey);
     32                this.surveyWidget.set('value', this.response.answers || {});
     33                if ( this.response.publicationDate ) {
     34                    this._showInfo("<div>You already submitted your survey and cannot change it anymore. You can still view your answers here.</div>");
     35                    this._disableSubmit();
     36                } else {
     37                    this._enableSubmit();
     38                }
    4639            }
    47         },
    48         _loadSurveyAndResponse: function(surveyRunId,responseId){
    49             return all([request.get('/api/surveyRuns/'+surveyRunId,{handleAs:'json'}),
    50                         request.get('/api/responses/'+responseId,{handleAs:'json'})])
    51             .then(lang.hitch(this,function(surveyAndResponse){
    52                 var surveyRun = surveyAndResponse[0];
    53                 this.response = surveyAndResponse[1];
    54                 if ( this.response.surveyRunId !== surveyRunId ) {
    55                     throw "Survey does not match the response...";
    56                 }
    57                 this.titleNode.innerHTML = Survey.DisplayTitle.get(surveyRun.survey);
    58                 this.surveyWidget.set('survey', surveyRun.survey);
    59                 this.surveyWidget.set('value', this.response.answers || {});
    60             }));
    6140        },
    6241        _enableSubmit: function() {
     
    7857            var answers = this.surveyWidget.get('value');
    7958            this.response.answers = answers;
    80             return request.put('/api/responses/'+store.getIdentity(this.response),{
    81                 handleAs:'json',
    82                 data:json.toJson(this.response),
    83                 headers:{"Content-Type": "application/json"}
    84             }).then(lang.hitch(this,function(res){
    85                 this.response._rev = res.rev;
     59            return responses.putWithSecret(this.response,this.response.secret)
     60            .then(lang.hitch(this,function(response){
     61                this.response = response;
    8662                Content.notify("Your response is saved.");
    8763            }), function(err){
     
    9066        },
    9167        _onSubmit: function(e) {
    92             this.response.publicationDate = store.timestamp();
     68            this.response.publicationDate = new Date();
    9369            this._getAnswersAndSaveResponse()
    9470            .then(lang.hitch(this,function(){
     
    11086            this._disableSubmit();
    11187            this.surveyWidget.destroy();
    112             request('/api/responses/'+store.getIdentity(this.response)+'?rev='+store.getRevision(this.response),{
    113                 method: 'DELETE',
    114                 handleAs:'json',
    115                 data:json.toJson(this.response),
    116                 headers:{"Content-Type": "application/json"}
    117             }).then(lang.hitch(this,function(res){
     88            responses.removeWithSecret(this.response,this.response.secret)
     89            .then(lang.hitch(this,function(res){
    11890                this._showInfo("<div>Your response has been deleted, no answers have been saved.</div>");
    11991                Content.notify("Your response is deleted.");
  • Dev/trunk/src/client/qed-client/pages/session.js

    r443 r487  
    11define([
    2     'dojo/_base/array',
     2    /*'dojo/_base/array',
    33    'dojo/_base/declare',
    44    'dojo/_base/event',
     
    1212    '../model/classes/SessionTemplate',
    1313    '../model/widgets/AccountListView',
    14     'dojo/text!./templates/session.html'
     14    'dojo/text!./templates/session.html'*/
    1515],function(array,declare,event,lang,when,search,store,Page,Router,ThresholdFilteringSelect,SessionTemplate,AccountListView,template){
    16     return declare([Page],{
     16    /*return declare([Page],{
    1717        templateString: template,
    1818        session: null,
     
    9292
    9393
    94     });
     94    });*/
    9595});
    9696
  • Dev/trunk/src/client/qed-client/pages/sessions.js

    r443 r487  
    11define([
    2     'dojo/_base/declare',
     2    /*'dojo/_base/declare',
    33    'dojo/_base/lang',
    44    'dojo/date/stamp',
     
    77    '../app/Page',
    88    '../widgets/ObjectBox',
    9     'dojo/text!./templates/sessions.html'
     9    'dojo/text!./templates/sessions.html'*/
    1010],function(declare,lang,dateStamp,store,Router,Page,ObjectBox,template){
    11     return declare([Page],{
     11    /*return declare([Page],{
    1212        templateString: template,
    1313        templateActions: null,
     
    1818            this.templateActions = {
    1919                "Edit": function(obj){
    20                     Router.go('/session/'+store.getIdentity(obj));
     20                    Router.go('/session/'+obj.get('id'));
    2121                },
    2222                "Delete": lang.hitch(this,function(obj){
    23                     store.remove(store.getIdentity(obj),store.getRevision(obj))
     23                    obj.remove()
    2424                    .then(lang.hitch(this,function(){
    2525                        this._refresh();
     
    3030            this.sessionActions = {
    3131                "Facilitate": function(obj){
    32                     Router.go('run',{uid:store.getIdentity(obj)});
     32                    Router.go('run',{uid: obj.get('id')});
    3333                },
    3434                "Delete": lang.hitch(this,function(obj){
    35                     store.remove(store.getIdentity(obj),store.getRevision(obj))
     35                    obj.remove()
    3636                    .then(lang.hitch(this,function(){
    3737                        this._refresh();
     
    4848        },
    4949        _refreshByType: function(type,container,actions) {
     50            // FIXME
    5051            store.query("_design/default/_view/by_type",{key:type})
    5152            .forEach(lang.hitch(this,function(obj){
     
    6162        },
    6263        _publishSession: function(sessionTemplate) {
     64            // FIXME
    6365            var session = lang.clone(sessionTemplate);
    6466            delete session[store.idProperty];
     
    7375            }));
    7476        }
    75     });
     77    });*/
    7678});
  • Dev/trunk/src/client/qed-client/pages/survey.js

    r443 r487  
    22    "../app/Page",
    33    "../app/Router",
    4     "../model/classes/Survey",
     4    "../model/classes/surveys",
    55    "../model/widgets/QuestionListView",
    66    "../model/widgets/TabbedQuestionBrowser",
    7     "../store",
    87    "dojo/_base/array",
    98    "dojo/_base/declare",
     
    1312    "require",
    1413    "dojo/text!./templates/survey.html"
    15 ], function(Page, Router, Survey, QuestionListView, TabbedQuestionBrowser, store, array, declare, event, lang, when, require, template) {
     14], function(Page, Router, surveys, QuestionListView, TabbedQuestionBrowser, array, declare, event, lang, when, require, template) {
    1615    return declare([Page],{
    1716        contextRequire: require,
     
    6362        _loadSurvey: function() {
    6463            if ( this.surveyId === "new" ) {
    65                 this.survey = Survey.create();
     64                this.survey = surveys.create();
    6665                this.refresh();
    6766            } else {
    68                 when(store.get(this.surveyId))
     67                when(surveys.load(this.surveyId))
    6968                .then(lang.hitch(this,function(survey){
    7069                    this.survey = survey;
    7170                    this.questionList.set('value',
    72                                           Survey.Questions.get(this.survey));
     71                                          this.survey.questions);
    7372                    this.refresh();
    7473                }));
     
    7978        },
    8079        refresh: function() {
    81             this.titleNode.innerHTML = Survey.DisplayTitle.get(this.survey) || "(set title in properties)";
     80            this.titleNode.innerHTML = this.survey.title || "(set title in properties)";
    8281            this.propertiesDialog.set('value',this.survey);
    8382        },
     
    10099        _onSave: function(evt) {
    101100            this.survey.questions = this.questionList.get('value');
    102             store.put(this.survey)
     101            surveys.save(this.survey)
    103102            .then(function() {
    104103                Router.go('/surveys');
     
    111110        },
    112111        _onShowPreview: function() {
    113             Router.go('/previewSurvey/'+store.getIdentity(this.survey),{
     112            Router.go('/previewSurvey/'+this.survey._id,{
    114113                preview: true
    115114            });
  • Dev/trunk/src/client/qed-client/pages/surveyRun.js

    r466 r487  
    44    "../app/Router",
    55    "../lib/func",
    6     "../model/classes/SurveyRun",
    7     "../store",
     6    "../model/classes/responses",
     7    "../model/classes/surveyRuns",
    88    "../widgets/LineWithActionsWidget",
     9    "dojo/_base/array",
    910    "dojo/_base/declare",
    1011    "dojo/_base/event",
     
    1314    "require",
    1415    "dojo/text!./templates/surveyRun.html"
    15 ], function(Content, Page, Router, func, SurveyRun, store, LineWithActionsWidget, declare, event, lang, when, require, template) {
     16], function(Content, Page, Router, func, responses, surveyRuns, LineWithActionsWidget, array, declare, event, lang, when, require, template) {
    1617    return declare([Page],{
    1718        contextRequire: require,
     
    2425            if ( this.surveyRunId ) {
    2526                this._loadSurveyRun();
    26                 this._loadResponses();
    2727            } else {
    2828                throw "No valid uid or survey passed!";
     
    3030        },
    3131        _loadSurveyRun: function() {
    32             when(store.get(this.surveyRunId))
     32            when(surveyRuns.load(this.surveyRunId))
    3333            .then(lang.hitch(this,function(surveyRun){
    3434                this.surveyRun = surveyRun;
    3535                this.refreshSurveyRun();
     36                this._loadResponses();
    3637            }));
    3738        },
    3839        refreshSurveyRun: function() {
    39             this.titleNode.innerHTML = SurveyRun.DisplayTitle.get(this.surveyRun);
    40             this.surveySummaryWidget.set('value',SurveyRun.Survey.get(this.surveyRun));
     40            this.titleNode.innerHTML = this.surveyRun.title || "";
     41            this.surveySummaryWidget.set('value',this.surveyRun.survey);
    4142            this.surveyRunWidget.set('value',this.surveyRun);
    4243            this._onPropChange();
    4344        },
    4445        _loadResponses: function() {
    45             when(store.query("_design/responses/_view/by_surveyrun",{key:this.surveyRunId}))
    46             .forEach(lang.hitch(this,function(response){
    47                 var actions = {
    48                     view: {
    49                         callback: function(){},
    50                         properties: {
    51                             title: "View response"
    52                         }
    53                     }
    54                 };
    55                 if ( !response.publicationDate ) {
    56                     actions.remove = {
    57                         callback: function(){},
    58                         properties: {
    59                             title: "Remove response"
     46            responses.query({surveyRunId:surveyRuns.getId(this.surveyRun)})
     47            .then(lang.hitch(this,function(allResponses){
     48                array.forEach(allResponses, function(response){
     49                    var actions = {
     50                        view: {
     51                            callback: function(){},
     52                            properties: {
     53                                title: "View response"
     54                            }
    6055                        }
    6156                    };
    62                 }
    63                 var w = new LineWithActionsWidget({
    64                     actions: actions
    65                 });
    66                 var responseId = store.getIdentity(response);
    67                 w.set('title',this._link(this._getResponseURL(this.surveyRunId,responseId),responseId));
    68                 w.placeAt(this.responsesNode);
     57                    if ( !response.publicationDate ) {
     58                        actions.remove = {
     59                            callback: function(){},
     60                            properties: {
     61                                title: "Remove response"
     62                            }
     63                        };
     64                    }
     65                    var w = new LineWithActionsWidget({
     66                        actions: actions
     67                    });
     68                    var rid = responses.getId(response);
     69                    w.set('title',this._link(this._buildResponseURL(response),rid),rid);
     70                    w.placeAt(this.responsesNode);
     71                }, this);
    6972            }));
    7073        },
     
    7376            if ( surveyRun.mode === "open" ) {
    7477                this.runURLNode.innerHTML =
    75                     this._link(this._getGeneralURL(store.getIdentity(this.surveyRun)));
     78                    this._link(this._buildGeneralURL(this.surveyRun));
    7679            } else {
    7780                this.runURLNode.innerHTML =
     
    7982            }
    8083        },
    81         _getGeneralURL: function(surveyRunId) {
    82             return 'response.html#!/'+surveyRunId;
     84        _buildGeneralURL: function(surveyRun) {
     85            return 'response.html#!/surveyRuns/'+surveyRuns.getId(surveyRun)+'!secret='+surveyRun.secret;
    8386        },
    84         _getResponseURL: function(surveyRunId,responseId) {
    85             return 'response.html#!/'+surveyRunId+'!id='+responseId;
     87        _buildResponseURL: function(response) {
     88            return 'response.html#!/responses/'+responses.getId(response)+'!secret='+response.secret;
    8689        },
    8790        _link: function(url,label) {
     
    9295                lang.mixin(this.surveyRun,this.surveyRunWidget.get('value'));
    9396
    94                 var SD = SurveyRun.StartDate;
    95                 var ED = SurveyRun.EndDate;
    96                 SD.set(this.surveyRun, SD.get(this.surveyRun));
    97                 ED.set(this.surveyRun, ED.get(this.surveyRun));
    98 
    99                 store.put(this.surveyRun)
     97                surveyRuns.save(this.surveyRun)
    10098                .then(function() {
    10199                    Router.go('/surveys');
  • Dev/trunk/src/client/qed-client/pages/surveys.js

    r443 r487  
    11define([
    2     'dojo/_base/array',
    3     'dojo/_base/declare',
    4     'dojo/_base/lang',
    5     'dojo/when',
    6     '../store',
    7     '../app/Content',
    8     '../app/Page',
    9     '../app/Router',
    10     '../model/classes/Survey',
    11     '../model/classes/SurveyRun',
    12     '../widgets/LineWithActionsWidget',
    13     'dojo/text!./templates/surveys.html'
    14 ],function(array,declare,lang,when,store,Content,Page,Router,Survey,SurveyRun,LineWithActionsWidget,template){
     2    "../app/Content",
     3    "../app/Page",
     4    "../app/Router",
     5    "../model/classes/surveys",
     6    "../model/classes/surveyRuns",
     7    "../widgets/LineWithActionsWidget",
     8    "dojo/_base/array",
     9    "dojo/_base/declare",
     10    "dojo/_base/lang",
     11    "dojo/when",
     12    "dojo/text!./templates/surveys.html"
     13], function(Content, Page, Router, surveys, surveyRuns, LineWithActionsWidget, array, declare, lang, when, template) {
    1514    return declare([Page],{
    1615        templateString: template,
     
    2524        _onPublishSurvey:function(survey){
    2625            var self = this;
    27             survey.publicationDate = store.timestamp();
    28             store.put(survey).then(function(){
     26            survey.publicationDate = new Date();
     27            surveys.save(survey)
     28            .then(function(){
    2929                self.refreshDrafts();
    3030                self.refreshPublished();
     
    3535        _onDeleteSurvey:function(survey){
    3636            var self = this;
    37             store.remove(store.getIdentity(survey),store.getRevision(survey))
     37            surveys.remove(survey)
    3838            .then(function(){
    3939                self.refreshDrafts();
     
    4343        },
    4444        _onEditSurvey:function(survey){
    45             Router.go('/survey/'+store.getIdentity(survey));
     45            Router.go('/survey/'+survey._id);
    4646        },
    4747        _onPreviewSurvey:function(survey){
    48             Router.go('/previewSurvey/'+store.getIdentity(survey));
     48            Router.go('/previewSurvey/'+survey._id);
    4949        },
    5050        _onRunSurvey:function(survey){
    51             var surveyRun = SurveyRun.create();
    52             SurveyRun.Survey.set(surveyRun,survey);
    53             store.put(surveyRun)
     51            var surveyRun = surveyRuns.create();
     52            surveyRun.survey = survey;
     53            surveyRuns.save(surveyRun)
    5454            .then(lang.hitch(this,function(surveyRun){
    5555                this._onRunDetails(surveyRun);
     
    5959        },
    6060        _onRunDetails: function(surveyRun) {
    61             Router.go('/surveyRun/'+store.getIdentity(surveyRun));
     61            Router.go('/surveyRun/'+surveyRun._id);
    6262        },
    6363        refresh: function() {
     
    6868        refreshDrafts: function() {
    6969            this.draftsContainer.set('content','');
    70             when(store.query("_design/surveys/_view/drafts"),
    71                     lang.hitch(this,function(surveys) {
     70            when(surveys.query({drafts:true}), lang.hitch(this,function(surveys) {
    7271                this.draftsTab.set('title','Drafts ('+surveys.length+')');
    7372                array.forEach(surveys,function(survey){
    7473                    var w = new LineWithActionsWidget({
    75                         title: Survey.DisplayTitle.get(survey) || '(unnamed)',
     74                        title: survey.title || '(unnamed)',
    7675                        actions: [{
    7776                            callback: lang.hitch(this,'_onPublishSurvey',survey),
     
    110109        refreshPublished: function() {
    111110            this.publishedContainer.set('content','');
    112             when(store.query("_design/surveys/_view/published"),
    113                     lang.hitch(this, function(surveys) {
     111            when(surveys.query({published:true}), lang.hitch(this, function(surveys) {
    114112                this.publishedTab.set('title','Published ('+surveys.length+')');
    115113                array.forEach(surveys,function(survey){
    116114                    var w = new LineWithActionsWidget({
    117                         title: Survey.DisplayTitle.get(survey),
     115                        title: survey.title || "",
    118116                        actions:[{
    119117                            callback: lang.hitch(this,'_onPreviewSurvey',survey),
     
    138136        refreshRuns: function() {
    139137            this.runsContainer.set('content','');
    140             when(store.query("_design/default/_view/by_type",{key:'SurveyRun'}),
    141                     lang.hitch(this,function(surveyRuns){
     138            when(surveyRuns.query(), lang.hitch(this,function(surveyRuns){
    142139                this.runsTab.set('title','Runs ('+surveyRuns.length+')');
    143140                array.forEach(surveyRuns,function(surveyRun){
    144141                    var w = new LineWithActionsWidget({
    145                         title: SurveyRun.DisplayTitle.get(surveyRun),
     142                        title: surveyRun.title || "",
    146143                        actions:[{
    147144                            callback: lang.hitch(this,'_onRunDetails',surveyRun),
  • Dev/trunk/src/client/qed-client/response.js

    r477 r487  
    44    "./app/Path",
    55    "./lib/async",
    6     "./model/classes/Response",
    7     "./model/classes/SurveyRun",
     6    "./model/classes/responses",
     7    "./model/classes/surveyRuns",
    88    "./pages/response",
    9     "./store",
    109    "dojo/_base/json",
    1110    "dojo/date",
     
    1615    "./stddeps",
    1716    "dojo/domReady!"
    18 ], function(Content, Page, Path, async, Response, SurveyRun, ResponsePage, store, json, date, locale, hash, parser, request) {
     17], function(Content, Page, Path, async, responses, surveyRuns, ResponsePage, json, date, locale, hash, parser, request) {
    1918    parser.parse();
    2019    Content.startup();
     
    2625    }
    2726
    28     var path = new Path('/:surveyRunId');
     27    var path = new Path('/:type/:id');
    2928    var params = path.match(hash());
    3029    params.options = params.options || {};
    31 
    32     if ( !params || !params.surveyRunId ) {
     30   
     31    if ( params && params.type === 'surveyRuns' ) {
     32        var response = responses.create();
     33        response.surveyRunId = params.id;
     34        responses.postWithSecret(response,params.options.secret)
     35        .then(setContent,function(err){ error(err.error); });
     36    } else if ( params && params.type === 'responses' ) {
     37        responses.getWithSecret(params.id,params.options.secret)
     38        .then(setContent,function(err){ error(err.error); });
     39    } else {
    3340        error("Something is wrong with the URL, don't know what survey to show you. Sorry.");
    34         return;
    3541    }
    3642
    37     var surveyRunId = params.surveyRunId;
    38 
    39     function checkDates(surveyRun) {
    40         var now = new Date();
    41         var startDate = SurveyRun.StartDate.get(surveyRun);
    42         var endDate = SurveyRun.EndDate.get(surveyRun);
    43         if ( startDate && date.compare(startDate,now) > 0 ) {
    44             error("This survey will start on "+locale.format(startDate,'date'));
    45             throw false;
    46         }
    47         if ( endDate && date.compare(now,endDate) > 0 ) {
    48             error("This survey ended on "+locale.format(endDate,'date'));
    49             throw false;
    50         }
     43    function setContent(response) {
     44        hash(Path.format("/responses/"+responses.getId(response),
     45                         {secret:response.secret}));
     46        Content.set(new ResponsePage({
     47            response: response
     48        }));
    5149    }
    5250
    53     request.get('/api/surveyRuns/'+surveyRunId,{handleAs:'json'})
    54     .then(function(surveyRun){
    55         checkDates(surveyRun);
    56         if ( params.options.id ) {
    57             return params.options.id;
    58         } else {
    59             if ( surveyRun.mode === "open") {
    60                 var response = Response.create();
    61                 response.surveyRunId = surveyRunId;
    62                 return request.post('/api/responses',{
    63                     handleAs:'json',
    64                     data:json.toJson(response),
    65                     headers:{"Content-Type": "application/json"}
    66                 }).then(function(res){
    67                     return res.id;
    68                 });
    69             } else {
    70                 error("Cannot respond to closed survey without response id. Sorry.");
    71                 throw false;
    72             }
    73         }
    74         return surveyRun;
    75     },function(){
    76         error("No running survey found for the given id. Sorry.");
    77         throw false;
    78     })
    79     .then(function(responseId){
    80         hash(Path.format("/"+surveyRunId,{ id: responseId }));
    81         Content.set(new ResponsePage({
    82             surveyRunId: surveyRunId,
    83             options: {
    84                 id: responseId
    85             }
    86         }));
    87     });
    8851});
  • Dev/trunk/src/client/qed-client/routes.js

    r443 r487  
    11define([
    2     './pages/index',
    3     './pages/questions',
    4     './pages/question',
    5     './pages/surveys',
    6     './pages/survey',
    7     './pages/surveyRun',
    8     './pages/sessions',
    9     './pages/session',
    10     './pages/previewSurvey'
    11 ],function(index,questions,question,surveys,survey,surveyRun,sessions,session,previewSurvey){
     2    "./pages/index",
     3    "./pages/previewSurvey",
     4    "./pages/question",
     5    "./pages/questions",
     6    "./pages/survey",
     7    "./pages/surveyRun",
     8    "./pages/surveys"
     9], function(index, previewSurvey, question, questions, survey, surveyRun, surveys) {
    1210
    1311    return [
  • Dev/trunk/src/client/qed-client/session.coffee

    r468 r487  
    22    "dojo/_base/declare",
    33    "dojo/_base/json",
     4    "dojo/Deferred",
    45    "dojo/Evented",
    56    "dojo/request"
    6 ], (declare, json, Evented, request) ->
     7], (declare, json, Deferred, Evented, request) ->
    78    Session = declare [Evented],
    89        info: null
     
    1819                @_set res
    1920            , () =>
    20                 throw (@_set null)
     21                new Deferred().reject (@_set null)
    2122
    2223        login: (username, password) ->
     
    3031                @_set res
    3132            , () =>
    32                 throw (@_set null)
     33                new Deferred().reject (@_set null)
    3334
    3435        logout: () ->
     
    4546                @info = newInfo
    4647                @emit 'change', @info
    47                 @info
     48            @info
    4849
    4950    new Session()
  • Dev/trunk/src/client/qed-client/stddeps.js

    r468 r487  
    11define([
     2
    23    // dijit & rft widgets used declaratively in templates
    34    'dijit/Dialog',
     
    4344    './widgets/Selector',
    4445    './widgets/TitleGroup'
     46
    4547],function(){});
  • Dev/trunk/src/client/qed-client/ui/LoginDialogWrapper.coffee

    r468 r487  
    1717            if @_started then return
    1818            @inherited arguments
    19             _on session, 'change', (lang.hitch @, @onUserChange)
     19            _on session, 'change', (lang.hitch @, 'onUserChange')
    2020            @onUserChange session.get()
    2121        onLogin: (evt) ->
     
    3535            else
    3636                @loginDialog.show()
     37            null
  • Dev/trunk/src/client/qed-client/xhr.js

    r486 r487  
    22    "./session",
    33    "dojo/Deferred",
     4    "dojo/_base/lang",
    45    "dojo/on",
    5     "dojo/request"
    6 ], function(session, Deferred, on, request) {
     6    "dojo/_base/xhr"
     7], function(session, Deferred, lang, on, xhr) {
    78
    89    var user = session.get();
    910    var queue = [];
    1011   
    11     on(session, 'change', function(newUser) {
     12    on(session, 'change', function(newUser){
    1213        user = newUser;
    13         if ( user ) {
    14             retry();
    15         }
     14        retry();
    1615    });
    17    
     16
    1817    function retry() {
    19         if (queue.length > 0) {
     18        if (user && queue.length > 0) {
    2019            var item = queue.shift();
    21             console.log("Retry",item.url);
     20            console.log("Retry",item.options.url);
    2221            real_request(item);
    2322        }
     
    2524   
    2625    function real_request(item) {
    27         var req = request(item.url,item.options);
     26        var req = xhr(item.method,lang.mixin(item.options||{},{
     27            failOk: true
     28        }));
     29        item.promise.ioArgs = req.ioArgs;
    2830
    29         // forward successfull response
    30         req.then(function(body){
    31             item.dfd.resolve(body);
    32         });
    33 
    34         // handle unauthenticated and queued requests
    35         req.response.then(function(response){
     31        req.then(function(result){
     32            item.dfd.resolve(result);
    3633            retry();
    37         }, function(error) {
     34        }, function(error){
    3835            if ( error.response.status === 401 ) {
    3936                queue.unshift(item);
    4037                session.restore();
    4138            } else {
    42                 item.dfd.reject(error); // this should be error body
    43                                         // not, the request?
     39                item.dfd.reject(error);
    4440                retry();
    4541            }
     
    4743    }
    4844   
    49     var _request = function(url, options) {
     45    var _request = function(method, options) {
    5046        var item = {
    51             url: url,
     47            method: method,
    5248            options: options,
    5349            dfd: new Deferred()
    5450        };
     51        item.promise = lang.delegate(item.dfd.promise);
    5552        // only do the request directly if we are authenticated and
    5653        // there are no earlier requests queued.
    5754        if ( user && queue.length === 0 ) {
    58             console.log("Request",url);
     55            console.log("Request",options.url);
    5956            real_request(item);
    6057        } else {
    61             console.log("Push",url);
     58            console.log("Push",options.url);
    6259            queue.push(item);
    6360        }
    64         return item.dfd.promise;
     61        return item.promise;
    6562    };
    6663
  • Dev/trunk/src/node_modules/express/node_modules/buffer-crc32/package.json

    r484 r487  
    2020  "main": "index.js",
    2121  "scripts": {
    22     "test": "./node_modules/.bin/tap tests/*.test.js"
     22    "test": "tap tests/*.test.js"
    2323  },
    2424  "dependencies": {},
     
    3636  },
    3737  "_id": "buffer-crc32@0.2.1",
    38   "_from": "buffer-crc32@0.2.1"
     38  "dist": {
     39    "shasum": "d4831cc88b961550a88d829efbc0b139caf22d8c"
     40  },
     41  "_from": "buffer-crc32@0.2.1",
     42  "_resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.1.tgz"
    3943}
  • Dev/trunk/src/node_modules/express/node_modules/commander/package.json

    r484 r487  
    3434    "url": "https://github.com/visionmedia/commander.js/issues"
    3535  },
     36  "homepage": "https://github.com/visionmedia/commander.js",
    3637  "_id": "commander@0.6.1",
    37   "_from": "commander@0.6.1"
     38  "dist": {
     39    "shasum": "c725ed5e9b2bf532b3fe3cba4f81b552cecf0550"
     40  },
     41  "_from": "commander@0.6.1",
     42  "_resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz"
    3843}
  • Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/bytes/package.json

    r484 r487  
    1717  "readmeFilename": "Readme.md",
    1818  "_id": "bytes@0.2.0",
    19   "_from": "bytes@0.2.0"
     19  "dist": {
     20    "shasum": "b4c569295d86a498a119945f1d8a26f76e4b5462"
     21  },
     22  "_from": "bytes@0.2.0",
     23  "_resolved": "https://registry.npmjs.org/bytes/-/bytes-0.2.0.tgz"
    2024}
  • Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/cookie/package.json

    r484 r487  
    3232    "url": "https://github.com/shtylman/node-cookie/issues"
    3333  },
     34  "homepage": "https://github.com/shtylman/node-cookie",
    3435  "_id": "cookie@0.0.5",
    35   "_from": "cookie@0.0.5"
     36  "dist": {
     37    "shasum": "2597397e06ec3eaf53c88851570e97949c1e4dcd"
     38  },
     39  "_from": "cookie@0.0.5",
     40  "_resolved": "https://registry.npmjs.org/cookie/-/cookie-0.0.5.tgz"
    3641}
  • Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/formidable/package.json

    r484 r487  
    3535  "dependencies": {},
    3636  "_id": "formidable@1.0.14",
    37   "_from": "formidable@1.0.14"
     37  "dist": {
     38    "shasum": "08be7c89a9ebffbe5b2fbd4df0ad3f5cd19c5147"
     39  },
     40  "_from": "formidable@1.0.14",
     41  "_resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.14.tgz"
    3842}
  • Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/pause/package.json

    r484 r487  
    1717  "readmeFilename": "Readme.md",
    1818  "_id": "pause@0.0.1",
    19   "_from": "pause@0.0.1"
     19  "dist": {
     20    "shasum": "dc3b2287742f3f6249f2d228e74cff5a3f9fe8da"
     21  },
     22  "_from": "pause@0.0.1",
     23  "_resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz"
    2024}
  • Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/qs/package.json

    r484 r487  
    3333    "url": "https://github.com/visionmedia/node-querystring/issues"
    3434  },
     35  "homepage": "https://github.com/visionmedia/node-querystring",
    3536  "_id": "qs@0.6.5",
    36   "_from": "qs@0.6.5"
     37  "dist": {
     38    "shasum": "cdaafb6ed1dbcf48405f41ca92bf95361ad923dc"
     39  },
     40  "_from": "qs@0.6.5",
     41  "_resolved": "https://registry.npmjs.org/qs/-/qs-0.6.5.tgz"
    3742}
  • Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/README.md

    r484 r487  
    1212
    1313### mime.lookup(path)
    14 Get the mime type associated with a file. Performs a case-insensitive lookup using the extension in `path` (the substring after the last '/' or '.').  E.g.
     14Get the mime type associated with a file, if no mime type is found `application/octet-stream` is returned. Performs a case-insensitive lookup using the extension in `path` (the substring after the last '/' or '.').  E.g.
    1515
    1616    var mime = require('mime');
     
    2020    mime.lookup('.TXT');                      // => 'text/plain'
    2121    mime.lookup('htm');                       // => 'text/html'
     22
     23### mime.default_type
     24Sets the mime type returned when `mime.lookup` fails to find the extension searched for. (Default is `application/octet-stream`.)
    2225
    2326### mime.extension(type)
  • Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/mime.js

    r484 r487  
    7070 */
    7171Mime.prototype.lookup = function(path, fallback) {
    72   var ext = path.replace(/.*[\.\/]/, '').toLowerCase();
     72  var ext = path.replace(/.*[\.\/\\]/, '').toLowerCase();
    7373
    7474  return this.types[ext] || fallback || this.default_type;
     
    7979 */
    8080Mime.prototype.extension = function(mimeType) {
    81   return this.extensions[mimeType];
     81  var type = mimeType.match(/^\s*([^;\s]*)(?:;|\s|$)/)[1].toLowerCase();
     82  return this.extensions[type];
    8283};
    8384
  • Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/package.json

    r484 r487  
    2525    "type": "git"
    2626  },
    27   "version": "1.2.9",
    28   "readme": "# mime\n\nComprehensive MIME type mapping API. Includes all 600+ types and 800+ extensions defined by the Apache project, plus additional types submitted by the node.js community.\n\n## Install\n\nInstall with [npm](http://github.com/isaacs/npm):\n\n    npm install mime\n\n## API - Queries\n\n### mime.lookup(path)\nGet the mime type associated with a file. Performs a case-insensitive lookup using the extension in `path` (the substring after the last '/' or '.').  E.g.\n\n    var mime = require('mime');\n\n    mime.lookup('/path/to/file.txt');         // => 'text/plain'\n    mime.lookup('file.txt');                  // => 'text/plain'\n    mime.lookup('.TXT');                      // => 'text/plain'\n    mime.lookup('htm');                       // => 'text/html'\n\n### mime.extension(type)\nGet the default extension for `type`\n\n    mime.extension('text/html');                 // => 'html'\n    mime.extension('application/octet-stream');  // => 'bin'\n\n### mime.charsets.lookup()\n\nMap mime-type to charset\n\n    mime.charsets.lookup('text/plain');        // => 'UTF-8'\n\n(The logic for charset lookups is pretty rudimentary.  Feel free to suggest improvements.)\n\n## API - Defining Custom Types\n\nThe following APIs allow you to add your own type mappings within your project.  If you feel a type should be included as part of node-mime, see [requesting new types](https://github.com/broofa/node-mime/wiki/Requesting-New-Types).\n\n### mime.define()\n\nAdd custom mime/extension mappings\n\n    mime.define({\n        'text/x-some-format': ['x-sf', 'x-sft', 'x-sfml'],\n        'application/x-my-type': ['x-mt', 'x-mtt'],\n        // etc ...\n    });\n\n    mime.lookup('x-sft');                 // => 'text/x-some-format'\n\nThe first entry in the extensions array is returned by `mime.extension()`. E.g.\n\n    mime.extension('text/x-some-format'); // => 'x-sf'\n\n### mime.load(filepath)\n\nLoad mappings from an Apache \".types\" format file\n\n    mime.load('./my_project.types');\n\nThe .types file format is simple -  See the `types` dir for examples.\n",
     27  "version": "1.2.11",
     28  "readme": "# mime\n\nComprehensive MIME type mapping API. Includes all 600+ types and 800+ extensions defined by the Apache project, plus additional types submitted by the node.js community.\n\n## Install\n\nInstall with [npm](http://github.com/isaacs/npm):\n\n    npm install mime\n\n## API - Queries\n\n### mime.lookup(path)\nGet the mime type associated with a file, if no mime type is found `application/octet-stream` is returned. Performs a case-insensitive lookup using the extension in `path` (the substring after the last '/' or '.').  E.g.\n\n    var mime = require('mime');\n\n    mime.lookup('/path/to/file.txt');         // => 'text/plain'\n    mime.lookup('file.txt');                  // => 'text/plain'\n    mime.lookup('.TXT');                      // => 'text/plain'\n    mime.lookup('htm');                       // => 'text/html'\n\n### mime.default_type\nSets the mime type returned when `mime.lookup` fails to find the extension searched for. (Default is `application/octet-stream`.)\n\n### mime.extension(type)\nGet the default extension for `type`\n\n    mime.extension('text/html');                 // => 'html'\n    mime.extension('application/octet-stream');  // => 'bin'\n\n### mime.charsets.lookup()\n\nMap mime-type to charset\n\n    mime.charsets.lookup('text/plain');        // => 'UTF-8'\n\n(The logic for charset lookups is pretty rudimentary.  Feel free to suggest improvements.)\n\n## API - Defining Custom Types\n\nThe following APIs allow you to add your own type mappings within your project.  If you feel a type should be included as part of node-mime, see [requesting new types](https://github.com/broofa/node-mime/wiki/Requesting-New-Types).\n\n### mime.define()\n\nAdd custom mime/extension mappings\n\n    mime.define({\n        'text/x-some-format': ['x-sf', 'x-sft', 'x-sfml'],\n        'application/x-my-type': ['x-mt', 'x-mtt'],\n        // etc ...\n    });\n\n    mime.lookup('x-sft');                 // => 'text/x-some-format'\n\nThe first entry in the extensions array is returned by `mime.extension()`. E.g.\n\n    mime.extension('text/x-some-format'); // => 'x-sf'\n\n### mime.load(filepath)\n\nLoad mappings from an Apache \".types\" format file\n\n    mime.load('./my_project.types');\n\nThe .types file format is simple -  See the `types` dir for examples.\n",
    2929  "readmeFilename": "README.md",
    3030  "bugs": {
    3131    "url": "https://github.com/broofa/node-mime/issues"
    3232  },
    33   "_id": "mime@1.2.9",
     33  "homepage": "https://github.com/broofa/node-mime",
     34  "_id": "mime@1.2.11",
    3435  "_from": "mime@~1.2.9"
    3536}
  • Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/test.js

    r484 r487  
    55var mime = require('./mime');
    66var assert = require('assert');
     7var path = require('path');
    78
    89function eq(a, b) {
     
    1819//
    1920
    20 eq('text/plain', mime.lookup('text.txt'));
    21 eq('text/plain', mime.lookup('.text.txt'));
    22 eq('text/plain', mime.lookup('.txt'));
    23 eq('text/plain', mime.lookup('txt'));
    24 eq('application/octet-stream', mime.lookup('text.nope'));
    25 eq('fallback', mime.lookup('text.fallback', 'fallback'));
    26 eq('application/octet-stream', mime.lookup('constructor'));
    27 eq('text/plain', mime.lookup('TEXT.TXT'));
    28 eq('text/event-stream', mime.lookup('text/event-stream'));
    29 eq('application/x-web-app-manifest+json', mime.lookup('text.webapp'));
     21eq('text/plain', mime.lookup('text.txt'));     // normal file
     22eq('text/plain', mime.lookup('TEXT.TXT'));     // uppercase
     23eq('text/plain', mime.lookup('dir/text.txt')); // dir + file
     24eq('text/plain', mime.lookup('.text.txt'));    // hidden file
     25eq('text/plain', mime.lookup('.txt'));         // nameless
     26eq('text/plain', mime.lookup('txt'));          // extension-only
     27eq('text/plain', mime.lookup('/txt'));         // extension-less ()
     28eq('text/plain', mime.lookup('\\txt'));        // Windows, extension-less
     29eq('application/octet-stream', mime.lookup('text.nope')); // unrecognized
     30eq('fallback', mime.lookup('text.fallback', 'fallback')); // alternate default
    3031
    3132//
     
    3637eq('html', mime.extension(mime.types.htm));
    3738eq('bin', mime.extension('application/octet-stream'));
    38 eq(undefined, mime.extension('constructor'));
     39eq('bin', mime.extension('application/octet-stream '));
     40eq('html', mime.extension(' text/html; charset=UTF-8'));
     41eq('html', mime.extension('text/html; charset=UTF-8 '));
     42eq('html', mime.extension('text/html; charset=UTF-8'));
     43eq('html', mime.extension('text/html ; charset=UTF-8'));
     44eq('html', mime.extension('text/html;charset=UTF-8'));
     45eq('html', mime.extension('text/Html;charset=UTF-8'));
     46eq(undefined, mime.extension('unrecognized'));
    3947
    4048//
    41 // Test node types
     49// Test node.types lookups
    4250//
    4351
     52eq('application/font-woff', mime.lookup('file.woff'));
    4453eq('application/octet-stream', mime.lookup('file.buffer'));
    4554eq('audio/mp4', mime.lookup('file.m4a'));
     55eq('font/opentype', mime.lookup('file.otf'));
    4656
    4757//
     
    5363eq('fallback', mime.charsets.lookup('application/octet-stream', 'fallback'));
    5464
     65//
     66// Test for overlaps between mime.types and node.types
     67//
     68
     69var apacheTypes = new mime.Mime(), nodeTypes = new mime.Mime();
     70apacheTypes.load(path.join(__dirname, 'types/mime.types'));
     71nodeTypes.load(path.join(__dirname, 'types/node.types'));
     72
     73var keys = [].concat(Object.keys(apacheTypes.types))
     74             .concat(Object.keys(nodeTypes.types));
     75keys.sort();
     76for (var i = 1; i < keys.length; i++) {
     77  if (keys[i] == keys[i-1]) {
     78    console.warn('Warning: ' +
     79      'node.types defines ' + keys[i] + '->' + nodeTypes.types[keys[i]] +
     80      ', mime.types defines ' + keys[i] + '->' + apacheTypes.types[keys[i]]);
     81  }
     82}
     83
    5584console.log('\nOK');
  • Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/types/mime.types

    r484 r487  
    10551055application/x-font-ttf                          ttf ttc
    10561056application/x-font-type1                        pfa pfb pfm afm
    1057 application/x-font-woff                         woff
     1057application/font-woff                           woff
    10581058# application/x-font-vfont
    10591059application/x-freearc                           arc
  • Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/node_modules/mime/types/node.types

    r484 r487  
    1616text/x-component  htc
    1717
    18 # What: HTML5 application cache manifest
     18# What: HTML5 application cache manifes ('.manifest' extension)
    1919# Why: De-facto standard. Required by Mozilla browser when serving HTML5 apps
    2020# per https://developer.mozilla.org/en/offline_resources_in_firefox
    2121# Added by: louisremi
    22 text/cache-manifest  appcache manifest
     22text/cache-manifest  manifest
    2323
    2424# What: node binary buffer format
     
    5959# Added by: avoidwork
    6060text/x-markdown  markdown md mkd
     61
     62# What: ini files
     63# Why: because they're just text files
     64# Added by: Matthew Kastor
     65text/plain  ini
     66
     67# What: DASH Adaptive Streaming manifest
     68# Why: https://developer.mozilla.org/en-US/docs/DASH_Adaptive_Streaming_for_HTML_5_Video
     69# Added by: eelcocramer
     70application/dash+xml mdp
     71
     72# What: OpenType font files - http://www.microsoft.com/typography/otspec/
     73# Why:  Browsers usually ignore the font MIME types and sniff the content,
     74#       but Chrome, shows a warning if OpenType fonts aren't served with
     75#       the `font/opentype` MIME type: http://i.imgur.com/8c5RN8M.png.
     76# Added by: alrra
     77font/opentype  otf
  • Dev/trunk/src/node_modules/express/node_modules/connect/node_modules/send/package.json

    r484 r487  
    3737    "url": "https://github.com/visionmedia/send/issues"
    3838  },
     39  "homepage": "https://github.com/visionmedia/send",
    3940  "_id": "send@0.1.1",
    40   "_from": "send@0.1.1"
     41  "dist": {
     42    "shasum": "40ded726322604c29d7229683f9207bd6d76e217"
     43  },
     44  "_from": "send@0.1.1",
     45  "_resolved": "https://registry.npmjs.org/send/-/send-0.1.1.tgz"
    4146}
  • Dev/trunk/src/node_modules/express/node_modules/connect/package.json

    r484 r487  
    4949    "url": "https://github.com/senchalabs/connect/issues"
    5050  },
     51  "homepage": "https://github.com/senchalabs/connect",
    5152  "_id": "connect@2.7.11",
    52   "_from": "connect@2.7.11"
     53  "dist": {
     54    "shasum": "839e3928b727827db859a4b3a1d04ea9a06b6ab1"
     55  },
     56  "_from": "connect@2.7.11",
     57  "_resolved": "https://registry.npmjs.org/connect/-/connect-2.7.11.tgz"
    5358}
  • Dev/trunk/src/node_modules/express/node_modules/cookie-signature/package.json

    r484 r487  
    2121  "readmeFilename": "Readme.md",
    2222  "_id": "cookie-signature@1.0.1",
    23   "_from": "cookie-signature@1.0.1"
     23  "dist": {
     24    "shasum": "404efcace10fb30f3451483045d743463ae8602c"
     25  },
     26  "_from": "cookie-signature@1.0.1",
     27  "_resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.1.tgz"
    2428}
  • Dev/trunk/src/node_modules/express/node_modules/cookie/package.json

    r484 r487  
    3232    "url": "https://github.com/shtylman/node-cookie/issues"
    3333  },
     34  "homepage": "https://github.com/shtylman/node-cookie",
    3435  "_id": "cookie@0.1.0",
    35   "_from": "cookie@0.1.0"
     36  "dist": {
     37    "shasum": "b56ac6108fcab9073208d405c4fa8aaa35f00810"
     38  },
     39  "_from": "cookie@0.1.0",
     40  "_resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.0.tgz"
    3641}
  • Dev/trunk/src/node_modules/express/node_modules/debug/Readme.md

    r484 r487  
    1 
    21# debug
    32
     
    5958  ![](http://f.cl.ly/items/2i3h1d3t121M2Z1A3Q0N/Screenshot.png)
    6059
    61   When stdout is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below:
     60  When stderr is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below:
     61  _(NOTE: Debug now uses stderr instead of stdout, so the correct shell command for this example is actually `DEBUG=* node example/worker 2> out &`)_
    6262 
    6363  ![](http://f.cl.ly/items/112H3i0e0o0P0a2Q2r11/Screenshot.png)
    64 
     64 
    6565## Conventions
    6666
  • Dev/trunk/src/node_modules/express/node_modules/debug/debug.js

    r484 r487  
    1818
    1919  return function(fmt){
     20    fmt = coerce(fmt);
     21
    2022    var curr = new Date;
    2123    var ms = curr - (debug[name] || curr);
     
    120122};
    121123
     124/**
     125 * Coerce `val`.
     126 */
     127
     128function coerce(val) {
     129  if (val instanceof Error) return val.stack || val.message;
     130  return val;
     131}
     132
    122133// persist
    123134
    124 if (window.localStorage) debug.enable(localStorage.debug);
     135try {
     136  if (window.localStorage) debug.enable(localStorage.debug);
     137} catch(e){}
  • Dev/trunk/src/node_modules/express/node_modules/debug/lib/debug.js

    r484 r487  
    109109
    110110  function colored(fmt) {
     111    fmt = coerce(fmt);
     112
    111113    var curr = new Date;
    112114    var ms = curr - (prev[name] || curr);
     
    122124
    123125  function plain(fmt) {
     126    fmt = coerce(fmt);
     127
    124128    fmt = new Date().toUTCString()
    125129      + ' ' + name + ' ' + fmt;
     
    133137    : plain;
    134138}
     139
     140/**
     141 * Coerce `val`.
     142 */
     143
     144function coerce(val) {
     145  if (val instanceof Error) return val.stack || val.message;
     146  return val;
     147}
  • Dev/trunk/src/node_modules/express/node_modules/debug/package.json

    r484 r487  
    11{
    22  "name": "debug",
    3   "version": "0.7.2",
     3  "version": "0.7.4",
    44  "repository": {
    55    "type": "git",
     
    2121  },
    2222  "main": "lib/debug.js",
    23   "browserify": "debug.js",
     23  "browser": "./debug.js",
    2424  "engines": {
    2525    "node": "*"
    2626  },
     27  "files": [
     28    "lib/debug.js",
     29    "debug.js",
     30    "index.js"
     31  ],
    2732  "component": {
    2833    "scripts": {
     
    3136    }
    3237  },
    33   "readme": "\n# debug\n\n  tiny node.js debugging utility modelled after node core's debugging technique.\n\n## Installation\n\n```\n$ npm install debug\n```\n\n## Usage\n\n With `debug` you simply invoke the exported function to generate your debug function, passing it a name which will determine if a noop function is returned, or a decorated `console.error`, so all of the `console` format string goodies you're used to work fine. A unique color is selected per-function for visibility.\n \nExample _app.js_:\n\n```js\nvar debug = require('debug')('http')\n  , http = require('http')\n  , name = 'My App';\n\n// fake app\n\ndebug('booting %s', name);\n\nhttp.createServer(function(req, res){\n  debug(req.method + ' ' + req.url);\n  res.end('hello\\n');\n}).listen(3000, function(){\n  debug('listening');\n});\n\n// fake worker of some kind\n\nrequire('./worker');\n```\n\nExample _worker.js_:\n\n```js\nvar debug = require('debug')('worker');\n\nsetInterval(function(){\n  debug('doing some work');\n}, 1000);\n```\n\n The __DEBUG__ environment variable is then used to enable these based on space or comma-delimited names. Here are some examples:\n\n  ![debug http and worker](http://f.cl.ly/items/18471z1H402O24072r1J/Screenshot.png)\n\n  ![debug worker](http://f.cl.ly/items/1X413v1a3M0d3C2c1E0i/Screenshot.png)\n\n## Millisecond diff\n\n  When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the \"+NNNms\" will show you how much time was spent between calls.\n\n  ![](http://f.cl.ly/items/2i3h1d3t121M2Z1A3Q0N/Screenshot.png)\n\n  When stdout is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below:\n  \n  ![](http://f.cl.ly/items/112H3i0e0o0P0a2Q2r11/Screenshot.png)\n\n## Conventions\n\n If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use \":\" to separate features. For example \"bodyParser\" from Connect would then be \"connect:bodyParser\". \n\n## Wildcards\n\n  The \"*\" character may be used as a wildcard. Suppose for example your library has debuggers named \"connect:bodyParser\", \"connect:compress\", \"connect:session\", instead of listing all three with `DEBUG=connect:bodyParser,connect.compress,connect:session`, you may simply do `DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`.\n\n  You can also exclude specific debuggers by prefixing them with a \"-\" character.  For example, `DEBUG=* -connect:*` would include all debuggers except those starting with \"connect:\".\n\n## Browser support\n\n Debug works in the browser as well, currently persisted by `localStorage`. For example if you have `worker:a` and `worker:b` as shown below, and wish to debug both type `debug.enable('worker:*')` in the console and refresh the page, this will remain until you disable with `debug.disable()`. \n\n```js\na = debug('worker:a');\nb = debug('worker:b');\n\nsetInterval(function(){\n  a('doing some work');\n}, 1000);\n\nsetInterval(function(){\n  a('doing some work');\n}, 1200);\n```\n\n## License \n\n(The MIT License)\n\nCopyright (c) 2011 TJ Holowaychuk &lt;tj@vision-media.ca&gt;\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.",
     38  "readme": "# debug\n\n  tiny node.js debugging utility modelled after node core's debugging technique.\n\n## Installation\n\n```\n$ npm install debug\n```\n\n## Usage\n\n With `debug` you simply invoke the exported function to generate your debug function, passing it a name which will determine if a noop function is returned, or a decorated `console.error`, so all of the `console` format string goodies you're used to work fine. A unique color is selected per-function for visibility.\n \nExample _app.js_:\n\n```js\nvar debug = require('debug')('http')\n  , http = require('http')\n  , name = 'My App';\n\n// fake app\n\ndebug('booting %s', name);\n\nhttp.createServer(function(req, res){\n  debug(req.method + ' ' + req.url);\n  res.end('hello\\n');\n}).listen(3000, function(){\n  debug('listening');\n});\n\n// fake worker of some kind\n\nrequire('./worker');\n```\n\nExample _worker.js_:\n\n```js\nvar debug = require('debug')('worker');\n\nsetInterval(function(){\n  debug('doing some work');\n}, 1000);\n```\n\n The __DEBUG__ environment variable is then used to enable these based on space or comma-delimited names. Here are some examples:\n\n  ![debug http and worker](http://f.cl.ly/items/18471z1H402O24072r1J/Screenshot.png)\n\n  ![debug worker](http://f.cl.ly/items/1X413v1a3M0d3C2c1E0i/Screenshot.png)\n\n## Millisecond diff\n\n  When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the \"+NNNms\" will show you how much time was spent between calls.\n\n  ![](http://f.cl.ly/items/2i3h1d3t121M2Z1A3Q0N/Screenshot.png)\n\n  When stderr is not a TTY, `Date#toUTCString()` is used, making it more useful for logging the debug information as shown below:\n  _(NOTE: Debug now uses stderr instead of stdout, so the correct shell command for this example is actually `DEBUG=* node example/worker 2> out &`)_\n  \n  ![](http://f.cl.ly/items/112H3i0e0o0P0a2Q2r11/Screenshot.png)\n  \n## Conventions\n\n If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use \":\" to separate features. For example \"bodyParser\" from Connect would then be \"connect:bodyParser\". \n\n## Wildcards\n\n  The \"*\" character may be used as a wildcard. Suppose for example your library has debuggers named \"connect:bodyParser\", \"connect:compress\", \"connect:session\", instead of listing all three with `DEBUG=connect:bodyParser,connect.compress,connect:session`, you may simply do `DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`.\n\n  You can also exclude specific debuggers by prefixing them with a \"-\" character.  For example, `DEBUG=* -connect:*` would include all debuggers except those starting with \"connect:\".\n\n## Browser support\n\n Debug works in the browser as well, currently persisted by `localStorage`. For example if you have `worker:a` and `worker:b` as shown below, and wish to debug both type `debug.enable('worker:*')` in the console and refresh the page, this will remain until you disable with `debug.disable()`. \n\n```js\na = debug('worker:a');\nb = debug('worker:b');\n\nsetInterval(function(){\n  a('doing some work');\n}, 1000);\n\nsetInterval(function(){\n  a('doing some work');\n}, 1200);\n```\n\n## License \n\n(The MIT License)\n\nCopyright (c) 2011 TJ Holowaychuk &lt;tj@vision-media.ca&gt;\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n'Software'), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\nCLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\nSOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n",
    3439  "readmeFilename": "Readme.md",
    3540  "bugs": {
    3641    "url": "https://github.com/visionmedia/debug/issues"
    3742  },
    38   "_id": "debug@0.7.2",
    39   "_from": "debug@*",
    40   "scripts": {}
     43  "homepage": "https://github.com/visionmedia/debug",
     44  "_id": "debug@0.7.4",
     45  "_from": "debug@*"
    4146}
  • Dev/trunk/src/node_modules/express/node_modules/fresh/package.json

    r484 r487  
    1717  "readmeFilename": "Readme.md",
    1818  "_id": "fresh@0.1.0",
    19   "_from": "fresh@0.1.0"
     19  "dist": {
     20    "shasum": "97ce63fda273b033f866d3a29b9920d034aa2074"
     21  },
     22  "_from": "fresh@0.1.0",
     23  "_resolved": "https://registry.npmjs.org/fresh/-/fresh-0.1.0.tgz"
    2024}
  • Dev/trunk/src/node_modules/express/node_modules/methods/package.json

    r484 r487  
    1717  "readme": "ERROR: No README data found!",
    1818  "_id": "methods@0.0.1",
    19   "_from": "methods@0.0.1"
     19  "dist": {
     20    "shasum": "c0a484b3e1f28764c5cfd234e1a156d47092ecca"
     21  },
     22  "_from": "methods@0.0.1",
     23  "_resolved": "https://registry.npmjs.org/methods/-/methods-0.0.1.tgz"
    2024}
  • Dev/trunk/src/node_modules/express/node_modules/mkdirp/package.json

    r484 r487  
    3232    "url": "https://github.com/substack/node-mkdirp/issues"
    3333  },
     34  "homepage": "https://github.com/substack/node-mkdirp",
    3435  "_id": "mkdirp@0.3.4",
    35   "_from": "mkdirp@0.3.4"
     36  "dist": {
     37    "shasum": "8642eb45b73f5cd19824e94b572871013af8a305"
     38  },
     39  "_from": "mkdirp@0.3.4",
     40  "_resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.4.tgz"
    3641}
  • Dev/trunk/src/node_modules/express/node_modules/range-parser/package.json

    r484 r487  
    1717  "readmeFilename": "Readme.md",
    1818  "_id": "range-parser@0.0.4",
    19   "_from": "range-parser@0.0.4"
     19  "dist": {
     20    "shasum": "a91c03c737187765f0dcdbe32fa824e52b47a9a6"
     21  },
     22  "_from": "range-parser@0.0.4",
     23  "_resolved": "https://registry.npmjs.org/range-parser/-/range-parser-0.0.4.tgz"
    2024}
  • Dev/trunk/src/node_modules/express/node_modules/send/node_modules/mime/package.json

    r484 r487  
    3131    "url": "https://github.com/broofa/node-mime/issues"
    3232  },
     33  "homepage": "https://github.com/broofa/node-mime",
    3334  "_id": "mime@1.2.6",
    34   "_from": "mime@1.2.6"
     35  "dist": {
     36    "shasum": "193c6817fabeefd6a5f29acabd0b6cebc2c0910f"
     37  },
     38  "_from": "mime@1.2.6",
     39  "_resolved": "https://registry.npmjs.org/mime/-/mime-1.2.6.tgz"
    3540}
  • Dev/trunk/src/node_modules/express/node_modules/send/package.json

    r484 r487  
    3131  "readmeFilename": "Readme.md",
    3232  "_id": "send@0.1.0",
    33   "_from": "send@0.1.0"
     33  "dist": {
     34    "shasum": "4ea2b3aca167b22699fdbe4eb5bce35fc57ca7d5"
     35  },
     36  "_from": "send@0.1.0",
     37  "_resolved": "https://registry.npmjs.org/send/-/send-0.1.0.tgz"
    3438}
  • Dev/trunk/src/node_modules/express/package.json

    r484 r487  
    8080    "url": "https://github.com/visionmedia/express/issues"
    8181  },
     82  "homepage": "https://github.com/visionmedia/express",
    8283  "_id": "express@3.2.6",
    83   "_from": "express@~3.2.3"
     84  "dist": {
     85    "shasum": "4e16430ceaf29ac1f185c6996d50ca6394ec6357"
     86  },
     87  "_from": "express@3.2.6",
     88  "_resolved": "https://registry.npmjs.org/express/-/express-3.2.6.tgz"
    8489}
  • Dev/trunk/src/node_modules/tv4/README.md

    r484 r487  
    8585## Cyclical JavaScript objects
    8686
    87 While they don't occur in proper JSON, JavaScript does support self-referencing objects. Any of the above calls support an optional final argument, checkRecursive. If true, tv4 will handle self-referencing objects properly - this slows down validation slightly, but that's better than a hanging script.
     87While they don't occur in proper JSON, JavaScript does support self-referencing objects. Any of the above calls support an optional third argument: `checkRecursive`. If true, tv4 will handle self-referencing objects properly - this slows down validation slightly, but that's better than a hanging script.
    8888
    8989Consider this data, notice how both `a` and `b` refer to each other:
     
    9999```
    100100
    101 If the final checkRecursive argument were missing, this would throw a "too much recursion" error.
    102 
    103 To enable supprot for this pass `true` as additional argument to any of the regular validation methods:
     101If the `checkRecursive` argument were missing, this would throw a "too much recursion" error.
     102
     103To enable support for this, pass `true` as additional argument to any of the regular validation methods:
    104104
    105105```javascript
    106106tv4.validate(a, aSchema, true);
    107 tv4.validate(a, schema, asynchronousFunction, true);
    108 
    109107tv4.validateResult(data, aSchema, true);
    110108tv4.validateMultiple(data, aSchema, true);
    111109```
    112110
     111## The `banUnknownProperties` flag
     112
     113Sometimes, it is desirable to flag all unknown properties as an error.  This is especially useful during development, to catch typos and the like, even when extra custom-defined properties are allowed.
     114
     115As such, tv4 implements ["ban unknown properties" mode](https://github.com/json-schema/json-schema/wiki/ban-unknown-properties-mode-\(v5-proposal\)), enabled by a fourth-argument flag:
     116
     117```javascript
     118tv4.validate(data, schema, checkRecursive, true);
     119tv4.validateResult(data, schema, checkRecursive, true);
     120tv4.validateMultiple(data, schema, checkRecursive, true);
     121```
     122
    113123## API
    114124
     
    229239##### addFormat(format, validationFunction)
    230240
    231 Add a custom format validator.
     241Add a custom format validator. (There are no built-in format validators.)
    232242
    233243* `format` is a string, corresponding to the `"format"` value in schemas.
  • Dev/trunk/src/node_modules/tv4/package.json

    r484 r487  
    11{
    22  "name": "tv4",
    3   "version": "1.0.11",
     3  "version": "1.0.16",
    44  "author": {
    55    "name": "Geraint Luff"
     
    5151    "grunt-markdown": "~0.3.0",
    5252    "grunt-component": "~0.1.4",
    53     "grunt-push-release": "~0.1.1"
     53    "grunt-push-release": "~0.1.1",
     54    "grunt-regex-replace": "~0.2.5"
    5455  },
    5556  "engines": {
     
    5758  },
    5859  "scripts": {
    59     "test": "grunt test"
     60    "test": "grunt test",
     61    "prepublish": "grunt prepublish"
    6062  },
    61   "readme": "# Tiny Validator (for v4 JSON Schema)\n\n[![Build Status](https://secure.travis-ci.org/geraintluff/tv4.png?branch=master)](http://travis-ci.org/geraintluff/tv4) [![Dependency Status](https://gemnasium.com/geraintluff/tv4.png)](https://gemnasium.com/geraintluff/tv4) [![NPM version](https://badge.fury.io/js/tv4.png)](http://badge.fury.io/js/tv4)\n\nUse [json-schema](http://json-schema.org/) [draft v4](http://json-schema.org/latest/json-schema-core.html) to validate simple values and complex objects using a rich [validation vocabulary](http://json-schema.org/latest/json-schema-validation.html) ([examples](http://json-schema.org/examples.html)).\n\nThere is support for `$ref` with JSON Pointer fragment paths (```other-schema.json#/properties/myKey```).\n\n## Usage 1: Simple validation\n\n```javascript\nvar valid = tv4.validate(data, schema);\n```\n\nIf validation returns ```false```, then an explanation of why validation failed can be found in ```tv4.error```.\n\nThe error object will look something like:\n```json\n{\n    \"code\": 0,\n    \"message\": \"Invalid type: string\",\n    \"dataPath\": \"/intKey\",\n    \"schemaKey\": \"/properties/intKey/type\"\n}\n```\n\nThe `\"code\"` property will refer to one of the values in `tv4.errorCodes` - in this case, `tv4.errorCodes.INVALID_TYPE`.\n\nTo enable external schema to be referenced, you use:\n```javascript\ntv4.addSchema(url, schema);\n```\n\nIf schemas are referenced (```$ref```) but not known, then validation will return ```true``` and the missing schema(s) will be listed in ```tv4.missing```. For more info see the API documentation below.\n\n## Usage 2: Multi-threaded validation\n\nStoring the error and missing schemas does not work well in multi-threaded environments, so there is an alternative syntax:\n\n```javascript\nvar result = tv4.validateResult(data, schema);\n```\n\nThe result will look something like:\n```json\n{\n    \"valid\": false,\n    \"error\": {...},\n    \"missing\": [...]\n}\n```\n\n## Usage 3: Multiple errors\n\nNormally, `tv4` stops when it encounters the first validation error.  However, you can collect an array of validation errors using:\n\n```javascript\nvar result = tv4.validateMultiple(data, schema);\n```\n\nThe result will look something like:\n```json\n{\n    \"valid\": false,\n    \"errors\": [\n        {...},\n        ...\n    ],\n    \"missing\": [...]\n}\n```\n\n## Asynchronous validation\n\nSupport for asynchronous validation (where missing schemas are fetched) can be added by including an extra JavaScript file.  Currently, the only version requires jQuery (`tv4.async-jquery.js`), but the code is very short and should be fairly easy to modify for other libraries (such as MooTools).\n\nUsage:\n\n```javascript\ntv4.validate(data, schema, function (isValid, validationError) { ... });\n```\n\n`validationFailure` is simply taken from `tv4.error`.\n\n## Cyclical JavaScript objects\n\nWhile they don't occur in proper JSON, JavaScript does support self-referencing objects. Any of the above calls support an optional final argument, checkRecursive. If true, tv4 will handle self-referencing objects properly - this slows down validation slightly, but that's better than a hanging script.\n\nConsider this data, notice how both `a` and `b` refer to each other:\n\n```javascript\nvar a = {};\nvar b = { a: a };\na.b = b;\nvar aSchema = { properties: { b: { $ref: 'bSchema' }}};\nvar bSchema = { properties: { a: { $ref: 'aSchema' }}};\ntv4.addSchema('aSchema', aSchema);\ntv4.addSchema('bSchema', bSchema);\n```\n\nIf the final checkRecursive argument were missing, this would throw a \"too much recursion\" error. \n\nTo enable supprot for this pass `true` as additional argument to any of the regular validation methods: \n\n```javascript\ntv4.validate(a, aSchema, true);\ntv4.validate(a, schema, asynchronousFunction, true);\n\ntv4.validateResult(data, aSchema, true); \ntv4.validateMultiple(data, aSchema, true);\n```\n\n## API\n\nThere are additional api commands available for more complex use-cases:\n\n##### addSchema(uri, schema)\nPre-register a schema for reference by other schema and synchronous validation.\n\n````js\ntv4.addSchema('http://example.com/schema', { ... });\n````\n\n* `uri` the uri to identify this schema.\n* `schema` the schema object.\n\nSchemas that have their `id` property set can be added directly.\n\n````js\ntv4.addSchema({ ... });\n````\n\n##### getSchema(uri)\n\nReturn a schema from the cache.\n\n* `uri` the uri of the schema (may contain a `#` fragment)\n\n````js\nvar schema = tv4.getSchema('http://example.com/schema');\n````\n\n##### getSchemaMap()\n\nReturn a shallow copy of the schema cache, mapping schema document URIs to schema objects.\n\n````\nvar map = tv4.getSchemaMap();\n\nvar schema = map[uri];\n````\n\n##### getSchemaUris(filter)\n\nReturn an Array with known schema document URIs.\n\n* `filter` optional RegExp to filter URIs\n\n````\nvar arr = tv4.getSchemaUris();\n\n// optional filter using a RegExp\nvar arr = tv4.getSchemaUris(/^https?://example.com/);\n````\n\n##### getMissingUris(filter)\n\nReturn an Array with schema document URIs that are used as `$ref` in known schemas but which currently have no associated schema data.\n\nUse this in combination with `tv4.addSchema(uri, schema)` to preload the cache for complete synchronous validation with.\n\n* `filter` optional RegExp to filter URIs\n\n````\nvar arr = tv4.getMissingUris();\n\n// optional filter using a RegExp\nvar arr = tv4.getMissingUris(/^https?://example.com/);\n````\n\n##### dropSchemas()\n\nDrop all known schema document URIs from the cache.\n\n````\ntv4.dropSchemas();\n````\n\n##### freshApi()\n\nReturn a new tv4 instance with no shared state.\n\n````\nvar otherTV4 = tv4.freshApi();\n````\n\n##### reset()\n\nManually reset validation status from the simple `tv4.validate(data, schema)`. Although tv4 will self reset on each validation there are some implementation scenarios where this is useful.\n\n````\ntv4.reset();\n````\n\n##### language(code)\n\nSelect the language map used for reporting.\n\n* `code` is a language code, like `'en'` or `'en-gb'`\n\n````\ntv4.language('en-gb');\n````\n\n##### addLanguage(code, map)\n\nAdd a new language map for selection by `tv4.language(code)`\n\n* `code` is new language code\n* `map` is an object mapping error IDs or constant names (e.g. `103` or `\"NUMBER_MAXIMUM\"`) to language strings.\n\n````\ntv4.addLanguage('fr', { ... });\n\n// select for use\ntv4.language('fr')\n````\n\n##### addFormat(format, validationFunction)\n\nAdd a custom format validator.\n\n* `format` is a string, corresponding to the `\"format\"` value in schemas.\n* `validationFunction` is a function that either returns:\n  * `null` (meaning no error)\n  * an error string (explaining the reason for failure)\n\n````\ntv4.addFormat('decimal-digits', function (data, schema) {\n\tif (typeof data === 'string' && !/^[0-9]+$/.test(data)) {\n\t\treturn null;\n\t}\n\treturn \"must be string of decimal digits\";\n});\n````\n\nAlternatively, multiple formats can be added at the same time using an object:\n````\ntv4.addFormat({\n\t'my-format': function () {...},\n\t'other-format': function () {...}\n});\n````\n\n## Demos\n\n### Basic usage\n<div class=\"content inline-demo\" markdown=\"1\" data-demo=\"demo1\">\n<pre class=\"code\" id=\"demo1\">\nvar schema = {\n\t\"items\": {\n\t\t\"type\": \"boolean\"\n\t}\n};\nvar data1 = [true, false];\nvar data2 = [true, 123];\n\nalert(\"data 1: \" + tv4.validate(data1, schema)); // true\nalert(\"data 2: \" + tv4.validate(data2, schema)); // false\nalert(\"data 2 error: \" + JSON.stringify(tv4.error, null, 4));\n</pre>\n</div>\n\n### Use of <code>$ref</code>\n<div class=\"content inline-demo\" markdown=\"1\" data-demo=\"demo2\">\n<pre class=\"code\" id=\"demo2\">\nvar schema = {\n\t\"type\": \"array\",\n\t\"items\": {\"$ref\": \"#\"}\n};\nvar data1 = [[], [[]]];\nvar data2 = [[], [true, []]];\n\nalert(\"data 1: \" + tv4.validate(data1, schema)); // true\nalert(\"data 2: \" + tv4.validate(data2, schema)); // false\n</pre>\n</div>\n\n### Missing schema\n<div class=\"content inline-demo\" markdown=\"1\" data-demo=\"demo3\">\n<pre class=\"code\" id=\"demo3\">\nvar schema = {\n\t\"type\": \"array\",\n\t\"items\": {\"$ref\": \"http://example.com/schema\" }\n};\nvar data = [1, 2, 3];\n\nalert(\"Valid: \" + tv4.validate(data, schema)); // true\nalert(\"Missing schemas: \" + JSON.stringify(tv4.missing));\n</pre>\n</div>\n\n### Referencing remote schema\n<div class=\"content inline-demo\" markdown=\"1\" data-demo=\"demo4\">\n<pre class=\"code\" id=\"demo4\">\ntv4.addSchema(\"http://example.com/schema\", {\n\t\"definitions\": {\n\t\t\"arrayItem\": {\"type\": \"boolean\"}\n\t}\n});\nvar schema = {\n\t\"type\": \"array\",\n\t\"items\": {\"$ref\": \"http://example.com/schema#/definitions/arrayItem\" }\n};\nvar data1 = [true, false, true];\nvar data2 = [1, 2, 3];\n\nalert(\"data 1: \" + tv4.validate(data1, schema)); // true\nalert(\"data 2: \" + tv4.validate(data2, schema)); // false\n</pre>\n</div>\n\n## Supported platforms\n\n* Node.js\n* All modern browsers\n* IE >= 7\n\n## Installation\n\nYou can manually download [`tv4.js`](https://raw.github.com/geraintluff/tv4/master/tv4.js) or the minified [`tv4.min.js`](https://raw.github.com/geraintluff/tv4/master/tv4.min.js) and include it in your html to create the global `tv4` variable.\n\nAlternately use it as a CommonJS module:\n\n````js\nvar tv4 = require('tv4');\n````\n\n#### npm\n\n````\n$ npm install tv4\n````\n\n#### bower\n\n````\n$ bower install tv4\n````\n\n#### component.io\n\n````\n$ component install geraintluff/tv4\n````\n\n## Build and test\n\nYou can rebuild and run the node and browser tests using node.js and [grunt](http://http://gruntjs.com/):\n\nMake sure you have the global grunt cli command:\n````\n$ npm install grunt-cli -g\n````\n\nClone the git repos, open a shell in the root folder and install the development dependencies:\n\n````\n$ npm install\n````\n\nRebuild and run the tests:\n````\n$ grunt\n````\n\nIt will run a build and display one Spec-style report for the node.js and two Dot-style reports for both the plain and minified browser tests (via phantomJS). You can also use your own browser to manually run the suites by opening [`test/index.html`](http://geraintluff.github.io/tv4/test/index.html) and [`test/index-min.html`](http://geraintluff.github.io/tv4/test/index-min.html).\n\n## Contributing\n\nPull-requests for fixes and expansions are welcome. Edit the partial files in `/source` and add your tests in a suitable suite or folder under `/test/tests` and run `grunt` to rebuild and run the test suite. Try to maintain an idiomatic coding style and add tests for any new features. It is recommend to discuss big changes in an Issue.\n\n## Packages using tv4\n\n* [chai-json-schema](http://chaijs.com/plugins/chai-json-schema) is a [Chai Assertion Library](http://chaijs.com) plugin to assert values against json-schema.\n* [grunt-tv4](http://www.github.com/Bartvds/grunt-tv4) is a plugin for [Grunt](http://http://gruntjs.com/) that uses tv4 to bulk validate json files.\n\n## License\n\nThe code is available as \"public domain\", meaning that it is completely free to use, without any restrictions at all.  Read the full license [here](http://geraintluff.github.com/tv4/LICENSE.txt).\n\nIt's also available under an [MIT license](http://jsonary.com/LICENSE.txt).\n",
     63  "readme": "# Tiny Validator (for v4 JSON Schema)\n\n[![Build Status](https://secure.travis-ci.org/geraintluff/tv4.png?branch=master)](http://travis-ci.org/geraintluff/tv4) [![Dependency Status](https://gemnasium.com/geraintluff/tv4.png)](https://gemnasium.com/geraintluff/tv4) [![NPM version](https://badge.fury.io/js/tv4.png)](http://badge.fury.io/js/tv4)\n\nUse [json-schema](http://json-schema.org/) [draft v4](http://json-schema.org/latest/json-schema-core.html) to validate simple values and complex objects using a rich [validation vocabulary](http://json-schema.org/latest/json-schema-validation.html) ([examples](http://json-schema.org/examples.html)).\n\nThere is support for `$ref` with JSON Pointer fragment paths (```other-schema.json#/properties/myKey```).\n\n## Usage 1: Simple validation\n\n```javascript\nvar valid = tv4.validate(data, schema);\n```\n\nIf validation returns ```false```, then an explanation of why validation failed can be found in ```tv4.error```.\n\nThe error object will look something like:\n```json\n{\n    \"code\": 0,\n    \"message\": \"Invalid type: string\",\n    \"dataPath\": \"/intKey\",\n    \"schemaKey\": \"/properties/intKey/type\"\n}\n```\n\nThe `\"code\"` property will refer to one of the values in `tv4.errorCodes` - in this case, `tv4.errorCodes.INVALID_TYPE`.\n\nTo enable external schema to be referenced, you use:\n```javascript\ntv4.addSchema(url, schema);\n```\n\nIf schemas are referenced (```$ref```) but not known, then validation will return ```true``` and the missing schema(s) will be listed in ```tv4.missing```. For more info see the API documentation below.\n\n## Usage 2: Multi-threaded validation\n\nStoring the error and missing schemas does not work well in multi-threaded environments, so there is an alternative syntax:\n\n```javascript\nvar result = tv4.validateResult(data, schema);\n```\n\nThe result will look something like:\n```json\n{\n    \"valid\": false,\n    \"error\": {...},\n    \"missing\": [...]\n}\n```\n\n## Usage 3: Multiple errors\n\nNormally, `tv4` stops when it encounters the first validation error.  However, you can collect an array of validation errors using:\n\n```javascript\nvar result = tv4.validateMultiple(data, schema);\n```\n\nThe result will look something like:\n```json\n{\n    \"valid\": false,\n    \"errors\": [\n        {...},\n        ...\n    ],\n    \"missing\": [...]\n}\n```\n\n## Asynchronous validation\n\nSupport for asynchronous validation (where missing schemas are fetched) can be added by including an extra JavaScript file.  Currently, the only version requires jQuery (`tv4.async-jquery.js`), but the code is very short and should be fairly easy to modify for other libraries (such as MooTools).\n\nUsage:\n\n```javascript\ntv4.validate(data, schema, function (isValid, validationError) { ... });\n```\n\n`validationFailure` is simply taken from `tv4.error`.\n\n## Cyclical JavaScript objects\n\nWhile they don't occur in proper JSON, JavaScript does support self-referencing objects. Any of the above calls support an optional third argument: `checkRecursive`. If true, tv4 will handle self-referencing objects properly - this slows down validation slightly, but that's better than a hanging script.\n\nConsider this data, notice how both `a` and `b` refer to each other:\n\n```javascript\nvar a = {};\nvar b = { a: a };\na.b = b;\nvar aSchema = { properties: { b: { $ref: 'bSchema' }}};\nvar bSchema = { properties: { a: { $ref: 'aSchema' }}};\ntv4.addSchema('aSchema', aSchema);\ntv4.addSchema('bSchema', bSchema);\n```\n\nIf the `checkRecursive` argument were missing, this would throw a \"too much recursion\" error. \n\nTo enable support for this, pass `true` as additional argument to any of the regular validation methods: \n\n```javascript\ntv4.validate(a, aSchema, true);\ntv4.validateResult(data, aSchema, true); \ntv4.validateMultiple(data, aSchema, true);\n```\n\n## The `banUnknownProperties` flag\n\nSometimes, it is desirable to flag all unknown properties as an error.  This is especially useful during development, to catch typos and the like, even when extra custom-defined properties are allowed.\n\nAs such, tv4 implements [\"ban unknown properties\" mode](https://github.com/json-schema/json-schema/wiki/ban-unknown-properties-mode-\\(v5-proposal\\)), enabled by a fourth-argument flag:\n\n```javascript\ntv4.validate(data, schema, checkRecursive, true);\ntv4.validateResult(data, schema, checkRecursive, true);\ntv4.validateMultiple(data, schema, checkRecursive, true);\n```\n\n## API\n\nThere are additional api commands available for more complex use-cases:\n\n##### addSchema(uri, schema)\nPre-register a schema for reference by other schema and synchronous validation.\n\n````js\ntv4.addSchema('http://example.com/schema', { ... });\n````\n\n* `uri` the uri to identify this schema.\n* `schema` the schema object.\n\nSchemas that have their `id` property set can be added directly.\n\n````js\ntv4.addSchema({ ... });\n````\n\n##### getSchema(uri)\n\nReturn a schema from the cache.\n\n* `uri` the uri of the schema (may contain a `#` fragment)\n\n````js\nvar schema = tv4.getSchema('http://example.com/schema');\n````\n\n##### getSchemaMap()\n\nReturn a shallow copy of the schema cache, mapping schema document URIs to schema objects.\n\n````\nvar map = tv4.getSchemaMap();\n\nvar schema = map[uri];\n````\n\n##### getSchemaUris(filter)\n\nReturn an Array with known schema document URIs.\n\n* `filter` optional RegExp to filter URIs\n\n````\nvar arr = tv4.getSchemaUris();\n\n// optional filter using a RegExp\nvar arr = tv4.getSchemaUris(/^https?://example.com/);\n````\n\n##### getMissingUris(filter)\n\nReturn an Array with schema document URIs that are used as `$ref` in known schemas but which currently have no associated schema data.\n\nUse this in combination with `tv4.addSchema(uri, schema)` to preload the cache for complete synchronous validation with.\n\n* `filter` optional RegExp to filter URIs\n\n````\nvar arr = tv4.getMissingUris();\n\n// optional filter using a RegExp\nvar arr = tv4.getMissingUris(/^https?://example.com/);\n````\n\n##### dropSchemas()\n\nDrop all known schema document URIs from the cache.\n\n````\ntv4.dropSchemas();\n````\n\n##### freshApi()\n\nReturn a new tv4 instance with no shared state.\n\n````\nvar otherTV4 = tv4.freshApi();\n````\n\n##### reset()\n\nManually reset validation status from the simple `tv4.validate(data, schema)`. Although tv4 will self reset on each validation there are some implementation scenarios where this is useful.\n\n````\ntv4.reset();\n````\n\n##### language(code)\n\nSelect the language map used for reporting.\n\n* `code` is a language code, like `'en'` or `'en-gb'`\n\n````\ntv4.language('en-gb');\n````\n\n##### addLanguage(code, map)\n\nAdd a new language map for selection by `tv4.language(code)`\n\n* `code` is new language code\n* `map` is an object mapping error IDs or constant names (e.g. `103` or `\"NUMBER_MAXIMUM\"`) to language strings.\n\n````\ntv4.addLanguage('fr', { ... });\n\n// select for use\ntv4.language('fr')\n````\n\n##### addFormat(format, validationFunction)\n\nAdd a custom format validator. (There are no built-in format validators.)\n\n* `format` is a string, corresponding to the `\"format\"` value in schemas.\n* `validationFunction` is a function that either returns:\n  * `null` (meaning no error)\n  * an error string (explaining the reason for failure)\n\n````\ntv4.addFormat('decimal-digits', function (data, schema) {\n\tif (typeof data === 'string' && !/^[0-9]+$/.test(data)) {\n\t\treturn null;\n\t}\n\treturn \"must be string of decimal digits\";\n});\n````\n\nAlternatively, multiple formats can be added at the same time using an object:\n````\ntv4.addFormat({\n\t'my-format': function () {...},\n\t'other-format': function () {...}\n});\n````\n\n## Demos\n\n### Basic usage\n<div class=\"content inline-demo\" markdown=\"1\" data-demo=\"demo1\">\n<pre class=\"code\" id=\"demo1\">\nvar schema = {\n\t\"items\": {\n\t\t\"type\": \"boolean\"\n\t}\n};\nvar data1 = [true, false];\nvar data2 = [true, 123];\n\nalert(\"data 1: \" + tv4.validate(data1, schema)); // true\nalert(\"data 2: \" + tv4.validate(data2, schema)); // false\nalert(\"data 2 error: \" + JSON.stringify(tv4.error, null, 4));\n</pre>\n</div>\n\n### Use of <code>$ref</code>\n<div class=\"content inline-demo\" markdown=\"1\" data-demo=\"demo2\">\n<pre class=\"code\" id=\"demo2\">\nvar schema = {\n\t\"type\": \"array\",\n\t\"items\": {\"$ref\": \"#\"}\n};\nvar data1 = [[], [[]]];\nvar data2 = [[], [true, []]];\n\nalert(\"data 1: \" + tv4.validate(data1, schema)); // true\nalert(\"data 2: \" + tv4.validate(data2, schema)); // false\n</pre>\n</div>\n\n### Missing schema\n<div class=\"content inline-demo\" markdown=\"1\" data-demo=\"demo3\">\n<pre class=\"code\" id=\"demo3\">\nvar schema = {\n\t\"type\": \"array\",\n\t\"items\": {\"$ref\": \"http://example.com/schema\" }\n};\nvar data = [1, 2, 3];\n\nalert(\"Valid: \" + tv4.validate(data, schema)); // true\nalert(\"Missing schemas: \" + JSON.stringify(tv4.missing));\n</pre>\n</div>\n\n### Referencing remote schema\n<div class=\"content inline-demo\" markdown=\"1\" data-demo=\"demo4\">\n<pre class=\"code\" id=\"demo4\">\ntv4.addSchema(\"http://example.com/schema\", {\n\t\"definitions\": {\n\t\t\"arrayItem\": {\"type\": \"boolean\"}\n\t}\n});\nvar schema = {\n\t\"type\": \"array\",\n\t\"items\": {\"$ref\": \"http://example.com/schema#/definitions/arrayItem\" }\n};\nvar data1 = [true, false, true];\nvar data2 = [1, 2, 3];\n\nalert(\"data 1: \" + tv4.validate(data1, schema)); // true\nalert(\"data 2: \" + tv4.validate(data2, schema)); // false\n</pre>\n</div>\n\n## Supported platforms\n\n* Node.js\n* All modern browsers\n* IE >= 7\n\n## Installation\n\nYou can manually download [`tv4.js`](https://raw.github.com/geraintluff/tv4/master/tv4.js) or the minified [`tv4.min.js`](https://raw.github.com/geraintluff/tv4/master/tv4.min.js) and include it in your html to create the global `tv4` variable.\n\nAlternately use it as a CommonJS module:\n\n````js\nvar tv4 = require('tv4');\n````\n\n#### npm\n\n````\n$ npm install tv4\n````\n\n#### bower\n\n````\n$ bower install tv4\n````\n\n#### component.io\n\n````\n$ component install geraintluff/tv4\n````\n\n## Build and test\n\nYou can rebuild and run the node and browser tests using node.js and [grunt](http://http://gruntjs.com/):\n\nMake sure you have the global grunt cli command:\n````\n$ npm install grunt-cli -g\n````\n\nClone the git repos, open a shell in the root folder and install the development dependencies:\n\n````\n$ npm install\n````\n\nRebuild and run the tests:\n````\n$ grunt\n````\n\nIt will run a build and display one Spec-style report for the node.js and two Dot-style reports for both the plain and minified browser tests (via phantomJS). You can also use your own browser to manually run the suites by opening [`test/index.html`](http://geraintluff.github.io/tv4/test/index.html) and [`test/index-min.html`](http://geraintluff.github.io/tv4/test/index-min.html).\n\n## Contributing\n\nPull-requests for fixes and expansions are welcome. Edit the partial files in `/source` and add your tests in a suitable suite or folder under `/test/tests` and run `grunt` to rebuild and run the test suite. Try to maintain an idiomatic coding style and add tests for any new features. It is recommend to discuss big changes in an Issue.\n\n## Packages using tv4\n\n* [chai-json-schema](http://chaijs.com/plugins/chai-json-schema) is a [Chai Assertion Library](http://chaijs.com) plugin to assert values against json-schema.\n* [grunt-tv4](http://www.github.com/Bartvds/grunt-tv4) is a plugin for [Grunt](http://http://gruntjs.com/) that uses tv4 to bulk validate json files.\n\n## License\n\nThe code is available as \"public domain\", meaning that it is completely free to use, without any restrictions at all.  Read the full license [here](http://geraintluff.github.com/tv4/LICENSE.txt).\n\nIt's also available under an [MIT license](http://jsonary.com/LICENSE.txt).\n",
    6264  "readmeFilename": "README.md",
    6365  "bugs": {
     
    6567  },
    6668  "homepage": "https://github.com/geraintluff/tv4",
    67   "_id": "tv4@1.0.11",
    68   "_from": "tv4@"
     69  "_id": "tv4@1.0.16",
     70  "dist": {
     71    "shasum": "f35372c01e94355b7aaff5860455fd231e2d9802"
     72  },
     73  "_from": "tv4@1.0.16",
     74  "_resolved": "https://registry.npmjs.org/tv4/-/tv4-1.0.16.tgz"
    6975}
  • Dev/trunk/src/node_modules/tv4/tv4.async-jquery.js

    r484 r487  
    44if (typeof (tv4.asyncValidate) === 'undefined') {
    55        tv4.syncValidate = tv4.validate;
    6         tv4.validate = function (data, schema, callback, checkRecursive) {
     6        tv4.validate = function (data, schema, callback, checkRecursive, banUnknownProperties) {
    77                if (typeof (callback) === 'undefined') {
    8                         return this.syncValidate(data, schema, checkRecursive);
     8                        return this.syncValidate(data, schema, checkRecursive, banUnknownProperties);
    99                } else {
    10                         return this.asyncValidate(data, schema, callback, checkRecursive);
     10                        return this.asyncValidate(data, schema, callback, checkRecursive, banUnknownProperties);
    1111                }
    1212        };
    13         tv4.asyncValidate = function (data, schema, callback, checkRecursive) {
     13        tv4.asyncValidate = function (data, schema, callback, checkRecursive, banUnknownProperties) {
    1414                var $ = jQuery;
    15                 var result = tv4.validate(data, schema, checkRecursive);
     15                var result = tv4.validate(data, schema, checkRecursive, banUnknownProperties);
    1616                if (!tv4.missing.length) {
    1717                        callback(result, tv4.error);
     
    2828                        // When all requests done, try again
    2929                        $.when.apply($, missingSchemas).done(function () {
    30                                 var result = tv4.asyncValidate(data, schema, callback, checkRecursive);
     30                                var result = tv4.asyncValidate(data, schema, callback, checkRecursive, banUnknownProperties);
    3131                        });
    3232                }
  • Dev/trunk/src/node_modules/tv4/tv4.js

    r484 r487  
    133133                this.scannedFrozen = [];
    134134                this.scannedFrozenSchemas = [];
    135                 this.key = 'tv4_validation_id';
     135                this.scannedFrozenValidationErrors = [];
     136                this.validatedSchemasKey = 'tv4_validation_id';
     137                this.validationErrorsKey = 'tv4_validation_errors_id';
    136138        }
    137139        if (trackUnknownProperties) {
     
    240242};
    241243ValidatorContext.prototype.searchSchemas = function (schema, url) {
    242         if (typeof schema.id === "string") {
    243                 if (isTrustedUrl(url, schema.id)) {
    244                         if (this.schemas[schema.id] === undefined) {
    245                                 this.schemas[schema.id] = schema;
    246                         }
    247                 }
    248         }
    249         if (typeof schema === "object") {
     244        if (schema && typeof schema === "object") {
     245                if (typeof schema.id === "string") {
     246                        if (isTrustedUrl(url, schema.id)) {
     247                                if (this.schemas[schema.id] === undefined) {
     248                                        this.schemas[schema.id] = schema;
     249                                }
     250                        }
     251                }
    250252                for (var key in schema) {
    251253                        if (key !== "enum") {
     
    264266ValidatorContext.prototype.addSchema = function (url, schema) {
    265267        //overload
    266         if (typeof schema === 'undefined') {
     268        if (typeof url !== 'string' || typeof schema === 'undefined') {
    267269                if (typeof url === 'object' && typeof url.id === 'string') {
    268270                        schema = url;
     
    331333        }
    332334
    333         if (this.checkRecursive && (typeof data) === 'object') {
     335        var startErrorCount = this.errors.length;
     336        var frozenIndex, scannedFrozenSchemaIndex = null, scannedSchemasIndex = null;
     337        if (this.checkRecursive && data && typeof data === 'object') {
    334338                topLevel = !this.scanned.length;
    335                 if (data[this.key] && data[this.key].indexOf(schema) !== -1) { return null; }
    336                 var frozenIndex;
     339                if (data[this.validatedSchemasKey]) {
     340                        var schemaIndex = data[this.validatedSchemasKey].indexOf(schema);
     341                        if (schemaIndex !== -1) {
     342                                this.errors = this.errors.concat(data[this.validationErrorsKey][schemaIndex]);
     343                                return null;
     344                        }
     345                }
    337346                if (Object.isFrozen(data)) {
    338347                        frozenIndex = this.scannedFrozen.indexOf(data);
    339                         if (frozenIndex !== -1 && this.scannedFrozenSchemas[frozenIndex].indexOf(schema) !== -1) { return null; }
     348                        if (frozenIndex !== -1) {
     349                                var frozenSchemaIndex = this.scannedFrozenSchemas[frozenIndex].indexOf(schema);
     350                                if (frozenSchemaIndex !== -1) {
     351                                        this.errors = this.errors.concat(this.scannedFrozenValidationErrors[frozenIndex][frozenSchemaIndex]);
     352                                        return null;
     353                                }
     354                        }
    340355                }
    341356                this.scanned.push(data);
     
    346361                                this.scannedFrozenSchemas.push([]);
    347362                        }
    348                         this.scannedFrozenSchemas[frozenIndex].push(schema);
     363                        scannedFrozenSchemaIndex = this.scannedFrozenSchemas[frozenIndex].length;
     364                        this.scannedFrozenSchemas[frozenIndex][scannedFrozenSchemaIndex] = schema;
     365                        this.scannedFrozenValidationErrors[frozenIndex][scannedFrozenSchemaIndex] = [];
    349366                } else {
    350                         if (!data[this.key]) {
     367                        if (!data[this.validatedSchemasKey]) {
    351368                                try {
    352                                         Object.defineProperty(data, this.key, {
     369                                        Object.defineProperty(data, this.validatedSchemasKey, {
     370                                                value: [],
     371                                                configurable: true
     372                                        });
     373                                        Object.defineProperty(data, this.validationErrorsKey, {
    353374                                                value: [],
    354375                                                configurable: true
     
    356377                                } catch (e) {
    357378                                        //IE 7/8 workaround
    358                                         data[this.key] = [];
    359                                 }
    360                         }
    361                         data[this.key].push(schema);
     379                                        data[this.validatedSchemasKey] = [];
     380                                        data[this.validationErrorsKey] = [];
     381                                }
     382                        }
     383                        scannedSchemasIndex = data[this.validatedSchemasKey].length;
     384                        data[this.validatedSchemasKey][scannedSchemasIndex] = schema;
     385                        data[this.validationErrorsKey][scannedSchemasIndex] = [];
    362386                }
    363387        }
     
    376400                while (this.scanned.length) {
    377401                        var item = this.scanned.pop();
    378                         delete item[this.key];
     402                        delete item[this.validatedSchemasKey];
    379403                }
    380404                this.scannedFrozen = [];
     
    391415                        this.prefixErrors(errorCount, dataPart, schemaPart);
    392416                }
     417        }
     418       
     419        if (scannedFrozenSchemaIndex !== null) {
     420                this.scannedFrozenValidationErrors[frozenIndex][scannedFrozenSchemaIndex] = this.errors.slice(startErrorCount);
     421        } else if (scannedSchemasIndex !== null) {
     422                data[this.validationErrorsKey][scannedSchemasIndex] = this.errors.slice(startErrorCount);
    393423        }
    394424
     
    9961026}
    9971027function normSchema(schema, baseUri) {
    998         if (baseUri === undefined) {
    999                 baseUri = schema.id;
    1000         } else if (typeof schema.id === "string") {
    1001                 baseUri = resolveUrl(baseUri, schema.id);
    1002                 schema.id = baseUri;
    1003         }
    1004         if (typeof schema === "object") {
     1028        if (schema && typeof schema === "object") {
     1029                if (baseUri === undefined) {
     1030                        baseUri = schema.id;
     1031                } else if (typeof schema.id === "string") {
     1032                        baseUri = resolveUrl(baseUri, schema.id);
     1033                        schema.id = baseUri;
     1034                }
    10051035                if (Array.isArray(schema)) {
    10061036                        for (var i = 0; i < schema.length; i++) {
     
    10501080        FORMAT_CUSTOM: 500,
    10511081        // Schema structure
    1052         CIRCULAR_REFERENCE: 500,
     1082        CIRCULAR_REFERENCE: 600,
    10531083        // Non-standard validation options
    10541084        UNKNOWN_PROPERTY: 1000
     
    10911121
    10921122function ValidationError(code, message, dataPath, schemaPath, subErrors) {
     1123        Error.call(this);
    10931124        if (code === undefined) {
    10941125                throw new Error ("No code supplied for error: "+ message);
    10951126        }
     1127        this.message = message;
    10961128        this.code = code;
    1097         this.message = message;
    10981129        this.dataPath = dataPath || "";
    10991130        this.schemaPath = schemaPath || "";
    11001131        this.subErrors = subErrors || null;
    1101 }
    1102 ValidationError.prototype = new Error();
     1132
     1133        var err = new Error(this.message);
     1134        this.stack = err.stack || err.stacktrace;
     1135        if (!this.stack) {
     1136                try {
     1137                        throw err;
     1138                }
     1139                catch(err) {
     1140                        this.stack = err.stack || err.stacktrace;
     1141                }
     1142        }
     1143}
     1144ValidationError.prototype = Object.create(Error.prototype);
     1145ValidationError.prototype.constructor = ValidationError;
     1146ValidationError.prototype.name = 'ValidationError';
     1147
    11031148ValidationError.prototype.prefixWith = function (dataPrefix, schemaPrefix) {
    11041149        if (dataPrefix !== null) {
     
    12651310
    12661311})(this);
    1267 
    1268 //@ sourceMappingURL=tv4.js.map
  • Dev/trunk/src/package.json

    r479 r487  
    44  "description": "QED Server",
    55  "dependencies": {
    6     "express": "~3.2.3",
     6    "express": "~3.2.6",
    77    "underscore": "~1.4.4",
    88    "passport": "~0.1.17",
     
    1111    "request": "~2.21.0",
    1212    "ya-csv": "~0.9.2",
    13     "tv4": "~1.0.11"
     13    "tv4": "~1.0.16"
    1414  },
    1515  "engines": {
  • Dev/trunk/src/server/app.js

    r481 r487  
    99  , _ = require("underscore")
    1010  , tv4 = require("tv4")
     11  , HTTPResult = require("./util/http-result")
     12  , etags = require("./util/etags")
     13  , cryptoken = require('./util/crypto-token')
    1114  ;
    1215
     
    2225exports.App = function(settings) {
    2326
    24     assertSetting("couchDbURL", settings, _.isString);
    25     var couch = new CouchDB(settings.couchDbURL);
     27    assertSetting("couchServerURL", settings, _.isString);
     28    assertSetting("dbName", settings, _.isString);
     29    var couch = new CouchDB(settings.couchServerURL,settings.dbName);
    2630   
    2731    var schema = require("./config/couchdb-schema.json");
     
    6973    app.use(passport.initialize());
    7074    app.use(passport.session());
     75   
     76    // various middlewares
    7177    function ensureAuthenticated(req,res,next){
    7278        if (!req.user) {
     
    7884    function returnUser(req,res) {
    7985        res.send(200, req.user);
     86    }
     87    function notImplemented(req,res) {
     88        res.send(501,{error:"API not implemented yet."});
    8089    }
    8190
     
    112121        });
    113122
     123    var JSON_MIME = 'application/json';
     124    var CSV_MIME = 'application/json';
     125    function ensureMIME(mimeType) {
     126        return function(req,res,next) {
     127            if (!req.accepts(mimeType)) {
     128                res.send(406);
     129            } else {
     130                res.set({
     131                    'Content-Type': mimeType
     132                });
     133                next();
     134            }
     135        };
     136    }
     137   
     138    function stripAndReturnPrivates(obj) {
     139        var priv = {};
     140        _.each(obj||{},function(val,key){
     141            if (key.substring(0,1) === '_') {
     142                priv[key] = val;
     143                delete obj[key];
     144            }
     145        });
     146        return priv;
     147    }
     148
     149    function identity(obj) { return obj; }
     150    function handleUnknownResponse(status,error){
     151        return new HTTPResult(500,{error: "Unexpected database response",
     152                                   innerStatus: status, innerResponse: error});
     153    }
     154    function handleUnknownError(error){
     155        return new HTTPResult(500, {error: "Unknown error", innerError: error});
     156    }
     157    function handleRowValues(result) {
     158        return _.map(result.rows, function(item) { return item.value; });
     159    }
     160    function handleRowDocs(result) {
     161        return _.map(result.rows, function(item) { return item.doc; });
     162    }
     163
     164    function getDocumentsOfType (type) {
     165        var url = '_design/default/_view/by_type?key='+JSON.stringify(type);
     166        return HTTPResult.fromResponsePromise(couch.get(url).response,
     167                                              handleUnknownError)
     168        .handle({
     169            200: handleRowValues,
     170            404: function() { return {error: "Cannot find collection of type "+type}; },
     171            default: handleUnknownResponse
     172        });
     173    }
     174    function getDocument(id,rev,type) {
     175        var opts = {headers:{}};
     176        if (rev) {
     177            opts.headers['If-Non-Match'] = '"'+rev+'"';
     178        }
     179        return HTTPResult.fromResponsePromise(couch.get(id,opts).response,
     180                                              handleUnknownError)
     181        .handle({
     182            200: function(doc){
     183                if ( doc.type !== type ) {
     184                    return new HTTPResult(404,{error:"Document not found."});
     185                } else {
     186                    var priv = stripAndReturnPrivates(doc);
     187                    if ( priv._rev !== rev ) {
     188                        doc._id = priv._id;
     189                        doc._rev = priv._rev;
     190                        return doc;
     191                    } else {
     192                        return new HTTPResult(304);
     193                    }
     194                }
     195            },
     196            304: identity,
     197            default: handleUnknownResponse
     198        });
     199    }
     200    function putDocument(id,rev,type,doc) {
     201        var priv = stripAndReturnPrivates(doc);
     202        if ( doc.type === type && tv4.validateResult(doc, schema) ) {
     203            var opts = rev ? {headers:{'If-Match':'"'+rev+'"'}} : {};
     204            return HTTPResult.fromResponsePromise(couch.put(id,doc,opts).response,
     205                                                  handleUnknownError)
     206            .handle({
     207                201: function(res){
     208                    doc._id = res.id;
     209                    doc._rev = res.rev;
     210                    return doc;
     211                },
     212                409: function(error) {
     213                    return {error: error.reason};
     214                },
     215                default: handleUnknownResponse
     216            });
     217        } else {
     218            return new HTTPResult(400,{error: "Document failed schema verification."});
     219        }
     220    }
     221    function deleteDocument(id,rev) {
     222        if ( rev ) {
     223            var opts = {headers:{'If-Match':'"'+rev+'"'}};
     224            return HTTPResult.fromResponsePromise(couch.delete(id,opts).response,
     225                                                  handleUnknownError)
     226            .handle({
     227                200: identity,
     228                409: function(error) {
     229                    return {error: error.reason};
     230                },
     231                default: handleUnknownResponse
     232            });
     233        } else {
     234            return new HTTPResult(409, {error: "Cannot identify document revision to delete."});
     235        }
     236    }
     237    function postDocument(type,doc) {
     238        var priv = stripAndReturnPrivates(doc);
     239        if ( doc.type === type && tv4.validateResult(doc, schema) ) {
     240            return HTTPResult.fromResponsePromise(couch.post(doc).response,
     241                                                  handleUnknownError)
     242            .handle({
     243                201: function(response) {
     244                    doc._id = response.id;
     245                    doc._rev = response.rev;
     246                    return doc;
     247                },
     248                default: handleUnknownResponse
     249            });
     250        } else {
     251            return new HTTPResult(400,{error: "Document failed schema verification."});
     252        }
     253    }
     254
    114255    function makeDocsGet(type) {
    115256        return function(req,res) {
    116             var url = '_design/default/_view/by_type?key='+JSON.stringify(type);
    117             couch.get(url).then(function(result){
    118                 var items = _.map(result.rows, function(item) { return item.value; });
    119                 res.send(200, items);
    120             }, function(error){
    121                 res.send(404, {error: "Cannot find collection"});
     257            getDocumentsOfType(type)
     258            .handle(res.send.bind(res));
     259        };
     260    }
     261    function makeDocGet_id(type) {
     262        return function(req,res) {
     263            var id = req.params.id;
     264            var rev = etags.parse(req.header('If-Non-Match'))[0];
     265            getDocument(id,rev,type)
     266            .handle({
     267                200: function(doc){
     268                    res.set({
     269                        'ETag': etags.format([doc._rev])
     270                    }).send(200, doc);
     271                },
     272                default: res.send.bind(res)
    122273            });
    123274        };
    124275    }
    125     function makeDocGet_id(type) {
     276    function makeDocPut_id(type) {
    126277        return function(req,res) {
    127278            var id = req.params.id;
    128             couch.get(id).then(function(result){
    129                 if ( result.type === type ) {
    130                     res.send(200, result);
    131                 } else {
    132                     res.send(404, {error: "Document "+id+" is not a "+type});
    133                 }
    134             }, function(error){
    135                 res.send(404, {error: "Cannot find survey run "+id});
     279            var doc = req.body;
     280            var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
     281            putDocument(id,rev,type,doc)
     282            .handle({
     283                201: function(doc) {
     284                    res.set({
     285                        'ETag': etags.format([doc._rev])
     286                    }).send(201, doc);
     287                },
     288                default: res.send.bind(res)
    136289            });
    137290        };
    138291    }
    139     function makeDocPut_id(type) {
     292    function makeDocDel_id(type) {
    140293        return function(req,res) {
    141294            var id = req.params.id;
    142295            var doc = req.body;
    143             if ( doc.type === type && tv4.validateResult(doc, schema) ) {
    144                 couch.put(id,doc).then(function(result){
    145                     res.send(200, result);
    146                 }, function(error){
    147                     res.send(500, error);
     296            var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
     297            deleteDocument(id,rev)
     298            .handle(res.send.bind(res));
     299        };
     300    }
     301    function makeDocPost(type) {
     302        return function(req,res) {
     303            var doc = req.body;
     304            postDocument(type,doc)
     305            .handle({
     306                201: function(doc) {
     307                    res.set({
     308                        'ETag': etags.format([doc._rev])
     309                    }).send(201, doc);
     310                },
     311                default: res.send.bind(res)
     312            });
     313        };
     314    }
     315
     316    // Questions
     317    function getQuestionsWithCode(code) {
     318        var url = '_design/questions/_view/by_code';
     319        var query = {include_docs:true,key:code};
     320        return HTTPResult.fromResponsePromise(couch.get(url,{query:query}).response,
     321                                              handleUnknownError)
     322        .handle({
     323            200: handleRowDocs,
     324            default: handleUnknownResponse
     325        });
     326    }
     327    function getQuestionsWithTopic(topic) {
     328        var url = '_design/questions/_view/all_topics';
     329        var query = {reduce:false,include_docs:true,key:topic};
     330        return HTTPResult.fromResponsePromise(couch.get(url,{query:query}).response,
     331                                              handleUnknownError)
     332        .handle({
     333            200: handleRowDocs,
     334            default: handleUnknownResponse
     335        });
     336    }
     337    function getQuestionsWithCategoryAndTopic(category,topic) {
     338        var hasTopic = typeof topic !== 'undefined';
     339        var url = '_design/questions/_view/all';
     340        var query = {reduce:false,include_docs:true,
     341                     startkey:hasTopic?[category,topic]:[category],
     342                     endkey:hasTopic?[category,topic]:[category,{}]};
     343        return HTTPResult.fromResponsePromise(couch.get(url,{query:query}).response,
     344                                              handleUnknownError)
     345        .handle({
     346            200: handleRowDocs,
     347            default: handleUnknownResponse
     348        });
     349    }
     350    app.get('/api/questions',
     351        ensureAuthenticated,
     352        ensureMIME(JSON_MIME),
     353        function(req,res) {
     354            var hr;
     355            if ( 'category' in req.query ) {
     356                hr = getQuestionsWithCategoryAndTopic(req.query.category,req.query.topic);
     357            } else if ( 'topic' in req.query ) {
     358                hr = getQuestionsWithTopic(req.query.topic);
     359            } else if ( 'code' in req.query ) {
     360                hr = getQuestionsWithCode(req.query.code);
     361            }
     362            hr.handle(res.send.bind(res));
     363        });
     364    app.post('/api/questions',
     365        ensureAuthenticated,
     366        ensureMIME(JSON_MIME),
     367        function (req,res) {
     368            var doc = req.body;
     369            getQuestionsWithCode(doc.code)
     370            .handle({
     371                200: function(others) {
     372                    if ( others.length > 0 ) {
     373                        return new HTTPResult(403,{error:"Other question with this code already exists."});
     374                    } else {
     375                        return postDocument('Question',doc);
     376                    }
     377                }
     378            })
     379            .handle(res.send.bind(res));
     380        });
     381    app.get('/api/questions/:id',
     382        ensureAuthenticated,
     383        ensureMIME(JSON_MIME),
     384        makeDocGet_id('Question'));
     385    app.put('/api/questions/:id',
     386        ensureAuthenticated,
     387        ensureMIME(JSON_MIME),
     388        function (req,res) {
     389            var id = req.params.id;
     390            var doc = req.body;
     391            var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
     392            getQuestionsWithCode(doc.code)
     393            .handle({
     394                200: function(others) {
     395                    if ( others.length > 0 ) {
     396                        return new HTTPResult(403,{error:"Other question with this code already exists."});
     397                    } else {
     398                        return putDocument(id,rev,'Question',doc);
     399                    }
     400                }
     401            })
     402            .handle(res.send.bind(res));
     403        });
     404    app.delete('/api/questions/:id',
     405        ensureAuthenticated,
     406        ensureMIME(JSON_MIME),
     407        makeDocDel_id('Question'));
     408
     409
     410    // Categories and topics
     411    app.get('/api/categories',
     412        ensureAuthenticated,
     413        ensureMIME(JSON_MIME),
     414        function(req,res) {
     415            var url = '_design/questions/_view/all';
     416            HTTPResult.fromResponsePromise(couch.get(url,{query:{reduce:true,group:true,group_level:1}}).response,
     417                                           handleUnknownError)
     418            .handle({
     419                200: function(result) {
     420                    return _.map(result.rows, function(item) {
     421                        return { name:item.key[0], count:item.value };
     422                    });
     423                },
     424                default: handleUnknownResponse
     425            })
     426            .handle(res.send.bind(res));
     427        });
     428    app.get('/api/categories/:category/topics',
     429        ensureAuthenticated,
     430        ensureMIME(JSON_MIME),
     431        function(req,res) {
     432            var category = req.params.category;
     433            var url = '_design/questions/_view/all';
     434            HTTPResult.fromResponsePromise(couch.get(url,{query:{reduce:true,group:true,group_level:2,startkey:[category],endkey:[category,{}]}}).response,
     435                                           handleUnknownError)
     436            .handle({
     437                200: function(result) {
     438                    return _.map(result.rows, function(item) { return { name:item.key[1], count:item.value }; });
     439                },
     440                default: handleUnknownResponse
     441            })
     442            .handle(res.send.bind(res));
     443        });
     444    app.get('/api/topics',
     445        ensureAuthenticated,
     446        ensureMIME(JSON_MIME),
     447        function(req,res) {
     448            var url = '_design/questions/_view/all_topics';
     449            HTTPResult.fromResponsePromise(couch.get(url,{query:{reduce:true,group:true}}).response,
     450                                           handleUnknownError)
     451            .handle({
     452                200: function(result) {
     453                    return _.map(result.rows, function(item) { return {name:item.key, count:item.value}; });
     454                },
     455                default: handleUnknownResponse
     456            })
     457            .handle(res.send.bind(res));
     458        });
     459
     460    // Surveys
     461    app.get('/api/surveys',
     462        ensureAuthenticated,
     463        ensureMIME(JSON_MIME),
     464        function(req,res) {
     465            var url;
     466            if ( 'drafts' in req.query ) {
     467                url = '_design/surveys/_view/drafts';
     468            } else if ( 'published' in req.query ) {
     469                url = '_design/surveys/_view/published';
     470            } else {
     471                url = '_design/default/_view/by_type?key='+JSON.stringify('Survey');
     472            }
     473            HTTPResult.fromResponsePromise(couch.get(url).response,
     474                                           handleUnknownError)
     475            .handle({
     476                200: function(result) {
     477                    return _.map(result.rows, function(item) { return item.value; });
     478                },
     479                default: handleUnknownResponse
     480            })
     481            .handle(res.send.bind(res));
     482        });
     483    app.post('/api/surveys',
     484        ensureAuthenticated,
     485        ensureMIME(JSON_MIME),
     486        makeDocPost('Survey'));
     487    app.get('/api/surveys/:id',
     488        ensureAuthenticated,
     489        ensureMIME(JSON_MIME),
     490        makeDocGet_id('Survey'));
     491    app.put('/api/surveys/:id',
     492        ensureAuthenticated,
     493        ensureMIME(JSON_MIME),
     494        makeDocPut_id('Survey'));
     495    app.delete('/api/surveys/:id',
     496        ensureAuthenticated,
     497        ensureMIME(JSON_MIME),
     498        makeDocDel_id('Survey'));
     499
     500    // SurveyRuns
     501    app.get('/api/surveyRuns',
     502        ensureAuthenticated,
     503        ensureMIME(JSON_MIME),
     504        makeDocsGet('SurveyRun'));
     505    app.post('/api/surveyRuns',
     506        ensureAuthenticated,
     507        ensureMIME(JSON_MIME),
     508        function(req,res) {
     509            var doc = req.body;
     510            randomToken()
     511            .handle({
     512                201: function(token) {
     513                    doc.secret = token;
     514                    return postDocument('SurveyRun',doc);
     515                }
     516            })
     517            .handle(res.send.bind(res));
     518        });
     519    app.get('/api/surveyRuns/:id',
     520        ensureAuthenticated,
     521        ensureMIME(JSON_MIME),
     522        makeDocGet_id('SurveyRun'));
     523    app.put('/api/surveyRuns/:id',
     524        ensureAuthenticated,
     525        ensureMIME(JSON_MIME),
     526        function (req,res) {
     527            var id = req.params.id;
     528            var doc = req.body;
     529            var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
     530            var hr;
     531            if ( typeof doc.secret === 'undefined' ) {
     532                hr = randomToken()
     533                .handle({
     534                    201: function(token) {
     535                        doc.secret = token;
     536                        return putDocument(id,rev,'SurveyRun',doc);
     537                    }
    148538                });
    149539            } else {
    150                 res.send(400,{error: "Document failed schema verification."});
     540                hr = putDocument(id,rev,'SurveyRun',doc);
    151541            }
    152         };
    153     }
    154     function makeDocDel_id(type) {
    155         return function(req,res) {
    156             var id = req.params.id;
    157             var rev = req.query.rev;
    158             couch.delete(id,rev).then(function(result){
    159                 res.send(200, result);
    160             }, function(error){
    161                 res.send(500, error);
     542            hr.handle(res.send.bind(res));
     543        });
     544    app.delete('/api/surveyRuns/:id',
     545        ensureAuthenticated,
     546        ensureMIME(JSON_MIME),
     547        makeDocDel_id('SurveyRun'));
     548    app.get('/api/surveyRuns/:id/responses',
     549        ensureAuthenticated,
     550        ensureMIME(JSON_MIME),
     551        function(req,res) {
     552            var id = req.params.id;
     553            getResponsesBySurveyRunId(id)
     554            .handle(res.send.bind(res));
     555        });
     556    app.get('/api/surveyRuns/:id/responses.csv',
     557        ensureAuthenticated,
     558        ensureMIME(CSV_MIME),
     559        function(req, res) {
     560            var id = req.params.id;
     561            getResponsesBySurveyRunId(id)
     562            .handle({
     563                200: function(responses) {
     564                    var flatResponses = responsesToVariables(responses);
     565                    res.set({
     566                        'Content-Disposition': 'attachment; filename=surveyRun-'+id+'-responses.csv'
     567                    });
     568                    res.status(200);
     569                    writeObjectsToCSVStream(flatResponses, res);
     570                    res.end();
     571                },
     572                default: res.send.bind(res)
    162573            });
    163         };
    164     }
    165     function makeDocPost(type) {
    166         return function(req,res) {
    167             var doc = req.body;
    168             if ( doc.type === type && tv4.validateResult(doc, schema) ) {
    169                 couch.post(req.body).then(function(result){
    170                     res.send(200, result);
    171                 }, function(error){
    172                     res.send(500, error);
     574        });
     575   
     576    // Responses
     577    function getResponsesBySurveyRunId(surveyRunId) {
     578        var url = '_design/responses/_view/by_surveyrun?key='+JSON.stringify(surveyRunId);
     579        return HTTPResult.fromResponsePromise(couch.get(url).response,
     580                                              handleUnknownError)
     581        .handle({
     582            200: handleRowValues,
     583            default: handleUnknownResponse
     584        });
     585    }
     586    app.get('/api/responses',
     587        ensureAuthenticated,
     588        ensureMIME(JSON_MIME),
     589        function(req,res){
     590            var hr;
     591            if ( 'surveyRunId' in req.query ) {
     592                hr = getResponsesBySurveyRunId(req.query.surveyRunId);
     593            } else {
     594                hr = getDocumentsOfType('Response');
     595            }
     596            hr.handle(res.send.bind(res));
     597        });
     598    app.get('/api/responses/:id',
     599        ensureAuthenticated,
     600        ensureMIME(JSON_MIME),
     601        makeDocGet_id('Response'));
     602    app.post('/api/responses',
     603        ensureAuthenticated,
     604        ensureMIME(JSON_MIME),
     605        function (req,res) {
     606            var doc = req.body;
     607            randomToken()
     608            .handle({
     609                201: function(token) {
     610                    doc.secret = token;
     611                    return postDocument('Response',doc);
     612                }
     613            })
     614            .handle(res.send.bind(res));
     615        });
     616    app.put('/api/responses/:id',
     617        ensureAuthenticated,
     618        ensureMIME(JSON_MIME),
     619        function (req,res) {
     620            var id = req.params.id;
     621            var doc = req.body;
     622            var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
     623            var hr;
     624            if ( typeof doc.secret === 'undefined' ) {
     625                hr = randomToken()
     626                .handle({
     627                    201: function(token) {
     628                        doc.secret = token;
     629                        return putDocument(id,rev,'Response',doc);
     630                    }
    173631                });
    174632            } else {
    175                 res.send(400,{error: "Document failed schema verification."});
     633                hr = putDocument(id,rev,'Response',doc);
    176634            }
    177         };
    178     }
    179     function notImplemented(req,res) {
    180         res.send(501,{error:"API not implemented yet."});
    181     }
    182 
    183     // Questions
    184     app.get('/api/questions',
    185         ensureAuthenticated,
    186         makeDocsGet('Question'));
    187     app.get('/api/questions/:id',
    188         ensureAuthenticated,
    189         makeDocGet_id('Question'));
    190     app.post('/api/questions',
    191         ensureAuthenticated,
    192         makeDocPost('Question'));
    193     app.put('/api/questions/:id',
    194         ensureAuthenticated,
    195         makeDocPut_id('Question'));
    196     app.delete('/api/questions/:id',
    197         ensureAuthenticated,
    198         makeDocDel_id('Question'));
    199     app.get('/api/questions/categories',
    200         ensureAuthenticated,
    201         notImplemented);
    202 
    203     // Surveys
    204     app.get('/api/surveys',
    205         ensureAuthenticated,
    206         makeDocsGet('Survey'));
    207     app.get('/api/surveys/:id',
    208         ensureAuthenticated,
    209         makeDocGet_id('Survey'));
    210     app.post('/api/surveys',
    211         ensureAuthenticated,
    212         makeDocPost('Survey'));
    213     app.put('/api/surveys/:id',
    214         ensureAuthenticated,
    215         makeDocPut_id('Survey'));
    216     app.delete('/api/surveys/:id',
    217         ensureAuthenticated,
    218         makeDocDel_id('Survey'));
    219 
    220     // SurveyRuns
    221     app.get('/api/surveyRuns',
    222         ensureAuthenticated,
    223         makeDocsGet('SurveyRun'));
    224     app.get('/api/surveyRuns/:id',
    225         ensureAuthenticated,
    226         makeDocGet_id('SurveyRun'));
    227     app.get('/api/surveyRuns/:id/responses.csv',
    228         ensureAuthenticated,
    229         function(req, res) {
    230             var id = req.params.id;
    231             var url = '_design/responses/_view/by_surveyrun?key='+JSON.stringify(id);
    232             couch.get(url).then(function(result){
    233                 var responses = _.map(result.rows, function(item) { return item.value; });
    234                 var flatResponses = responsesToVariables(responses);
    235                 res.set({
    236                     'Content-Type': 'text/csv',
    237                     'Content-Disposition': 'attachment; filename=surveyRun-'+id+'-responses.csv'
    238                 });
    239                 res.status(200);
    240                 writeObjectsToCSVStream(flatResponses, res);
    241                 res.end();
    242             }, function(error){
    243                 res.send(404, {error: "Cannot find responses for survey run "+id});
    244             });
    245         });
    246     app.get('/api/surveyRuns/:id/responses',
    247         ensureAuthenticated,
     635            hr.handle(res.send.bind(res));
     636        });
     637    app.delete('/api/responses/:id',
     638        ensureAuthenticated,
     639        ensureMIME(JSON_MIME),
     640        makeDocDel_id('Response'));
     641
     642    //respondents api
     643    function isSurveyRunRunning(surveyRun) {
     644        var now = new Date();
     645        var afterStart = !surveyRun.startDate || now >= new Date(surveyRun.startDate);
     646        var beforeEnd = !surveyRun.endDate || now <= new Date(surveyRun.endDate);
     647        return afterStart && beforeEnd;
     648    }
     649    app.get('/api/open/responses/:id',
     650        ensureMIME(JSON_MIME),
    248651        function(req,res) {
    249652            var id = req.params.id;
    250             var url = '_design/responses/_view/by_surveyrun?key='+JSON.stringify(id);
    251             couch.get(url).then(function(result){
    252                 var responses = _.map(result.rows, function(item) { return item.value; });
    253                 res.send(200, responses);
    254             }, function(error){
    255                 res.send(404, {error: "Cannot find responses for survey run "+id});
    256             });
    257         });
    258    
    259     // Responses
    260     app.get('/api/responses',
    261         ensureAuthenticated,
    262         makeDocsGet('Response'));
    263     app.get('/api/responses/:id',
    264         ensureAuthenticated,
    265         makeDocGet_id('Response'));
    266     app.post('/api/responses',
    267         ensureAuthenticated,
    268         makeDocPost('Response'));
    269     app.put('/api/responses/:id',
    270         ensureAuthenticated,
    271         makeDocPut_id('Response'));
    272     app.delete('/api/responses/:id',
    273         ensureAuthenticated,
    274         makeDocDel_id('Response'));
     653            var rev = etags.parse(req.header('If-Non-Match'))[0];
     654            var secret = req.query.secret;
     655            getDocument(id,rev,'Response')
     656            .handle({
     657                200: function(response) {
     658                    if ( response.secret === secret ) {
     659                        return getDocument(response.surveyRunId,[],'SurveyRun')
     660                        .handle({
     661                            200: function(surveyRun) {
     662                                if ( !isSurveyRunRunning(surveyRun) ) {
     663                                    return new HTTPResult(404,{error:"Survey is not running anymore."});
     664                                } else {
     665                                    response._surveyRun = surveyRun;
     666                                    return response;
     667                                }
     668                            }
     669                        });
     670                    } else {
     671                        return new HTTPResult(403,{error: "Wrong secret for response"});
     672                    }
     673                }
     674            })
     675            .handle(res.send.bind(res));
     676        });
     677    app.post('/api/open/responses',
     678        ensureMIME(JSON_MIME),
     679        function(req,res) {
     680            var secret = req.query.secret;
     681            var response = req.body;
     682            delete response._surveyRun;
     683            getDocument(response.surveyRunId,[],'SurveyRun')
     684            .handle({
     685                200: function(surveyRun) {
     686                    if ( surveyRun.secret !== secret ) {
     687                        return new HTTPResult(403,{error:"Wrong secret for surveyRun."});
     688                    } else if ( !isSurveyRunRunning(surveyRun) ) {
     689                        return new HTTPResult(404,{error:"Survey is not running anymore."});
     690                    } else if ( surveyRun.mode === 'closed' ) {
     691                        return new HTTPResult(403,{error:"Survey is closed and doesn't allow new responses."});
     692                    } else {
     693                        return randomToken()
     694                        .handle({
     695                            201: function(token) {
     696                                response.secret = token;
     697                                return postDocument('Response',response)
     698                                .handle({
     699                                    201: function(doc){
     700                                        doc._surveyRun = surveyRun;
     701                                        return doc;
     702                                    }
     703                                });
     704                            }
     705                        });
     706                    }
     707                }
     708            })
     709            .handle(res.send.bind(res));
     710        });
     711    app.put('/api/open/responses/:id',
     712        ensureMIME(JSON_MIME),
     713        function(req,res){
     714            var id = req.params.id;
     715            var doc = req.body;
     716            var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
     717            var secret = req.query.secret || doc.secret;
     718            delete doc._surveyRun;
     719            getDocument(id,[],'Response')
     720            .handle({
     721                200: function(prev) {
     722                    if ( prev.secret !== secret ) {
     723                        return new HTTPResult(403,{error: "Secrets are not the same."});
     724                    } else if ( prev.surveyRunId !== doc.surveyRunId ) {
     725                        return new HTTPResult(400,{error:"Response cannot change it's surveyRunId."});
     726                    } else {
     727                        doc.secret = secret; // restore in case it got lost or was changed
     728                        return getDocument(doc.surveyRunId,[],'SurveyRun')
     729                        .handle({
     730                            200: function(surveyRun) {
     731                                if ( !isSurveyRunRunning(surveyRun) ) {
     732                                    return new HTTPResult(404,{error:"Survey is not running anymore."});
     733                                } else {
     734                                    return putDocument(id,rev,'Response',doc)
     735                                    .handle({
     736                                        200: function(doc) {
     737                                            doc._surveyRun = surveyRun;
     738                                            return new HTTPResult(201,doc);
     739                                        }
     740                                    });
     741                                }
     742                            }
     743                        });
     744                    }
     745                }
     746            })
     747            .handle(res.send.bind(res));
     748        });
     749    app.delete('/api/open/responses/:id',
     750        ensureMIME(JSON_MIME),
     751        function(req,res){
     752            var id = req.params.id;
     753            var doc = req.body;
     754            var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
     755            var secret = req.query.secret || doc.secret;
     756            delete doc._surveyRun;
     757            getDocument(id,[],'Response')
     758            .handle({
     759                200: function(prev) {
     760                    if ( prev.secret !== secret ) {
     761                        return new HTTPResult(403,{error: "Secrets are not the same."});
     762                    } else {
     763                        return deleteDocument(id,rev,doc);
     764                    }
     765                }
     766            })
     767            .handle(res.send.bind(res));
     768        });
     769
     770    // uuids
     771    app.get('/api/uuids',
     772        ensureAuthenticated,
     773        ensureMIME(JSON_MIME),
     774        function(req,res){
     775            var count = (req.query.count && parseInt(req.query.count,10)) || 1;
     776            HTTPResult.fromResponsePromise(couch.uuids({query:{count:count}}).response,
     777                                           handleUnknownError)
     778            .handle({
     779                200: function(res) {
     780                    return res.uuids;
     781                },
     782                default: handleUnknownResponse
     783            })
     784            .handle(res.send.bind(res));
     785        });
    275786
    276787    return app;
    277 
    278788}
    279789
     
    341851    });
    342852}
     853
     854function randomToken() {
     855    var result = new HTTPResult();
     856    cryptoken(8)
     857    .then(function(token){
     858        result.set(201,token);
     859    }, function(ex){
     860        result.set(500,{error:"Cannot generate secrets.", innerError: ex});
     861    });
     862    return result;
     863}
  • Dev/trunk/src/server/bin/check-db.js

    r479 r487  
    11var env = require('../env')
    2   , checkCouch = require('../config/check-couchdb');
    3 checkCouch(env.couchDbURL)
     2  , checkCouch = require('../config/check-couchdb')
     3  ;
     4checkCouch(env.couchServerURL,env.dbName)
    45.then(function(res){
    56    console.log("done",res);
  • Dev/trunk/src/server/bin/config-db.js

    r479 r487  
    11var env = require('../env')
    2   , checkCouch = require('../config/config-couchdb');
    3 checkCouch(env.couchServerURL)
     2  , configCouch = require('../config/config-couchdb')
     3  , designDocs = require('../config/couchdb-design-docs')
     4  ;
     5configCouch(env.couchServerURL,env.dbName,designDocs)
    46.then(function(res){
    57    console.log("done",res);
  • Dev/trunk/src/server/bin/heroku.js

    r481 r487  
    1 var env = require('../env');
    2 var configCouch = require('../config/config-couchdb');
     1var env = require('../env')
     2  , configCouch = require('../config/config-couchdb')
     3  , designDocs = require('../config/config-couchdb')
     4  ;
    35
    46console.log("Running on",env.couchServerURL);
    57
    68require('../app').App({
    7     couchDbURL: env.couchDbURL
     9    couchServerURL: env.couchServerURL,
     10    dbName: env.dbName
    811}).then(function(app){
    9     configCouch(env.couchServerURL);
     12    configCouch(env.couchServerURL,env.dbName,designDocs);
    1013    return app;
    1114}).then(function(app){
  • Dev/trunk/src/server/config/check-couchdb.js

    r479 r487  
    77var schema = require('./couchdb-schema');
    88
    9 module.exports = function(couchDbURL) {
    10     var server = new CouchDB(couchDbURL);
     9module.exports = function(couchServerURL,dbName) {
     10    var server = new CouchDB(couchServerURL,dbName);
    1111    var designRe = /^_design\//;
    1212    return server.get('/_all_docs')
  • Dev/trunk/src/server/config/config-couchdb.js

    r479 r487  
    55  ;
    66
    7 var designDocs = require('./couchdb-design-docs');
     7module.exports = function(couchServerURL, dbName, designDocs) {
     8    var server = new CouchDB(couchServerURL);
    89
    9 module.exports = function(couchServerURL) {
    10     var server = new CouchDB(couchServerURL);
     10    if ( !designDocs ) { throw new Error("Forgot to pass design docs to checkCouch."); }
    1111
    1212    console.log("Configuring CouchDB for QED");
     
    1414    return server.get('')
    1515    .then(function(info){
    16         if (info.version !== "1.2.0" ) {
     16        if (info.version !== "1.4.0" ) {
    1717            console.log("Found "+info.version+", only tested with CouchDB 1.2.0");
    1818        } else {
    19             console.log("CouchDB 1.2.0 found");
     19            console.log("CouchDB 1.4.0 found");
    2020        }
    2121    }).then(function(res){
  • Dev/trunk/src/server/config/couchdb-design-docs.js

    r480 r487  
    1111
    1212    "qed/schemaInfo": {
    13         version: "1"
     13        version: "2"
    1414    },
    1515
     
    3131        language: "javascript",
    3232        validate_doc_update: function(newDoc, oldDoc, userCtx, secObj) {
    33             if ( oldDoc && oldDoc.publicationDate ) { throw({forbidden:'Published documents cannot be modified.'}); }
     33            if ( oldDoc && oldDoc.publicationDate ) {
     34                throw({forbidden:'Published documents cannot be modified.'});
     35            }
    3436        },
    3537        views: {
     
    5961                    if ( doc.categories && doc.categories.length > 0 ) {
    6062                        for ( var i = 0; i < doc.categories.length; i++ ) {
    61                             emit([doc.categories[i],doc.topic||null],1);
     63                            emit([doc.categories[i],doc.topic||"(default)"],1);
    6264                        }
    6365                    } else {
    64                         emit([null,null],1);
     66                        emit(["(default)","(default)"],1);
    6567                    }
    6668                },
     
    7274                    if ( doc.categories && doc.categories.length > 0 ) {
    7375                        for ( var i = 0; i < doc.categories.length; i++ ) {
    74                             emit([doc.categories[i],doc.topic||null],1);
     76                            emit([doc.categories[i],doc.topic||"(default)"],1);
    7577                        }
    7678                    } else {
    77                         emit([null,null],1);
     79                        emit(["(default)","(default)"],1);
    7880                    }
    7981                },
     
    8385                map: function(doc){
    8486                    if( doc.type !== 'Question' ){ return; }
    85                     emit(doc.topic);
     87                    emit(doc.topic||"(default)",1);
    8688                },
    87                 reduce: function(key, values, rereduce) { return null; }
     89                reduce: function(key, values, rereduce) { return sum(values); }
    8890            },
    8991            published_topics: {
    9092                map: function(doc){
    9193                    if ( doc.type !== 'Question' || !doc.publicationDate ) { return; }
    92                     emit(doc.topic);
     94                    emit(doc.topic||"(default)",1);
    9395                },
    94                 reduce: function(key, values, rereduce) { return null; }
     96                reduce: function(key, values, rereduce) { return sum(values); }
     97            },
     98            by_code: {
     99                map: function(doc){
     100                    if ( doc.type !== 'Question' ) { return; }
     101                    emit(doc.code,doc);
     102                }
    95103            }
    96104        }
  • Dev/trunk/src/server/config/couchdb-schema.json

    r481 r487  
    11{
     2  "$schema": "http://json-schema.org/draft-04/schema#",
    23  "title": "QED Object Schema",
    3   "version": "1",
     4  "version": "2",
    45  "type": "object",
    56  "oneOf": [
    6     { "$ref": "#/schemaInfo" },
    7     { "$ref": "#/docTypes/any" }
     7    { "$ref": "#/definitions/schemaInfo" },
     8    { "$ref": "#/definitions/docs/any" }
    89  ],
    9   "schemaInfo": {
    10     "type": "object",
    11     "properties": {
    12         "_id": { "type": "string", "pattern": "schemaInfo" },
    13         "_rev": { "type": "string", "optional": true },
    14         "version": { "type": "string" }
    15     },
    16     "additionalProperties": false
    17   },
    18   "docTypes": {
    19     "any": {
     10  "definitions": {
     11    "schemaInfo": {
    2012      "type": "object",
    21       "oneOf": [
    22         { "$ref": "#/docTypes/Question" },
    23         { "$ref": "#/docTypes/Survey" },
    24         { "$ref": "#/docTypes/SurveyRun" },
    25         { "$ref": "#/docTypes/Response" }
    26       ]
    27     },
    28     "Question": {
    2913      "properties": {
    30         "type": { "type": "string", "pattern": "Question" },
    31         "_id": { "type": "string", "optional": true },
    32         "_rev": { "type": "string", "optional": true },
    33         "categories": { "type": "array", "items": { "type": "string" } },
    34         "code": { "type": "string" },
    35         "content": { "type": "array", "items": { "$ref": "#/contentTypes/any" } },
    36         "description": { "type": "string", "optional": true },
    37         "publicationDate": { "type": "string", "pattern": "", "optional": true },
    38         "title": { "type": "string" },
    39         "topic": { "type": "string", "optional": true }
     14          "_id": { "type": "string", "pattern": "schemaInfo" },
     15          "_rev": { "type": "string" },
     16          "version": { "type": "string" }
    4017      },
     18      "required": ["_id","version"],
    4119      "additionalProperties": false
    4220    },
    43     "Survey": {
    44       "type": "object",
    45       "properties": {
    46         "type": { "type": "string", "pattern": "Survey" },
    47         "_id": { "type": "string", "optional": true },
    48         "_rev": { "type": "string", "optional": true },
    49         "publicationDate": { "type": "string", "pattern": "", "optional": true },
    50         "questions": { "type": "array", "items": { "$ref": "#/docTypes/Question" } },
    51         "title": { "type": "string" }
     21    "docs": {
     22      "any": {
     23        "type": "object",
     24        "oneOf":[
     25          { "$ref": "#/definitions/docs/Question" },
     26          { "$ref": "#/definitions/docs/Survey" },
     27          { "$ref": "#/definitions/docs/SurveyRun" },
     28          { "$ref": "#/definitions/docs/Response" }
     29        ]
    5230      },
    53       "additionalProperties": false
    54     },
    55     "SurveyRun": {
    56       "type": "object",
    57       "properties": {
    58         "type": { "type": "string", "pattern": "SurveyRun" },
    59         "_id": { "type": "string", "optional": true },
    60         "_rev": { "type": "string", "optional": true },
    61         "description": { "type": "string" },
    62         "endDate": { "type": "string", "pattern": "" },
    63         "mode": { "type": "string", "enum": [ "open", "closed" ] },
    64         "startDate": { "type": "string", "pattern": "" },
    65         "survey": { "$ref": "#/docTypes/Survey" },
    66         "title": { "type": "string" }
    67       },
    68       "additionalProperties": false
    69     },
    70     "Response": {
    71       "type": "object",
    72       "properties": {
    73         "type": { "type": "string", "pattern": "Response" },
    74         "_id": { "type": "string", "optional": true },
    75         "_rev": { "type": "string", "optional": true },
    76         "answers": { "type": "object" },
    77         "publicationDate": { "type": "string", "pattern": "", "optional": true },
    78         "surveyRunId": { "type": "string" }
    79       },
    80       "additionalProperties": false
    81     }
    82   },
    83   "contentTypes": {
    84     "any": {
    85       "type": "object",
    86       "oneOf": [
    87         { "$ref": "#/contentTypes/Text" },
    88         { "$ref": "#/contentTypes/StringInput" },
    89         { "$ref": "#/contentTypes/ScaleInput" }
    90       ]
    91     },
    92     "Text": {
     31      "Question": {
    9332        "type": "object",
    9433        "properties": {
    95             "type": { "type": "string", "pattern": "Text" },
    96             "text": { "type": "string" }
     34          "type": { "type": "string", "pattern": "Question" },
     35          "_id": { "type": "string" },
     36          "_rev": { "type": "string" },
     37          "categories": { "type": "array", "items": { "type": "string" } },
     38          "code": { "type": "string" },
     39          "content": { "type": "array", "items": { "$ref": "#/definitions/content/any" } },
     40          "description": { "type": "string" },
     41          "publicationDate": { "type": "string", "format": "datetime" },
     42          "title": { "type": "string" },
     43          "topic": { "type": "string" }
    9744        },
     45        "required": ["type","categories","code","content","title"],
    9846        "additionalProperties": false
    99     },
    100     "StringInput": {
     47      },
     48      "Survey": {
    10149        "type": "object",
    10250        "properties": {
    103             "type": { "type": "string", "pattern": "StringInput" },
    104             "text": { "type": "string" }
     51          "type": { "type": "string", "pattern": "Survey" },
     52          "_id": { "type": "string" },
     53          "_rev": { "type": "string" },
     54          "publicationDate": { "type": "string", "format": "datetime" },
     55          "questions": { "type": "array", "items": { "$ref": "#/definitions/docs/Question" } },
     56          "title": { "type": "string" }
    10557        },
     58        "required": ["type","questions","title"],
    10659        "additionalProperties": false
    107     },
    108     "ScaleInput": {
     60      },
     61      "SurveyRun": {
    10962        "type": "object",
    11063        "properties": {
    111             "type": { "type": "string", "pattern": "ScaleInput" },
    112             "minLabel": { "type": "string", "optional": true },
    113             "min": { "type": "integer" },
    114             "max": { "type": "integer" },
    115             "maxLabel": { "type": "string", "optional": true },
    116             "naLabel": { "type": "string", "optional": true },
    117             "items": { "type": "array", "items": {
    118                 "type": "object",
    119                 "properties": {
    120                     "text": { "type": "string" },
    121                     "minLabel": { "type": "string", "optional": true },
    122                     "maxLabel": { "type": "string", "optional": true }
    123                 },
    124                 "additionalProperties": false
    125             } }
     64          "type": { "type": "string", "pattern": "SurveyRun" },
     65          "_id": { "type": "string" },
     66          "_rev": { "type": "string" },
     67          "description": { "type": "string" },
     68          "endDate": { "type": "string", "format": "datetime" },
     69          "mode": { "type": "string", "enum": [ "open", "closed" ] },
     70          "secret": { "type": "string", "minLength": 8 },
     71          "startDate": { "type": "string", "format": "datetime" },
     72          "survey": { "$ref": "#/definitions/docs/Survey" },
     73          "title": { "type": "string" }
    12674        },
     75        "required": ["type","description","mode","secret","survey","title"],
    12776        "additionalProperties": false
     77      },
     78      "Response": {
     79        "type": "object",
     80        "properties": {
     81          "type": { "type": "string", "pattern": "Response" },
     82          "_id": { "type": "string" },
     83          "_rev": { "type": "string" },
     84          "answers": { "type": "object" },
     85          "publicationDate": { "type": "string", "format": "datetime" },
     86          "secret": { "type": "string", "minLength": 8 },
     87          "surveyRunId": { "type": "string" }
     88        },
     89        "required": ["type","answers","secret","surveyRunId"],
     90        "additionalProperties": false
     91      }
     92    },
     93    "content":{
     94      "any": {
     95        "type": "object",
     96        "oneOf": [
     97          { "$ref": "#/definitions/content/Text" },
     98          { "$ref": "#/definitions/content/StringInput" },
     99          { "$ref": "#/definitions/content/ScaleInput" }
     100        ]
     101      },
     102      "Text": {
     103        "type": "object",
     104        "properties": {
     105          "type": { "type": "string", "pattern": "Text" },
     106          "text": { "type": "string" }
     107        },
     108        "required": ["type","text"],
     109        "additionalProperties": false
     110      },
     111      "StringInput": {
     112        "type": "object",
     113        "properties": {
     114          "type": { "type": "string", "pattern": "StringInput" },
     115          "text": { "type": "string" }
     116        },
     117        "required":["type","text"],
     118        "additionalProperties": false
     119      },
     120      "ScaleInput": {
     121        "type": "object",
     122        "properties": {
     123          "type": { "type": "string", "pattern": "ScaleInput" },
     124          "minLabel": { "type": "string" },
     125          "min": { "type": "integer" },
     126          "max": { "type": "integer" },
     127          "maxLabel": { "type": "string" },
     128          "naLabel": { "type": "string" },
     129          "items": { "type": "array", "items": {
     130            "type": "object",
     131            "properties": {
     132              "text": { "type": "string" },
     133              "minLabel": { "type": "string" },
     134              "maxLabel": { "type": "string" }
     135            },
     136            "required":["text"],
     137            "additionalProperties": false
     138          } }
     139        },
     140        "required":["type","min","max","items"],
     141        "additionalProperties": false
     142      }
    128143    }
    129144  }
  • Dev/trunk/src/server/env.js

    r479 r487  
    33    port: process.env.PORT || 5000,
    44    couchServerURL: couchServerURL,
    5     couchDbURL: couchServerURL+'qed',
     5    dbName: 'qed'
    66};
  • Dev/trunk/src/server/util/couch.coffee

    r479 r487  
    66 
    77class CouchDB
    8     constructor: (url) ->
    9         @url = normalizeURL url
    10     get: (id) ->
     8    constructor: (url,db) ->
     9        @serverURL = normalizeURL url
     10        @db = db
     11        if @db
     12            @url = "#{@serverURL}/#{@db}"
     13        else
     14            @url = "#{@serverURL}"
     15    get: (id, opts) ->
    1116        url = "#{@url}/#{id}"
    12         couchRequest 'GET', url
    13     post: (doc) ->
    14         url = "#{@url}"
    15         couchRequest 'POST', url, doc
    16     put: (id, doc) ->
     17        couchRequest 'GET', url, null, opts
     18    post: (doc, opts) ->
     19        url = "#{@url}/"
     20        couchRequest 'POST', url, doc, opts
     21    put: (id, doc, opts) ->
    1722        url = "#{@url}/#{id}"
    18         couchRequest 'PUT', url, doc
    19     delete: (id, rev) ->
    20         url = "#{@url}/#{id}?rev=#{rev}"
    21         couchRequest 'DELETE', url
     23        couchRequest 'PUT', url, doc, opts
     24    delete: (id, opts) ->
     25        url = "#{@url}/#{id}"
     26        couchRequest 'DELETE', url, null, opts
     27    uuids: (opts) ->
     28        url = "#{@serverURL}/_uuids"
     29        couchRequest 'GET', url, null, opts
    2230
    2331normalizeURL = (url) ->
    2432    url.replace /\/$/, ''
    2533
    26 couchRequest = (method, url, data) ->
     34couchRequest = (method, url, body, opts) ->
    2735    options =
    2836        method: method,
    2937        headers:
    30             'content-type': 'application/json; charset=utf-8'
    31             'accept': 'application/json'
    32         body: JSON.stringify (stringifyFunctions (data || {}))
     38            'Content-Type': 'application/json; charset=utf-8'
     39            'Accept': 'application/json'
     40        body: JSON.stringify (stringifyFunctions (body || {}))
     41    if opts
     42        options.qs = createQueryObj opts.query if opts.query
     43        _.extend options.headers, opts.headers if opts.headers
    3344    req = request(url, options)
    34     req.response
    35     .then (res) =>
     45    res = req.response.then (res) =>
    3646        req.then (res) =>
    3747            JSON.parse res
     
    4050    , (err) =>
    4151        Q.reject err
     52    res.response = req.response.then (res) =>
     53        statusCode: res.statusCode
     54        headers: res.headers
     55        body: JSON.parse res.body
     56    , (err) =>
     57        Q.reject err
     58    res
    4259
     60createQueryObj = (obj) ->
     61    newObj = {}
     62    _.each obj, (val,key) ->
     63        newObj[key] = JSON.stringify val
     64    newObj
     65       
    4366stringifyFunctions = (value) ->
    4467    if value is null
Note: See TracChangeset for help on using the changeset viewer.