Index: Dev/trunk/src/server/app.js
===================================================================
--- Dev/trunk/src/server/app.js	(revision 490)
+++ Dev/trunk/src/server/app.js	(revision 492)
@@ -8,5 +8,5 @@
   , CouchDB = require('./util/couch').CouchDB
   , _ = require("underscore")
-  , tv4 = require("tv4")
+  , validator = require("./util/validator")
   , HTTPResult = require("./util/http-result")
   , etags = require("./util/etags")
@@ -151,5 +151,5 @@
         return new HTTPResult(500,{error: error.reason});
     }
-    function handleUnknownError(error){
+    function handleUnknownError(error) {
         return new HTTPResult(500, {error: "Unknown error", innerError: error});
     }
@@ -159,4 +159,30 @@
     function handleRowDocs(result) {
         return _.map(result.rows, function(item) { return item.doc; });
+    }
+    function handleRowFirstDoc(result) {
+        if ( result.rows.length > 0 ) {
+            return result.rows[0].doc;
+        } else {
+            return new HTTPResult(404,{error:"No document found."});
+        }
+    }
+    function handleRowFirstValue(result) {
+        if ( result.rows.length > 0 ) {
+            return result.rows[0].value;
+        } else {
+            return new HTTPResult(404,{error:"No document found."});
+        }
+    }
+    function handleRowDictOfDocs(result) {
+        return _.reduce(result.rows, function(dict,item) {
+            dict[item.key] = item.doc;
+            return dict;
+        }, {});
+    }
+    function handleRowDictOfValues(result) {
+        return _.reduce(result.rows, function(dict,item) {
+            dict[item.key] = item.value;
+            return dict;
+        }, {});
     }
 
@@ -199,5 +225,5 @@
     function putDocument(id,rev,type,doc) {
         var priv = stripAndReturnPrivates(doc);
-        if ( doc.type === type && tv4.validateResult(doc, schema) ) {
+        if ( doc.type === type && validator(doc, schema).valid ) {
             var opts = rev ? {headers:{'If-Match':'"'+rev+'"'}} : {};
             return HTTPResult.fromResponsePromise(couch.put(id,doc,opts).response,
@@ -236,5 +262,5 @@
     function postDocument(type,doc) {
         var priv = stripAndReturnPrivates(doc);
-        if ( doc.type === type && tv4.validateResult(doc, schema) ) {
+        if ( doc.type === type && validator(doc, schema).valid ) {
             return HTTPResult.fromResponsePromise(couch.post(doc).response,
                                                   handleUnknownError)
@@ -321,4 +347,14 @@
         .handle({
             200: handleRowDocs,
+            default: handleUnknownResponse
+        });
+    }
+    function getQuestionsAndCodes() {
+        var url = '_design/questions/_view/by_code';
+        var query = {include_docs:true};
+        return HTTPResult.fromResponsePromise(couch.get(url,{query:query}).response,
+                                              handleUnknownError)
+        .handle({
+            200: handleRowDictOfDocs,
             default: handleUnknownResponse
         });
@@ -760,5 +796,14 @@
                         return new HTTPResult(403,{error: "Secrets are not the same."});
                     } else {
-                        return deleteDocument(id,rev,doc);
+                        return getDocument(doc.surveyRunId,[],'SurveyRun')
+                        .handle({
+                            200: function(surveyRun) {
+                                if ( surveyRun.respondentCanDeleteOwnResponse === true ) {
+                                    return deleteDocument(id,rev,doc);
+                                } else {
+                                    return new HTTPResult(403,{error:"Not allowed to delete response."});
+                                }
+                            }
+                        });
                     }
                 }
Index: Dev/trunk/src/server/config/check-couchdb.js
===================================================================
--- Dev/trunk/src/server/config/check-couchdb.js	(revision 490)
+++ Dev/trunk/src/server/config/check-couchdb.js	(revision 492)
@@ -1,5 +1,5 @@
 var Q = require('q')
   , _ = require('underscore')
-  , tv4 = require('tv4')
+  , validator = require('../util/validator')
   , CouchDB = require('../util/couch').CouchDB
   ;
@@ -10,4 +10,15 @@
     var server = new CouchDB(couchServerURL,dbName);
     var designRe = /^_design\//;
+
+    var codes = {};
+    function codeUnique(code) {
+        if ( code in codes ) {
+            return false;
+        } else {
+            codes[code] = true;
+            return true;
+        }
+    }
+
     return server.get('/_all_docs')
     .then(function(allDocs){
@@ -20,5 +31,9 @@
                 return server.get(doc.id)
                 .then(function(doc){
-                    var valid = tv4.validateResult(doc,schema);
+                    var valid = validator(doc,schema);
+                    if ( doc.type === "Question" && !codeUnique(doc.code) ) {
+                        valid.valid = false;
+                        valid.error = "Question code "+doc.code+" is not unique.";
+                    }
                     result[doc._id] = valid;
                     return result;
Index: Dev/trunk/src/server/config/couchdb-design-docs.js
===================================================================
--- Dev/trunk/src/server/config/couchdb-design-docs.js	(revision 490)
+++ Dev/trunk/src/server/config/couchdb-design-docs.js	(revision 492)
@@ -125,4 +125,20 @@
     },
 
+    "qed/_design/surveyRuns": {
+        __configAction: "replace",
+        _id: "_design/surveys",
+        language: "javascript",
+        views: {
+            by_dates: {
+                map: function(doc){
+                    if ( doc.type !== 'SurveyRun' ) { return; }
+                    var startDate = doc.startDate || "";
+                    var endDate = doc.endDate || {};
+                    emit([startDate,endDate,doc.liveName||null],doc);
+                }
+            }
+        }
+    },
+
     "qed/_design/responses": {
         __configAction: "replace",
Index: Dev/trunk/src/server/config/couchdb-schema.json
===================================================================
--- Dev/trunk/src/server/config/couchdb-schema.json	(revision 490)
+++ Dev/trunk/src/server/config/couchdb-schema.json	(revision 492)
@@ -36,5 +36,5 @@
           "_rev": { "type": "string" },
           "categories": { "type": "array", "items": { "type": "string" } },
-          "code": { "type": "string" },
+          "code": { "type": "string", "minLength": 1 },
           "content": { "type": "array", "items": { "$ref": "#/definitions/content/any" } },
           "description": { "type": "string" },
@@ -52,4 +52,5 @@
           "_id": { "type": "string" },
           "_rev": { "type": "string" },
+          "description": { "type": "string" },
           "publicationDate": { "type": "string", "format": "datetime" },
           "questions": { "type": "array", "items": { "$ref": "#/definitions/docs/Question" } },
@@ -67,6 +68,8 @@
           "description": { "type": "string" },
           "endDate": { "type": "string", "format": "datetime" },
+          "liveName": { "type": "string" },
           "mode": { "type": "string", "enum": [ "open", "closed" ] },
-          "secret": { "type": "string", "minLength": 8 },
+          "respondentCanDeleteOwnResponse": { "type": "boolean" },
+          "secret": { "type": "string", "minLength": 1 },
           "startDate": { "type": "string", "format": "datetime" },
           "survey": { "$ref": "#/definitions/docs/Survey" },
@@ -83,6 +86,7 @@
           "_rev": { "type": "string" },
           "answers": { "type": "object" },
+          "email": { "type": "string", "format": "email" },
           "publicationDate": { "type": "string", "format": "datetime" },
-          "secret": { "type": "string", "minLength": 8 },
+          "secret": { "type": "string", "minLength": 1 },
           "surveyRunId": { "type": "string" }
         },
Index: Dev/trunk/src/server/config/upgrade-couchdb.js
===================================================================
--- Dev/trunk/src/server/config/upgrade-couchdb.js	(revision 490)
+++ Dev/trunk/src/server/config/upgrade-couchdb.js	(revision 492)
@@ -1,5 +1,4 @@
 var Q = require('q')
   , _ = require('underscore')
-  , tv4 = require('tv4')
   , CouchDB = require('../util/couch').CouchDB
   ;
Index: Dev/trunk/src/server/util/validator.js
===================================================================
--- Dev/trunk/src/server/util/validator.js	(revision 492)
+++ Dev/trunk/src/server/util/validator.js	(revision 492)
@@ -0,0 +1,24 @@
+var tv4 = require('tv4');
+
+// from: http://www.w3.org/TR/html5/forms.html#valid-e-mail-address
+var html5EmailRe = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
+var datetimeRe = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z/;
+
+tv4.addFormat({
+    email: function(data){
+        if ( typeof data === "string" && html5EmailRe.test(data) ) {
+            return null;
+        } else {
+            return "Probably an invalid email address.";
+        }
+    },
+    datetime: function(data){
+        if ( typeof data === "string" && datetimeRe.test(data) ) {
+            return null;
+        } else {
+            return "Invalid timestamp.";
+        }
+    }
+});
+
+module.exports = tv4.validateResult.bind(tv4);
