git ssb

0+

cel / ssb-issues



Tree: d1c35a56ec4179d01c63cbaea8d6934445b0eca9

Files: d1c35a56ec4179d01c63cbaea8d6934445b0eca9 / index.js

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

Built with git-ssb-web