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