Changeset 415


Ignore:
Timestamp:
12/04/12 16:29:49 (12 years ago)
Author:
hendrikvanantwerpen
Message:

Support data validation and increase dendency separation.

Added json-schema validation before documents are saved. Acts both as
a safe-guard and as a reference of the data model.

Added directory for server related material. For now it contains scripts
to configure CouchDB and check validity of documents against the schema.

Started separating out parts that depend on the data model from parts
that do not.

Location:
Dev/branches/rest-dojo-ui
Files:
19 added
7 deleted
12 edited
5 copied
2 moved

Legend:

Unmodified
Added
Removed
  • Dev/branches/rest-dojo-ui/client/rft/pages/question.html

    r407 r415  
    33        <h2>
    44            <span class="rftIcon rftIconSurvey"></span>
    5             <span class="headerText">Question 123 [Editing]</span>
     5            <span class="headerText"><span data-dojo-attach-point="titleNode">Question title</span> [Editing]</span>
    66        </h2>
    77    </div>
  • Dev/branches/rest-dojo-ui/client/rft/pages/question.js

    r410 r415  
    2121            if ( this._started ) { return; }
    2222            this.inherited(arguments);
    23             if (this.questionId) {
     23            if ( !this.questionId ) {
     24                throw new Error("Error: no reference to object set!");
     25            }
     26            this._setupEditor();
     27            if (this.questionId === "new") {
     28                this.question = { type: 'Question' };
     29                this._refresh();
     30            } else {
    2431                Deferred.when(store.get(this.questionId))
    2532                .then(lang.hitch(this, function(obj) {
     
    2734                    this._refresh();
    2835                }));
    29             } else {
    30                 throw new Error("Error: no reference to object set!");
    3136            }
    32             this._setupEditor();
    3337        },
    3438        onLeave: function() {
     
    3640        },
    3741        _refresh: function () {
     42            this.titleNode.innerHTML = this.question.title || "";
    3843            this._toolkit.set('value',this.question);
    3944            this._preview.appendItems(this.question.content || []);
     
    4651                Router.go('/questions');
    4752            },function(err){
    48                 Content.notify(err.reason,'error');
     53                Content.notify(err,'error');
    4954            });
    5055            evt && event.stop( evt );
  • Dev/branches/rest-dojo-ui/client/rft/pages/questions.js

    r414 r415  
    4040        },
    4141        onNewQuestion: function() {
    42             Deferred.when(store.add({type:'Question'}))
    43             .then(lang.hitch(this,function(question){
    44                 this.onEditQuestion(question);
    45             }));
     42            Router.go("/question/new");
    4643        },
    4744        onDeleteQuestion: function(question) {
     
    5047                Content.notify("Question deleted.");
    5148            },function(err){
    52                 Content.notify(err.reason,'error');
     49                Content.notify(err,'error');
    5350            });
    5451        },
     
    6259                Content.notify("Question published.");
    6360            },function(err){
    64                 Content.notify(err.reason,'error');
     61                Content.notify(err,'error');
    6562            });
    6663        }
  • Dev/branches/rest-dojo-ui/client/rft/pages/survey.html

    r407 r415  
    4040         data-dojo-attach-event="onSubmit:_onPropertiesOk">
    4141
    42         <form data-dojo-type="dijit/form/Form"
    43               data-dojo-attach-point="propertiesForm">
    44             <label for="title">Title</label>
    45             <input data-dojo-type="dijit/form/TextBox" name="title"/><br/>
    46             <label for="description">Description</label>
    47             <input data-dojo-type="dijit/form/Textarea" name="description"/><br/>
    48         </form>
     42        <div data-dojo-type="rft/ui/model/SurveyForm"
     43              data-dojo-attach-point="propertiesForm"></div>
    4944           
    5045        <div>
  • Dev/branches/rest-dojo-ui/client/rft/pages/surveys.html

    r414 r415  
    3636    </div>
    3737
     38    <div data-dojo-type="dijit/Dialog"
     39         title="SurveyRun properties"
     40         data-dojo-attach-point="surveyRunDialog">
     41        <form data-dojo-type="rft/ui/model/SurveyRunForm" data-dojo-attach-point="surveyRunForm">
     42            <button data-dojo-type="dijit/form/Button">OK</button>
     43            <button data-dojo-type="dijit/form/Button">Cancel</button>
     44        </form>
     45    </div>
     46
    3847</div>
  • Dev/branches/rest-dojo-ui/client/rft/pages/surveys.js

    r414 r415  
    2323                Router.go('/survey/'+store.getIdentity(survey));
    2424            },function(err){
    25                 Content.notify(err.reason,'error');
     25                Content.notify(err,'error');
    2626            });
    2727        },
     
    3333                self.refreshPublished();
    3434            },function(err){
    35                 Content.notify(err.reason,'error');
     35                Content.notify(err,'error');
    3636            });
    3737        },
     
    4242                self.refreshDrafts();
    4343            },function(err){
    44                 Content.notify(err.reason,'error');
     44                Content.notify(err,'error');
    4545            });
    4646        },
     
    5252        },
    5353        _onRunSurvey:function(survey){
    54 
     54            this.surveyRunDialog.show();
    5555        },
    5656        refresh: function() {
  • Dev/branches/rest-dojo-ui/client/rft/pages/viewSurvey.js

    r410 r415  
    77    '../store',
    88    '../app/Page',
    9     '../ui/content/ContentWidgetFactory',
     9    '../ui/model/QuestionWidgetFactory',
    1010    'dojo/text!./viewSurvey.html'
    11 ],function(array,declare,Deferred,event,lang,store,Page,ContentWidgetFactory,template){
     11],function(array,declare,Deferred,event,lang,store,Page,QuestionWidgetFactory,template){
    1212    return declare([Page],{
    1313        templateString: template,
     
    3535                    this.titleNode.innerHTML = survey.title +
    3636                            (this.options.preview?' [preview]':'');
    37                     var f = new ContentWidgetFactory();
     37                    var f = new QuestionWidgetFactory();
    3838                    this.survey = survey;
    3939                    store.query(null,{keys:this.survey.questions,include_docs:true})
  • Dev/branches/rest-dojo-ui/client/rft/stddeps.js

    r407 r415  
    2121    'dijit/form/Textarea',
    2222    'dijit/form/TextBox',
     23    'dijit/form/TimeTextBox',
    2324
    2425    'dojox/grid/DataGrid',
     
    3334    'rft/ui/Selector',
    3435    'rft/ui/TitleGroup',
     36    'rft/ui/generic/DateTimeTextBox',
     37    'rft/ui/model/SurveyForm',
     38    'rft/ui/model/SurveyRunForm',
    3539    'rft/ui/lists/AccountListView',
    3640    'rft/ui/lists/List',
  • Dev/branches/rest-dojo-ui/client/rft/store.js

    r410 r415  
    11define([
    22    'dojo/date/stamp',
     3    'dojox/json/schema',
     4    './schema',
    35    './store/CouchStore'
    4 ],function(stamp,CouchStore){
     6],function(stamp,jsonSchema,schema,CouchStore){
    57   
    6     var store = new CouchStore({target: 'data/couch/'});
     8    var store = new CouchStore({
     9        target: 'data/couch/',
     10        validate: function(object) {
     11            var result = jsonSchema.validate(object,schema);
     12            if ( result.valid ) {
     13                return true;
     14            } else {
     15                console.log("Found error ",result," for invalid object ",object);
     16                return false;
     17            }
     18        }
     19    });
    720    store.formatDate = function(date){
    821        return stamp.toISOString(date,{zulu:true});
  • Dev/branches/rest-dojo-ui/client/rft/store/CouchStore.js

    r414 r415  
    1010   
    1111    function getCouchError(err){
    12         return (err.response && err.response.data)
    13                 ? json.fromJson(err.response.data)
    14                 : "Unknown error.";
     12        var reason = err.response &&
     13                     err.response.data &&
     14                     json.fromJson(err.response.data).reason;
     15        return reason || "Unknown error.";
    1516    }
    1617
     
    7475            return dfd.promise;
    7576        },
     77        validate: function(object) {
     78            return true;
     79        },
    7680        put: function(object, options){
    7781             // summary:
     
    8387             //         id: String
    8488             //
     89
     90            if ( !this.validate(object) ) {
     91                var dfd = new Deferred();
     92                dfd.reject("Invalid document.");
     93                return dfd.promise;
     94            }
     95            return this._putValid(object, options);
     96
     97        },
     98        _putValid: function(object,options) {
     99            var dfd = new Deferred();
    85100            options = options || {};
    86 
    87             var dfd = new Deferred();
    88101            var id = options.id ? options.id : this.getIdentity(object);
    89             var hasId = typeof id != "undefined";
     102            var hasId = typeof id !== "undefined";
    90103            xhr(hasId ? "PUT" : "POST", {
    91104                url: hasId ? this.target + id : this.target,
  • Dev/branches/rest-dojo-ui/client/rft/ui/QuestionEditorPreviewItem.js

    r414 r415  
    1010    'dijit/_WidgetBase',
    1111    'dijit/_WidgetsInTemplateMixin',
    12     './content/ContentWidgetFactory',
     12    './model/QuestionWidgetFactory',
    1313    'dojo/text!./templates/QuestionEditorPreviewItem.html'
    1414], function(
     
    2323    _WidgetBase,
    2424    _WidgetsInTemplateMixin,
    25     ContentWidgetFactory,
     25    QuestionWidgetFactory,
    2626    template
    2727){
     
    5555        },
    5656        _showViewWidget: function() {
    57             var factory = new ContentWidgetFactory();
     57            var factory = new QuestionWidgetFactory();
    5858            this.innerWidget = factory.createViewWidget( this.item );
    5959            if ( this.innerWidget !== null ) {
     
    6565        },
    6666        _showEditWidget: function() {
    67             var factory = new ContentWidgetFactory();
     67            var factory = new QuestionWidgetFactory();
    6868            this.innerWidget = factory.createEditWidget( this.item );
    6969            if ( this.innerWidget !== null ) {
  • Dev/branches/rest-dojo-ui/docs/jsonformat.txt

    r414 r415  
    1 Document types
    2 ==============
    3 
    4 
    5 Question types
    6 ==============
    7 
    8 
    9 
    10 sessions
    11 ========
    12 {
    13     type: 'SessionTemplate'
    14     title: ''
    15     description: ''
    16     plannedDate: '' /* ISO UTC datetime */
    17     accounts: [ /* Account ids */ ]
    18 }
    19 
    20 {
    21     type: 'SessionInstance'
    22     title: ''
    23     description: ''
    24     publishedDate: '' /* ISO UTC datetime */
    25     accounts: [ /* Account ids */ ]
    26 }
    27 
    28 survey
    29 ======
    30 {
    31     type: 'Survey'
    32     title: ''
    33     description: ''
    34     questions: [ /* Question ids */ ]
    35     publishedDate: ''
    36 }
    37 
    38 {
    39     type: 'SurveyInstance'
    40     surveyId: '' // String
    41     publishedDate: '' // ISO datetime
    42     startDate: '' // can fill in after
    43     endDate: '' // can fill in until
    44 }
    45 
    46 question
    47 ========
    48 {
    49     type: 'Question'
    50     title: ''
    51     description: ''
    52     topic: ''
    53     categories: []
    54     content: [{ type:'contentTypeId', text: '' || ['' ...], /* custom content element fields */ } /*, and more ... */]
    55 }
     1This file is now obsolete. Look in rft/schema for a JSON schema that we
     2can use to actually verify our data.
  • Dev/branches/rest-dojo-ui/server/couchdb-admin/config/config.js

    r414 r415  
    22     'dojo/_base/json',
    33     'dojo/_base/lang',
    4      'dojo/Deferred',
    5      'dojo/request',
    64     'dojox/lang/functional',
    7      './docs'
    8 ],function(json,lang,Deferred,request,func,docs){
    9     var dbUrl = "http://localhost:5984/";
     5     './util/async',
     6     './util/db',
     7     './data/design-docs'
     8],function(json,lang,func,async,db,docs){
    109
    1110    function serializeFunctions(value) {
     
    2322    }
    2423
    25     function req(method,path,body) {
    26         var args = {
    27             contentType: 'application/json',
    28             handleAs: 'json',
    29             data: json.toJson(body || {})
    30         };
    31         return request[method](dbUrl+path,args);
    32     }
    33 
    34     function asyncSeq(functions) {
    35         var d = new Deferred();
    36         (function stepper(fs,arg) {
    37             if ( fs.length > 0 ) {
    38                 try {
    39                     var f = fs.shift();
    40                     var ret = f(arg);
    41                     if ( ret && ret.then ) {
    42                         ret.then(function(ret){
    43                             stepper(fs,ret);
    44                         });
    45                     } else {
    46                         stepper(fs,ret);
    47                     }
    48                 } catch(err) {
    49                     d.reject(err);
    50                 }
    51             } else {
    52                 d.resolve();
    53             }
    54         })(functions);
    55         return d.promise;
    56     }
    57 
    58     function asyncFor(list,callback,ctx) {
    59         var d = new Deferred();
    60         (function stepper(list,idx){
    61             if ( list.length > 0 ) {
    62                 var el = list.shift();
    63                 var ret = callback.call(ctx,el,idx);
    64                 if ( ret && ret.then ) {
    65                     ret.then(function(){
    66                         stepper(list,idx+1);
    67                     });
    68                 } else {
    69                     stepper(list,idx+1);
    70                 }
    71             } else {
    72                 d.resolve();
    73             }
    74         })(list,0);
    75         return d.promise;
    76     }
    77 
    78     asyncSeq([
     24    async.seq([
    7925        function(){
    80             console.log("Configuring CouchDB for RFT:");
     26            console.log("Configuring CouchDB for QED:");
    8127        },
    8228        function(){
    8329            console.log("Checking CouchDB version");
    84             return req('get','')
     30            return db.req('get','')
    8531            .then(function(res){
    8632                if (res.version !== "1.2.0" ) {
     
    9238        },function(){
    9339            console.log("Checking database 'qed'");
    94             return req('get','qed')
     40            return db.req('get','qed')
    9541            .then(function(res){
    9642                if (res.error) {
    9743                    console.log("Creating database 'qed'");
    98                     return req('put','/qed');
     44                    return db.req('put','/qed');
    9945                } else {
    10046                    console.log("Database 'qed' found.");
     
    10248            },function(res){
    10349                console.log("Creating database 'qed'");
    104                 return req('put','/qed');
     50                return db.req('put','/qed');
    10551            });
    10652        },function(){
     
    10854        },function(docs){
    10955            console.log("Putting documents in database.");
    110             return asyncFor(func.keys(docs),function(docUrl){
     56            return async.forEach(func.keys(docs),function(docUrl){
    11157                var doc = docs[docUrl];
    11258                var configAction = doc.__configAction;
     
    11763                    case "update":
    11864                        console.log(docUrl+" updating.");
    119                         return req('get',docUrl)
     65                        return db.req('get',docUrl)
    12066                        .then(function(oldDoc){
    12167                            lang.mixin(oldDoc,doc);
    122                             return req('put',docUrl,oldDoc);
     68                            return db.req('put',docUrl,oldDoc);
    12369                        },function(){
    124                             return req('put',docUrl,doc);
     70                            return db.req('put',docUrl,doc);
    12571                        });
    12672                    case "replace":
    12773                    default:
    12874                        console.log(docUrl+" replacing.");
    129                         return req('get',docUrl)
     75                        return db.req('get',docUrl)
    13076                        .then(function(oldDoc){
    13177                            doc['_rev'] = oldDoc['_rev'];
    132                             return req('put',docUrl,doc);
     78                            return db.req('put',docUrl,doc);
    13379                        },function(){
    134                             return req('put',docUrl,doc);
     80                            return db.req('put',docUrl,doc);
    13581                        });
    13682                }
  • Dev/branches/rest-dojo-ui/server/couchdb-admin/config/data/design-docs.js

    r414 r415  
    2626            validate_doc_update: function(newDoc, oldDoc, userCtx, secObj) {
    2727                if ( oldDoc && oldDoc.publicationDate ) { throw({forbidden:'Published documents cannot be modified.'}); }
    28                 if ( !newDoc._deleted && !newDoc.type ) { throw({forbidden:'Documents must have a type field.'}); }
    2928            },
    3029            views: {
     
    4039            _id: "_design/questions",
    4140            language: "javascript",
    42             validate_doc_update: function(newDoc, oldDoc, userCtx, secObj) {
    43                 if( newDoc._deleted || newDoc.type!=='Question' ){ return; }
    44                 if( !newDoc.code ){ throw({forbidden:'Question must have a code field.'});}
    45             },
    4641            views: {
    4742                all: {
  • Dev/branches/rest-dojo-ui/server/couchdb-admin/run.bat

    r412 r415  
    1 node ../dojo/dojo.js load=config/db
     1nodejs bootstrap.js load=$1
  • Dev/branches/rest-dojo-ui/server/couchdb-admin/run.sh

    r414 r415  
    11#!/bin/sh
    2 nodejs ../dojo/dojo.js load=config/db
     2nodejs bootstrap.js load=$1
Note: See TracChangeset for help on using the changeset viewer.