Skip to content

Commit

Permalink
Merge pull request #28 from eHealthAfrica/diff-user-doc
Browse files Browse the repository at this point in the history
Diff user doc
  • Loading branch information
jo committed Dec 28, 2015
2 parents 3fa24a4 + 11be415 commit 2c8fc86
Show file tree
Hide file tree
Showing 7 changed files with 314 additions and 219 deletions.
5 changes: 3 additions & 2 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ var options = minimist(process.argv.slice(2), {
boolean: ['index', 'multipart', 'watch']
})
if (!options._.length) {
return console.log('Usage: \ncouchdb-push URL [SOURCE] [OPTIONS]')
console.log('Usage: \ncouchdb-push URL [SOURCE] [OPTIONS]')
process.exit()
}

var db = options._[0]
var source = options._[1] || process.cwd()

push(db, source, options, function(error, response) {
push(db, source, options, function (error, response) {
if (error) return console.error(error)

console.log(JSON.stringify(response, null, ' '))
Expand Down
176 changes: 105 additions & 71 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,140 +1,174 @@
// couchdb-push
// (c) 2014 Johannes J. Schmidt

var crypto = require('crypto');
var assert = require('assert');
var async = require('async');
var nanoOption = require('nano-option');
var compile = require('couchdb-compile');
var ensure = require('couchdb-ensure');
var chokidar = require('chokidar');


module.exports = function push(db, source, options, callback) {
var crypto = require('crypto')
var async = require('async')
var omit = require('lodash/object/omit')
var isEqual = require('lodash/lang/isEqual')
var nanoOption = require('nano-option')
var compile = require('couchdb-compile')
var ensure = require('couchdb-ensure')
var chokidar = require('chokidar')

module.exports = function push (db, source, options, callback) {
if (typeof options === 'function') {
callback = options;
options = {};
callback = options
options = {}
}
options = options || {};
options = options || {}

try {
db = nanoOption(db);
} catch(e) {
return callback({ error: 'invalid_db', reason: 'Not a valid database: ' + url });
db = nanoOption(db)
} catch (e) {
return callback({ error: 'invalid_db', reason: 'Not a valid database: ' + db })
}

if (!db.config.db) {
return callback({ error: 'no_db', reason: 'Not a database: ' + db });
return callback({ error: 'no_db', reason: 'Not a database: ' + db })
}

function pushDoc(doc, attachments, done) {
function pushDoc (doc, attachments, done) {
if (options.multipart && attachments.length) {
db.multipart.insert(doc, attachments, doc._id, done);
db.multipart.insert(doc, attachments, doc._id, done)
} else {
db.insert(doc, doc._id, done);
db.insert(doc, doc._id, done)
}
}

function diffAttachment(attachment, existingAttachment) {
function diffAttachment (attachment, existingAttachment) {
if (!existingAttachment) {
return false;
return false
}

var md5sum = crypto.createHash('md5');
var data = options.multipart ? attachment.data : new Buffer(attachment.data, 'base64');
md5sum.update(data);
var digest = 'md5-' + md5sum.digest('base64');
var md5sum = crypto.createHash('md5')
var data = options.multipart ? attachment.data : new Buffer(attachment.data, 'base64')
md5sum.update(data)
var digest = 'md5-' + md5sum.digest('base64')

return existingAttachment.digest === digest;
return existingAttachment.digest === digest
}

function diffDoc(doc, existingDoc, attachments, done) {
doc._rev = existingDoc._rev;
function diffDoc (doc, existingDoc, attachments, done) {
doc._rev = existingDoc._rev

if (options.multipart) {
if (attachments.length) {
for (var i = 0; i < attachments.length; i++) {
var name = attachments[i].name;
var identical = diffAttachment(attachments[i], existingDoc && existingDoc._attachments && existingDoc._attachments[name]);
var name = attachments[i].name
var identical = diffAttachment(attachments[i], existingDoc && existingDoc._attachments && existingDoc._attachments[name])

if (identical) {
doc._attachments = doc._attachments || {};
doc._attachments[name] = existingDoc._attachments[name];
attachments.splice(i--, 1);
doc._attachments = doc._attachments || {}
doc._attachments[name] = existingDoc._attachments[name]
attachments.splice(i--, 1)
}
};
}
}
} else {
if (doc._attachments) {
Object.keys(doc._attachments).forEach(function(name) {
var identical = diffAttachment(doc._attachments[name], existingDoc && existingDoc._attachments && existingDoc._attachments[name]);
Object.keys(doc._attachments).forEach(function (name) {
var identical = diffAttachment(doc._attachments[name], existingDoc && existingDoc._attachments && existingDoc._attachments[name])

if (identical) {
doc._attachments[name] = existingDoc._attachments[name];
doc._attachments[name] = existingDoc._attachments[name]
}
});
})
}
}

try {
assert.deepEqual(doc, existingDoc);
if (options.multipart) {
assert.equal(attachments.length, 0);
}
// cannot diff multipart attachments
if (options.multipart && attachments.length > 0) {
return pushDoc(doc, attachments, done)
}

hasChanged(doc, existingDoc, function (error, changed) {
if (error) return done(error)

if (changed) return pushDoc(doc, attachments, done)

done(null, {
ok: true,
id: doc._id,
rev: doc._rev,
unchanged: true
})
})
}

done(null, { ok: true, id: doc._id, rev: doc._rev, unchanged: true });
} catch(e) {
pushDoc(doc, attachments, done);
function hasChanged (doc, existingDoc, callback) {
if (isUserDoc(doc) && doc.name && doc.password) {
confirmSession(doc.name, doc.password, function (error, result) {
if (error) {
if (error.statusCode === 401) return callback(null, true)
return callback(error)
}

var userDocToCompare = omit(doc, 'password')
var existingDocToCompare = omit(existingDoc, 'derived_key', 'iterations', 'password_scheme', 'salt')

callback(null, !isEqual(userDocToCompare, existingDocToCompare))
})
} else {
callback(null, !isEqual(doc, existingDoc))
}
}

function getDoc(doc, attachments, done) {
db.get(doc._id, function(err, response) {
// TChecking against `_users` is not acurate, because the users db can be configured:
// [couch_httpd_auth]
// authentication_db = _users
function isUserDoc (doc) {
return db.config.db === '_users'
}

function confirmSession (name, password, done) {
db.auth(name, password, done)
}

function getDoc (doc, attachments, done) {
db.get(doc._id, function (err, response) {
if (err && err.statusCode === 404) {
return pushDoc(doc, attachments, done);
return pushDoc(doc, attachments, done)
}

diffDoc(doc, response, attachments, done);
diffDoc(doc, response, attachments, done)
})
}


function compileDoc(done) {
compile(source, options, function(err, doc, attachments) {
function compileDoc (done) {
compile(source, options, function (err, doc, attachments) {
if (err) {
return done(err);
return done(err)
}

if (!doc._id) {
return done({ error: 'missing_id', reason: 'Missing _id property' });
return done({ error: 'missing_id', reason: 'Missing _id property' })
}

attachments = attachments || [];
attachments = attachments || []

getDoc(doc, attachments, done);
});
getDoc(doc, attachments, done)
})
}


ensure(db, function(error) {
ensure(db, function (error) {
if (error) {
return callback(error);
return callback(error)
}

if (options.watch) {
var queue = async.queue(function(task, done) {
compileDoc(function(error, response) {
error ? console.error(error) :
console.log(JSON.stringify(response, null, ' '))
var queue = async.queue(function (task, done) {
compileDoc(function (error, response) {
error ? console.error(error)
: console.log(JSON.stringify(response, null, ' '))
done(error)
})
}, 1)

chokidar
.watch(source, { ignoreInitial: true })
.on('all', queue.push);
.on('all', queue.push)
}
compileDoc(callback);
});
};

compileDoc(callback)
})
}
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"main": "index.js",
"bin": "cli.js",
"scripts": {
"pretest": "standard",
"test": "tap test/*.js",
"semantic-release": "semantic-release pre && npm publish && semantic-release post"
},
Expand All @@ -27,12 +28,14 @@
"chokidar": "^1.2.0",
"couchdb-compile": "^1.6.2",
"couchdb-ensure": "^1.3.1",
"lodash": "^3.10.1",
"minimist": "^1.2.0",
"nano-option": "^1.1.1"
},
"devDependencies": {
"tap": "^2.1.1",
"nano": "^6.1.5",
"semantic-release": "^6.0.3"
"semantic-release": "^6.0.3",
"standard": "^5.4.1",
"tap": "^2.1.1"
}
}
Loading

0 comments on commit 2c8fc86

Please sign in to comment.