source: Dev/trunk/src/server/api/util.js @ 527

Last change on this file since 527 was 527, checked in by hendrikvanantwerpen, 11 years ago
  • Dropped request module for a simple one we wrote ourselves using the native Node modules. Hopefully this one will not suffer from the problem that sometimes empty bodies are returned even when the statuscode and content-length of the request are ok & present.
  • Handle exceptions better in HTTPResult chain, they were hoisted in unknown responses before. Should we not include them in default processing, because they are special?
File size: 10.1 KB
Line 
1var _ = require('underscore')
2  , HTTPResult = require("../util/http-result")
3  , validator = require("../util/validator")
4  , etags = require("../util/etags")
5  , cryptoken = require('../util/crypto-token')
6  , CSV = require("ya-csv")
7  ;
8
9module.exports = function(couch,schema) {
10    var exports = {};
11
12    var identity = exports.identity = function(obj) { return obj; };
13    var stripAndReturnPrivates = exports.stripAndReturnPrivates = function(obj) {
14        var priv = {};
15        _.each(obj||{},function(val,key){
16            if (key.substring(0,1) === '_') {
17                priv[key] = val;
18                delete obj[key];
19            }
20        });
21        return priv;
22    };
23    var areDocsUnique = exports.areDocsUnique = function(docs) {
24        return _.chain(docs)
25                .map(function(doc){ return doc._id; })
26                .uniq()
27                .value().length === docs.length;
28    };
29    var isDocPublished = exports.isDocPublished = function(doc) {
30        return !!doc.publicationDate;
31    };
32    var areDocsPublished = exports.areDocsPublished = function(docs) {
33        return _.every(docs, isDocPublished);
34    };
35
36    var handleException = exports.handleException = function(error) {
37        return new HTTPResult(500,{
38            reason: error.message,
39            filename: error.filename,
40            lineNumber: error.lineNumber
41        });
42    };
43    var handleUnknownResponse = exports.handleUnknownResponse = function(status,error) {
44        return new HTTPResult(500,{error: error.reason});
45    };
46    var handleRowKeys = exports.handleRowKeys = function(result) {
47        return _.map(result.rows, function(item) { return item.key; });
48    };
49    var handleRowValues = exports.handleRowValues = function(result) {
50        return _.map(result.rows, function(item) { return item.value; });
51    };
52    var handleRowDocs = exports.handleRowDocs = function(result) {
53        return _.map(result.rows, function(item) { return item.doc; });
54    };
55    var handleRowFirstDoc = exports.handleRowFirstDoc = function(result) {
56        if ( result.rows.length > 0 ) {
57            return result.rows[0].doc;
58        } else {
59            return new HTTPResult(404,{error:"No document found."});
60        }
61    };
62    var handleRowFirstValue = exports.handleRowFirstValue = function(result) {
63        if ( result.rows.length > 0 ) {
64            return result.rows[0].value;
65        } else {
66            return new HTTPResult(404,{error:"No document found."});
67        }
68    };
69    var handleRowDictOfDocs = exports.handleRowDictOfDocs = function(result) {
70        return _.reduce(result.rows, function(dict,item) {
71            dict[item.key] = item.doc;
72            return dict;
73        }, {});
74    };
75    var handleRowDictOfValues = exports.handleRowDictOfValues = function(result) {
76        return _.reduce(result.rows, function(dict,item) {
77            dict[item.key] = item.value;
78            return dict;
79        }, {});
80    };
81
82    var getDocumentsOfType = exports.getDocumentsOfType = function(type) {
83        var url = '_design/default/_view/by_type?key='+JSON.stringify(type);
84        return couch.get(url)
85        .handle({
86            '-1': _.identity,
87            200: handleRowValues,
88            404: function() { return {error: "Cannot find collection of type "+type}; },
89            default: handleUnknownResponse
90        });
91    };
92    var getDocument = exports.getDocument = function(id,rev,type) {
93        var opts = {headers:{}};
94        if (rev) {
95            opts.headers['If-Non-Match'] = '"'+rev+'"';
96        }
97        return couch.get(id,opts)
98        .handle({
99            '-1': _.identity,
100            200: function(doc){
101                if ( doc.type !== type ) {
102                    return new HTTPResult(404,{error:"Document not found."});
103                } else {
104                    var priv = stripAndReturnPrivates(doc);
105                    if ( priv._rev !== rev ) {
106                        doc._id = priv._id;
107                        doc._rev = priv._rev;
108                        return doc;
109                    } else {
110                        return new HTTPResult(304);
111                    }
112                }
113            },
114            304: identity,
115            default: handleUnknownResponse
116        });
117    };
118    var putDocument = exports.putDocument = function(id,rev,type,doc) {
119        var priv = stripAndReturnPrivates(doc);
120        var valid;
121        if ( doc.type === type && (valid = validator(doc, schema)).valid ) {
122            var opts = rev ? {headers:{'If-Match':'"'+rev+'"'}} : {};
123            return couch.put(id,doc,opts)
124            .handle({
125                '-1': _.identity,
126                201: function(res){
127                    doc._id = res.id;
128                    doc._rev = res.rev;
129                    return doc;
130                },
131                409: function(error) {
132                    return {error: error.reason};
133                },
134                default: handleUnknownResponse
135            });
136        } else {
137            return new HTTPResult(400,{error: "Document failed schema verification.", valid: valid});
138        }
139    };
140    var deleteDocument = exports.deleteDocument = function(id,rev) {
141        if ( rev ) {
142            var opts = {headers:{'If-Match':'"'+rev+'"'}};
143            return couch.delete(id,opts)
144            .handle({
145                '-1': _.identity,
146                200: identity,
147                409: function(error) {
148                    return {error: error.reason};
149                },
150                default: handleUnknownResponse
151            });
152        } else {
153            return new HTTPResult(409, {error: "Cannot identify document revision to delete."});
154        }
155    };
156    var postDocument = exports.postDocument = function(type,doc) {
157        var priv = stripAndReturnPrivates(doc);
158        var valid;
159        if ( doc.type === type && (valid = validator(doc, schema)).valid ) {
160            return couch.post(doc)
161            .handle({
162                '-1': _.identity,
163                201: function(response) {
164                    doc._id = response.id;
165                    doc._rev = response.rev;
166                    return doc;
167                },
168                default: handleUnknownResponse
169            });
170        } else {
171            return new HTTPResult(400,{error: "Document failed schema verification.", valid: valid});
172        }
173    };
174
175    var makeDocsGet = exports.makeDocsGet = function(type) {
176        return function(req,res) {
177            getDocumentsOfType(type)
178            .handle({'-1': handleException})
179            .handle(res.send.bind(res));
180        };
181    };
182    var makeDocGet_id = exports.makeDocGet_id = function(type) {
183        return function(req,res) {
184            var id = req.params.id;
185            var rev = etags.parse(req.header('If-Non-Match'))[0];
186            getDocument(id,rev,type)
187            .handle({
188                '-1': function(result) {
189                    handleException(result)
190                    .handle(res.send.bind(res));
191                },
192                200: function(doc){
193                    res.set({
194                        'ETag': etags.format([doc._rev])
195                    }).send(200, doc);
196                },
197                default: res.send.bind(res)
198            });
199        };
200    };
201    var makeDocPut_id = exports.makeDocPut_id = function(type) {
202        return function(req,res) {
203            var id = req.params.id;
204            var doc = req.body;
205            var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
206            putDocument(id,rev,type,doc)
207            .handle({
208                '-1': function(result) {
209                    handleException(result)
210                    .handle(res.send.bind(res));
211                },
212                201: function(doc) {
213                    res.set({
214                        'ETag': etags.format([doc._rev])
215                    }).send(201, doc);
216                },
217                default: res.send.bind(res)
218            });
219        };
220    };
221    var makeDocDel_id = exports.makeDocDel_id = function(type) {
222        return function(req,res) {
223            var id = req.params.id;
224            var doc = req.body;
225            var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev);
226            deleteDocument(id,rev)
227            .handle({'-1': handleException})
228            .handle(res.send.bind(res));
229        };
230    };
231    var makeDocPost = exports.makeDocPost = function(type) {
232        return function(req,res) {
233            var doc = req.body;
234            postDocument(type,doc)
235            .handle({
236                '-1': function(result) {
237                    handleException(result)
238                    .handle(res.send.bind(res));
239                },
240                201: function(doc) {
241                    res.set({
242                        'ETag': etags.format([doc._rev])
243                    }).send(201, doc);
244                },
245                default: res.send.bind(res)
246            });
247        };
248    };
249
250    var JSON_MIME = exports.JSON_MIME = 'application/json';
251    var CSV_MIME = exports.CSV_MIME = 'application/json';
252    var ensureMIME = exports.ensureMIME = function(mimeType) {
253        return function(req,res,next) {
254            if (!req.accepts(mimeType)) {
255                res.send(406);
256            } else {
257                res.set({
258                    'Content-Type': mimeType
259                });
260                next();
261            }
262        };
263    };
264
265    var writeObjectsToCSVStream = exports.writeObjectsToCSVStream = function(objects, stream, prelude) {
266        var keys = _.chain(objects)
267                    .map(_.keys)
268                    .flatten()
269                    .uniq()
270                    .value();
271        var idxs = {};
272        _.forEach(keys, function(key,idx){
273            idxs[key] = idx;
274        });
275        var writer = new CSV.CsvWriter(stream);
276        if ( prelude ) {
277            _.forEach(prelude, function(val,key){
278                writer.writeRecord([key,val]);
279            });
280        }
281        writer.writeRecord(keys);
282        _.forEach(objects, function(obj){
283            var row = [];
284            _.forEach(obj, function(val,key){
285                row[idxs[key]] = val;
286            });
287            writer.writeRecord(row);
288        });
289    };
290
291    return exports;
292};
Note: See TracBrowser for help on using the repository browser.