var pull = require('pull-stream') var paramap = require('pull-paramap') var asyncMemo = require('asyncmemo') var issueSchemas = require('./lib/schemas') function Cache(fn, ssb) { return asyncMemo(fn) return function (key, cb) { ac.get(key, cb) } } function isUpdateValid(issue, msg) { return msg.value.author == issue.author && msg.value.author == issue.projectAuthor } exports.name = 'issues' exports.manifest = { get: 'async', createFeedStream: 'source', new: 'async', edit: 'async', close: 'async', reopen: 'async' } exports.schemas = issueSchemas exports.init = function (ssb) { var ssbGet = asyncMemo(ssb.get) var getIssue = asyncMemo(function (id, cb) { var issue = {} var issueMsg if (id && id.value && id.key) { var msg = id id = id.key gotIssueMsg(null, msg) } else { ssbGet(id, gotIssueMsg) } function gotIssueMsg(err, msg) { if (err) return cb(err) issueMsg = msg issue.id = msg.key issue.author = msg.value.author var c = msg.value.content issue.project = c.project issue.text = c.text issue.created_at = issue.updated_at = msg.value.timestamp if (c.project) ssbGet(c.project, gotProjectMsg) else getLinks() } function gotProjectMsg(err, msg) { if (err) return cb(err) issue.projectAuthor = msg.author getLinks() } function getLinks() { var now = Date.now() // compute the result from the past data pull( ssb.links({dest: id, lt: now, reverse: true}), pull.drain(onOldMsg, onOldEnd) ) // keep the results up-to-date in the future pull( ssb.links({dest: id, gte: now, values: true, live: true}), pull.drain(onNewMsg, onNewEnd) ) } function onOldMsg(msg) { if (!isUpdateValid(issue, msg)) return var c = msg.value.content if (c.open != null && issue.open == null) issue.open = c.open if (c.title != null && issue.title == null) issue.title = c.title if (msg.value.timestamp > issue.updated_at) issue.updated_at = msg.value.timestamp checkReady() } function onNewMsg(msg) { if (!isUpdateValid(issue, msg)) return var c = msg.value.content if (c.open != null) issue.open = c.open if (c.title != null) issue.title = c.title if (msg.value.timestamp > issue.updated_at) issue.updated_at = msg.value.timestamp } function checkReady() { // call back once all the issue properties are set if (issue.open != null && issue.title != null) { var _cb = cb delete cb _cb(null, issue) } } function onOldEnd(err) { if (err) { if (cb) cb(err) else console.error(err) return } // process the root message last onOldMsg(issueMsg) // if callback hasn't been called yet, the issue is missing a field if (cb) { if (issue.open == null) issue.open = true if (issue.title == null) issue.title = issue.id checkReady() } } function onNewEnd(err) { if (err) { if (cb) cb(err) else console.error(err) } } }) function createFeedStream(opts) { opts.type = 'issue' delete opts.limit return pull( // TODO: use links2 for this ssb.messagesByType(opts), pull.filter(function (msg) { return (!opts.project || opts.project == msg.value.content.project) && (!opts.author || opts.author == msg.value.author) }), paramap(getIssue, 8) ) } function editIssue(id, opts, cb) { ssb.publish(issueSchemas.edit(id, opts), cb) } function closeIssue(id, cb) { ssb.publish(issueSchemas.close(id), cb) } function reopenIssue(id, cb) { ssb.publish(issueSchemas.reopen(id), cb) } function newIssue(opts, cb) { var msg = issueSchemas.new(opts.project, opts.title, opts.text) ssb.publish(msg, function (err, msg) { if (err) return cb(err) getIssue(msg, cb) }) } return { get: getIssue, createFeedStream: createFeedStream, new: newIssue, edit: editIssue, close: closeIssue, reopen: reopenIssue } }