Files: c46ad9c39623cd98b6cbb308d0c0870f15ff701c / poll / obs / get.js
4985 bytesRaw
1 | const pull = require('pull-stream') |
2 | const sort = require('ssb-sort') |
3 | const { Struct, Value, Array: MutantArray, computed, resolve } = require('mutant') |
4 | const getContent = require('ssb-msg-content') |
5 | const { isPoll, isPosition, isChooseOnePoll, isPollUpdate, isChooseOnePosition } = require('ssb-poll-schema') |
6 | isPoll.chooseOne = isChooseOnePoll |
7 | isPosition.chooseOne = isChooseOnePosition |
8 | const buildResults = require('../../results/sync/buildResults') |
9 | |
10 | module.exports = function (server) { |
11 | return function get (key) { |
12 | const myKey = server.id |
13 | |
14 | const positions = MutantArray([]) |
15 | const closingTimes = MutantArray([]) |
16 | const sortedClosingTimes = computed(closingTimes, sort) |
17 | const sortedPositions = computed(positions, sort) |
18 | const myPosition = computed(sortedPositions, (positions) => { |
19 | const myPositions = positions.filter(position => position.value.author === myKey) |
20 | return myPositions.pop() |
21 | }) |
22 | const closesAt = computed(sortedClosingTimes, (times) => { |
23 | const time = times.pop() |
24 | return time ? time.value.content.closesAt : '' |
25 | }) |
26 | |
27 | // This is ugly, would all be done by pollDoc. Refactor in the future. |
28 | const pollObs = Value({}) |
29 | |
30 | const results = computed(sortedPositions, (positions) => { |
31 | const resultsErrors = buildResults({ poll: resolve(pollObs), positions }) |
32 | return resultsErrors ? resultsErrors.results : [] |
33 | }) |
34 | |
35 | const errors = computed(sortedPositions, (positions) => { |
36 | const resultsErrors = buildResults({ poll: resolve(pollObs), positions }) |
37 | if (resultsErrors && resultsErrors.errors) { } |
38 | return resultsErrors ? resultsErrors.errors : [] |
39 | }) |
40 | |
41 | function PollDoc (poll) { |
42 | poll = poll || { |
43 | key: '', |
44 | value: { |
45 | author: '', |
46 | content: { |
47 | title: '', |
48 | body: '', |
49 | channel: '', |
50 | details: { |
51 | type: 'UNKNOWN' |
52 | } |
53 | }, |
54 | recps: [], |
55 | mentions: [] |
56 | } |
57 | } |
58 | |
59 | return Struct(Object.assign({}, decoratePoll(poll), { |
60 | sync: false, |
61 | closesAt, |
62 | myPosition, |
63 | positions: sortedPositions, |
64 | results, |
65 | errors |
66 | })) |
67 | } |
68 | |
69 | const pollDoc = PollDoc() |
70 | |
71 | server.get(key, (err, value) => { |
72 | if (err) return err |
73 | |
74 | var poll = { key, value } |
75 | if (!isPoll(poll)) return new Error('scuttle-poll could not get poll, key provided was not a valid poll key') |
76 | |
77 | // give subscribers a chance to start listening so they don't miss updates. |
78 | setImmediate(function () { |
79 | const decoratedPoll = decoratePoll(poll) |
80 | pollObs.set(decoratedPoll) |
81 | |
82 | Object.keys(decoratedPoll).forEach(function (key) { |
83 | pollDoc[key].set(decoratedPoll[key]) |
84 | }) |
85 | |
86 | pull( |
87 | createBacklinkStream(key, {live: false, old: true}), |
88 | pull.collect((err, refs) => { |
89 | if (!err) { |
90 | const sorted = sort(refs) |
91 | const decoratedPosition = sorted |
92 | .filter(isPosition) |
93 | .map(DecoratePosition(poll)) |
94 | |
95 | positions.set(decoratedPosition) |
96 | // push in the closing time from the poll object and then update if there are updates published. |
97 | closingTimes.push(poll) |
98 | |
99 | setImmediate(() => pollDoc.sync.set(true)) |
100 | } |
101 | }) |
102 | ) |
103 | |
104 | pull( |
105 | createBacklinkStream(key, {old: false, live: true}), |
106 | pull.filter(isPosition), |
107 | pull.map(DecoratePosition(poll)), |
108 | pull.drain(positions.push) |
109 | ) |
110 | |
111 | pull( |
112 | createBacklinkStream(key, {old: true, live: true}), |
113 | pull.filter(isPollUpdate), |
114 | pull.drain(closingTimes.push) |
115 | ) |
116 | }) |
117 | }) |
118 | return pollDoc |
119 | } |
120 | |
121 | function createBacklinkStream (key, opts) { |
122 | opts = opts || { |
123 | live: true, |
124 | old: true |
125 | } |
126 | |
127 | var filterQuery = { |
128 | $filter: { |
129 | dest: key |
130 | } |
131 | } |
132 | |
133 | return server.backlinks.read(Object.assign({ |
134 | query: [filterQuery], |
135 | index: 'DTA' // use asserted timestamps |
136 | }, opts)) |
137 | } |
138 | } |
139 | |
140 | function decoratePoll (rawPoll) { |
141 | const { |
142 | author, |
143 | content: { |
144 | title, |
145 | body, |
146 | channel, |
147 | details: { type } |
148 | }, |
149 | recps, |
150 | mentions |
151 | } = rawPoll.value |
152 | |
153 | const poll = Object.assign({}, rawPoll, { |
154 | type, |
155 | author, |
156 | title, |
157 | body, |
158 | channel, |
159 | recps, |
160 | mentions |
161 | }) |
162 | |
163 | return poll |
164 | } |
165 | |
166 | function DecoratePosition (poll) { |
167 | return (position) => decoratePosition({position, poll}) |
168 | } |
169 | |
170 | function decoratePosition ({position: rawPosition, poll: rawPoll}) { |
171 | var position = getContent(rawPosition) |
172 | var poll = getContent(rawPoll) |
173 | |
174 | // NOTE this isn't deep enough to be a safe clone |
175 | var newPosition = Object.assign({}, rawPosition) |
176 | newPosition.reason = position.reason |
177 | |
178 | if (isPoll.chooseOne(poll)) { |
179 | var choiceIndex = position.details.choice |
180 | newPosition.choice = poll.details.choices[choiceIndex] |
181 | } |
182 | return newPosition |
183 | } |
184 |
Built with git-ssb-web