Files: ea7483671e4c557ff8a2d71717148f1ea356c1f4 / index.js
6563 bytesRaw
1 | var pull = require('pull-stream') |
2 | var paramap = require('pull-paramap') |
3 | var asyncMemo = require('asyncmemo') |
4 | var issueSchemas = require('./lib/schemas') |
5 | var multicb = require('multicb') |
6 | |
7 | function Cache(fn, ssb) { |
8 | return asyncMemo(fn) |
9 | return function (key, cb) { ac.get(key, cb) } |
10 | } |
11 | |
12 | function isUpdateValid(issue, msg) { |
13 | return msg.value.author == issue.author |
14 | || msg.value.author == issue.projectAuthor |
15 | } |
16 | |
17 | function truncate(str, len) { |
18 | return str.length > len ? str.substr(0, len) + '...' : str |
19 | } |
20 | |
21 | exports.name = 'issues' |
22 | |
23 | exports.manifest = { |
24 | get: 'async', |
25 | list: 'source', |
26 | new: 'async', |
27 | edit: 'async', |
28 | close: 'async', |
29 | reopen: 'async', |
30 | getMention: 'sync', |
31 | isStatusChanged: 'sync' |
32 | } |
33 | |
34 | exports.schemas = issueSchemas |
35 | |
36 | function isStatusChanged(msg, issue) { |
37 | var mention = getMention(msg, issue) |
38 | return mention ? mention.open : null |
39 | } |
40 | |
41 | function getMention(msg, issue) { |
42 | var c = msg.value.content |
43 | if (msg.key == issue.id || c.issue == issue.id || c.link == issue.id) |
44 | if (c.open != null) |
45 | return c |
46 | if (c.issues) { |
47 | var mention |
48 | for (var i = 0; i < c.issues.length; i++) { |
49 | mention = getMention({value: { |
50 | timestamp: msg.value.timestamp, |
51 | author: msg.value.author, |
52 | content: c.issues[i] |
53 | }}, issue) |
54 | if (mention) |
55 | return mention |
56 | } |
57 | } |
58 | } |
59 | |
60 | exports.init = function (ssb) { |
61 | |
62 | var ssbGet = asyncMemo(ssb.get) |
63 | var liveStreams = [] |
64 | |
65 | var getIssue = asyncMemo(function (id, cb) { |
66 | var issue = {} |
67 | var issueMsg |
68 | |
69 | ssbGet(id, function (err, msg) { |
70 | msg = {key: id, value: msg} |
71 | if (err) return cb(err) |
72 | issueMsg = msg |
73 | issue.id = msg.key |
74 | issue.msg = msg |
75 | issue.author = msg.value.author |
76 | var c = msg.value.content |
77 | issue.project = c.project |
78 | issue.text = String(c.text) |
79 | issue.created_at = issue.updated_at = msg.value.timestamp |
80 | if (c.project) |
81 | ssbGet(c.project, gotProjectMsg) |
82 | else |
83 | getLinks() |
84 | }) |
85 | |
86 | function gotProjectMsg(err, msg) { |
87 | if (err) return cb(err) |
88 | issue.projectAuthor = msg.author |
89 | getLinks() |
90 | } |
91 | |
92 | function getLinks() { |
93 | var now = Date.now() |
94 | // compute the result from the past data |
95 | pull( |
96 | ssb.links({dest: id, reverse: true, values: true, |
97 | old: true, live: false, sync: false}), |
98 | pull.drain(onOldMsg, onOldEnd) |
99 | ) |
100 | // keep the results up-to-date in the future |
101 | var read = ssb.links({dest: id, values: true, |
102 | old: false, live: true, sync: false}) |
103 | liveStreams.push(read) |
104 | pull( |
105 | read, |
106 | pull.drain(onNewMsg, onNewEnd) |
107 | ) |
108 | } |
109 | |
110 | function onOldMsg(msg) { |
111 | if (!msg.value || !isUpdateValid(issue, msg)) |
112 | return |
113 | var c = msg.value.content |
114 | |
115 | // handle updates to issue |
116 | if (msg.key == id || c.issue == id || c.link == id) { |
117 | if (c.open != null && issue.open == null) |
118 | issue.open = c.open |
119 | if (c.title != null && issue.title == null) |
120 | issue.title = c.title |
121 | if (msg.value.timestamp > issue.updated_at) |
122 | issue.updated_at = msg.value.timestamp |
123 | } |
124 | |
125 | // handle updates via mention |
126 | if (c.issues) { |
127 | for (var i = 0; i < c.issues.length; i++) |
128 | onOldMsg({value: { |
129 | timestamp: msg.value.timestamp, |
130 | author: msg.value.author, |
131 | content: c.issues[i] |
132 | }}) |
133 | } |
134 | |
135 | checkReady() |
136 | } |
137 | |
138 | function onNewMsg(msg) { |
139 | if (!msg.value || !isUpdateValid(issue, msg)) |
140 | return |
141 | var c = msg.value.content |
142 | |
143 | // handle updates to issue |
144 | if (msg.key == id || c.issue == id || c.link == id) { |
145 | if (c.open != null) |
146 | issue.open = c.open |
147 | if (c.title != null) |
148 | issue.title = c.title |
149 | if (msg.value.timestamp > issue.updated_at) |
150 | issue.updated_at = msg.value.timestamp |
151 | } |
152 | |
153 | // handle updates via mention |
154 | if (c.issues) { |
155 | for (var i = 0; i < c.issues.length; i++) |
156 | onNewMsg({value: { |
157 | timestamp: msg.value.timestamp, |
158 | author: msg.value.author, |
159 | content: c.issues[i] |
160 | }}) |
161 | } |
162 | } |
163 | |
164 | function checkReady() { |
165 | // call back once all the issue properties are set |
166 | if (issue.open != null && issue.title != null) { |
167 | var _cb = cb |
168 | delete cb |
169 | _cb(null, issue) |
170 | } |
171 | } |
172 | |
173 | function onOldEnd(err) { |
174 | if (err) { |
175 | if (cb) cb(err) |
176 | else console.error(err) |
177 | return |
178 | } |
179 | // process the root message last |
180 | onOldMsg(issueMsg) |
181 | // if callback hasn't been called yet, the issue is missing a field |
182 | if (cb) { |
183 | if (issue.open == null) |
184 | issue.open = true |
185 | if (issue.title == null) |
186 | issue.title = truncate(issue.text, 40) || issue.id |
187 | checkReady() |
188 | } |
189 | } |
190 | |
191 | function onNewEnd(err) { |
192 | if (err) { |
193 | if (cb) cb(err) |
194 | else console.error(err) |
195 | } |
196 | } |
197 | }) |
198 | |
199 | function deinit(cb) { |
200 | var done = multicb() |
201 | // cancel all live streams |
202 | liveStreams.forEach(function (read) { |
203 | read(true, done()) |
204 | }) |
205 | done(cb) |
206 | } |
207 | |
208 | function listIssues(opts) { |
209 | opts.type = 'issue' |
210 | return pull( |
211 | // TODO: use links2 for this |
212 | ssb.messagesByType(opts), |
213 | pull.filter(function (msg) { |
214 | return (!opts.project || opts.project == msg.value.content.project) |
215 | && (!opts.author || opts.author == msg.value.author) |
216 | }), |
217 | paramap(function (msg, cb) { |
218 | getIssue(msg.key, cb) |
219 | }, 8), |
220 | pull.filter(opts.open != null && function (pr) { |
221 | return pr.open == opts.open |
222 | }) |
223 | ) |
224 | } |
225 | |
226 | function editIssue(id, opts, cb) { |
227 | var msg |
228 | try { ssb.publish(issueSchemas.edit(id, opts), cb) } |
229 | catch(e) { return cb(e) } |
230 | } |
231 | |
232 | function closeIssue(id, cb) { |
233 | var msg |
234 | try { ssb.publish(issueSchemas.close(id), cb) } |
235 | catch(e) { return cb(e) } |
236 | } |
237 | |
238 | function reopenIssue(id, cb) { |
239 | var msg |
240 | try { msg = issueSchemas.reopen(id) } |
241 | catch(e) { return cb(e) } |
242 | ssb.publish(msg, cb) |
243 | } |
244 | |
245 | function newIssue(opts, cb) { |
246 | var msg |
247 | try { msg = issueSchemas.new(opts.project, opts.title, opts.text) } |
248 | catch(e) { return cb(e) } |
249 | ssb.publish(msg, function (err, msg) { |
250 | if (err) return cb(err) |
251 | getIssue(msg.key, cb) |
252 | }) |
253 | } |
254 | |
255 | return { |
256 | deinit: deinit, |
257 | get: getIssue, |
258 | list: listIssues, |
259 | new: newIssue, |
260 | edit: editIssue, |
261 | close: closeIssue, |
262 | reopen: reopenIssue, |
263 | getMention: getMention, |
264 | isStatusChanged: isStatusChanged |
265 | } |
266 | } |
267 |
Built with git-ssb-web