var _ = require('underscore') , HTTPResult = require("../util/http-result") , validator = require("../util/validator") , etags = require("../util/etags") , cryptoken = require('../util/crypto-token') , CSV = require("ya-csv") ; module.exports = function(couch,schema) { var exports = {}; var identity = exports.identity = function(obj) { return obj; }; var stripAndReturnPrivates = exports.stripAndReturnPrivates = function(obj) { var priv = {}; _.each(obj||{},function(val,key){ if (key.substring(0,1) === '_') { priv[key] = val; delete obj[key]; } }); return priv; }; var areDocsUnique = exports.areDocsUnique = function(docs) { return _.chain(docs) .map(function(doc){ return doc._id; }) .uniq() .value().length === docs.length; }; var isDocPublished = exports.isDocPublished = function(doc) { return !!doc.publicationDate; }; var areDocsPublished = exports.areDocsPublished = function(docs) { return _.every(docs, isDocPublished); }; var handleException = exports.handleException = function(error) { return new HTTPResult(500,{ reason: error.message, filename: error.filename, lineNumber: error.lineNumber }); }; var handleUnknownResponse = exports.handleUnknownResponse = function(status,error) { return new HTTPResult(500,{error: error.reason}); }; var handleRowKeys = exports.handleRowKeys = function(result) { return _.map(result.rows, function(item) { return item.key; }); }; var handleRowValues = exports.handleRowValues = function(result) { return _.map(result.rows, function(item) { return item.value; }); }; var handleRowDocs = exports.handleRowDocs = function(result) { return _.map(result.rows, function(item) { return item.doc; }); }; var handleRowFirstDoc = exports.handleRowFirstDoc = function(result) { if ( result.rows.length > 0 ) { return result.rows[0].doc; } else { return new HTTPResult(404,{error:"No document found."}); } }; var handleRowFirstValue = exports.handleRowFirstValue = function(result) { if ( result.rows.length > 0 ) { return result.rows[0].value; } else { return new HTTPResult(404,{error:"No document found."}); } }; var handleRowDictOfDocs = exports.handleRowDictOfDocs = function(result) { return _.reduce(result.rows, function(dict,item) { dict[item.key] = item.doc; return dict; }, {}); }; var handleRowDictOfValues = exports.handleRowDictOfValues = function(result) { return _.reduce(result.rows, function(dict,item) { dict[item.key] = item.value; return dict; }, {}); }; var getDocumentsOfType = exports.getDocumentsOfType = function(type) { return couch.get('_design/default/_view/by_type', {query:{reduce:false,include_docs:true,key:type}}) .handle({ '-1': _.identity, 200: handleRowDocs, 404: function() { return {error: "Cannot find collection of type "+type}; }, default: handleUnknownResponse }); }; var getDocument = exports.getDocument = function(id,rev,type) { var opts = {headers:{}}; if (rev) { opts.headers['If-Non-Match'] = '"'+rev+'"'; } return couch.get(id,opts) .handle({ '-1': _.identity, 200: function(doc){ if ( doc.type !== type ) { return new HTTPResult(404,{error:"Document not found."}); } else { var priv = stripAndReturnPrivates(doc); if ( priv._rev !== rev ) { doc._id = priv._id; doc._rev = priv._rev; return doc; } else { return new HTTPResult(304); } } }, 304: identity, default: handleUnknownResponse }); }; var putDocument = exports.putDocument = function(id,rev,type,doc) { var priv = stripAndReturnPrivates(doc); var valid; if ( doc.type === type && (valid = validator(doc, schema)).valid ) { var opts = rev ? {headers:{'If-Match':'"'+rev+'"'}} : {}; return couch.put(id,doc,opts) .handle({ '-1': _.identity, 201: function(res){ doc._id = res.id; doc._rev = res.rev; return doc; }, 409: function(error) { return {error: error.reason}; }, default: handleUnknownResponse }); } else { return new HTTPResult(400,{error: "Document failed schema verification.", valid: valid}); } }; var deleteDocument = exports.deleteDocument = function(id,rev) { if ( rev ) { var opts = {headers:{'If-Match':'"'+rev+'"'}}; return couch.delete(id,opts) .handle({ '-1': _.identity, 200: identity, 409: function(error) { return {error: error.reason}; }, default: handleUnknownResponse }); } else { return new HTTPResult(409, {error: "Cannot identify document revision to delete."}); } }; var postDocument = exports.postDocument = function(type,doc) { var priv = stripAndReturnPrivates(doc); var valid; if ( doc.type === type && (valid = validator(doc, schema)).valid ) { return couch.post(doc) .handle({ '-1': _.identity, 201: function(response) { doc._id = response.id; doc._rev = response.rev; return doc; }, default: handleUnknownResponse }); } else { return new HTTPResult(400,{error: "Document failed schema verification.", valid: valid}); } }; var makeDocsGet = exports.makeDocsGet = function(type) { return function(req,res) { getDocumentsOfType(type) .handle({'-1': handleException}) .handle(res.send.bind(res)); }; }; var makeDocGet_id = exports.makeDocGet_id = function(type) { return function(req,res) { var id = req.params.id; var rev = etags.parse(req.header('If-Non-Match'))[0]; getDocument(id,rev,type) .handle({ '-1': function(result) { handleException(result) .handle(res.send.bind(res)); }, 200: function(doc){ res.set({ 'ETag': etags.format([doc._rev]) }).send(200, doc); }, default: res.send.bind(res) }); }; }; var makeDocPut_id = exports.makeDocPut_id = function(type) { return function(req,res) { var id = req.params.id; var doc = req.body; var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev); putDocument(id,rev,type,doc) .handle({ '-1': function(result) { handleException(result) .handle(res.send.bind(res)); }, 201: function(doc) { res.set({ 'ETag': etags.format([doc._rev]) }).send(201, doc); }, default: res.send.bind(res) }); }; }; var makeDocDel_id = exports.makeDocDel_id = function(type) { return function(req,res) { var id = req.params.id; var doc = req.body; var rev = etags.parse(req.header('If-Match'))[0] || (doc && doc._rev); deleteDocument(id,rev) .handle({'-1': handleException}) .handle(res.send.bind(res)); }; }; var makeDocPost = exports.makeDocPost = function(type) { return function(req,res) { var doc = req.body; postDocument(type,doc) .handle({ '-1': function(result) { handleException(result) .handle(res.send.bind(res)); }, 201: function(doc) { res.set({ 'ETag': etags.format([doc._rev]) }).send(201, doc); }, default: res.send.bind(res) }); }; }; var JSON_MIME = exports.JSON_MIME = 'application/json'; var CSV_MIME = exports.CSV_MIME = 'application/json'; var ensureMIME = exports.ensureMIME = function(mimeType) { return function(req,res,next) { if (!req.accepts(mimeType)) { res.send(406); } else { res.set({ 'Content-Type': mimeType }); next(); } }; }; var writeObjectsToCSVStream = exports.writeObjectsToCSVStream = function(objects, stream, prelude) { var keys = _.chain(objects) .map(_.keys) .flatten() .uniq() .value(); var idxs = {}; _.forEach(keys, function(key,idx){ idxs[key] = idx; }); var writer = new CSV.CsvWriter(stream); if ( prelude ) { _.forEach(prelude, function(val,key){ writer.writeRecord([key,val]); }); } writer.writeRecord(keys); _.forEach(objects, function(obj){ var row = []; _.forEach(obj, function(val,key){ row[idxs[key]] = val; }); writer.writeRecord(row); }); }; return exports; };