nodebb-plugin-poll 3.1.8 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/poll.js CHANGED
@@ -1,312 +1,226 @@
1
1
  'use strict';
2
2
 
3
- const async = require('async');
4
-
5
3
  const NodeBB = require('./nodebb');
6
4
 
7
5
  const Vote = require('./vote');
8
6
  const Scheduler = require('./scheduler');
9
7
 
10
- (function (Poll) {
11
- const POLL_DATA_VERSION = 2;
12
-
13
- Poll.add = function (pollData, postData, callback) {
14
- NodeBB.db.incrObjectField('global', 'nextPollId', (err, pollId) => {
15
- if (err) {
16
- return callback(err);
17
- }
18
-
19
- const poll = {
20
- pollId: pollId,
21
- uid: postData.editor || postData.uid,
22
- tid: postData.tid,
23
- pid: postData.pid,
24
- deleted: 0,
25
- ended: 0,
26
- timestamp: postData.edited || postData.timestamp,
27
- version: POLL_DATA_VERSION,
28
- };
29
-
30
- pollData.options = pollData.options.map((val, i) => ({
31
- id: i,
32
- title: val,
33
- }));
34
-
35
- // Save asynchronously
36
- async.parallel([
37
- async.apply(async.series, [
38
- // Save poll options in parallel
39
- async.apply(
40
- async.parallel,
41
- pollData.options.map(
42
- option => async.apply(NodeBB.db.setObject, `poll:${pollId}:options:${option.id}`, option)
43
- ),
44
- ),
45
- // Add poll option id to option list synchronously
46
- // To avoid race condition causing duplicate error
47
- async.apply(
48
- async.series,
49
- pollData.options.map(
50
- option => async.apply(NodeBB.db.setAdd, `poll:${pollId}:options`, option.id)
51
- ),
52
- ),
53
- ]),
54
- async.apply(NodeBB.db.setObject, `poll:${pollId}`, poll),
55
- async.apply(NodeBB.db.setObject, `poll:${pollId}:settings`, pollData.settings),
56
- async.apply(NodeBB.db.listAppend, 'polls', pollId),
57
- async.apply(NodeBB.db.setObjectField, `topic:${poll.tid}`, 'pollId', pollId),
58
- ], (err) => {
59
- if (err) {
60
- return callback(err);
61
- }
62
-
63
- // Check if this poll is scheduled to end
64
- if (parseInt(pollData.settings.end, 10) > 0) {
65
- Poll.schedule(pollId);
66
- }
67
-
68
- return callback(null, pollId);
69
- });
70
- });
71
- };
72
-
73
- Poll.get = function (pollId, uid, withVotes, callback) {
74
- async.parallel({
75
- info: function (next) {
76
- Poll.getInfo(pollId, next);
77
- },
78
- options: function (next) {
79
- Poll.getOptions(pollId, withVotes, next);
80
- },
81
- settings: function (next) {
82
- Poll.getSettings(pollId, next);
83
- },
84
- hasVoted: function (next) {
85
- if (uid) {
86
- Vote.hasUidVoted(uid, pollId, next);
87
- } else {
88
- next(null, false);
89
- }
90
- },
91
- vote: function (next) {
92
- if (uid) {
93
- Vote.getUidVote(uid, pollId, next);
94
- } else {
95
- next(null, null);
96
- }
97
- },
98
- }, (err, pollData) => {
99
- if (err) {
100
- return callback(err);
101
- }
102
- pollData.options.forEach((option) => {
103
- const percentage = ((option.voteCount / pollData.info.voteCount) * 100).toFixed(0);
104
- option.percentage = isNaN(percentage) ? 0 : percentage;
105
- });
106
- pollData.settings.disallowVoteUpdate = parseInt(pollData.settings.disallowVoteUpdate, 10);
107
- callback(null, pollData);
108
- });
109
- };
110
-
111
- Poll.getPollIdByTid = function (tid, callback) {
112
- NodeBB.db.getObjectField(`topic:${tid}`, 'pollId', callback);
113
- };
114
-
115
- Poll.getPollIdByPid = function (pid, callback) {
116
- NodeBB.db.getObjectField(`post:${pid}`, 'pollId', callback);
117
- };
118
-
119
- Poll.getInfo = function (pollId, callback) {
120
- async.parallel({
121
- poll: function (next) {
122
- NodeBB.db.getObject(`poll:${pollId}`, next);
123
- },
124
- voteCount: function (next) {
125
- Poll.getVoteCount(pollId, next);
126
- },
127
- }, (err, results) => {
128
- if (err) {
129
- return callback(err);
130
- }
131
- results.poll.voteCount = parseInt(results.voteCount, 10) || 0;
132
- callback(null, results.poll);
133
- });
134
- };
135
-
136
- Poll.getOptions = function (pollId, withVotes, callback) {
137
- if (typeof withVotes === 'function') {
138
- callback = withVotes;
139
- withVotes = false;
140
- }
141
-
142
- NodeBB.db.getSetMembers(`poll:${pollId}:options`, (err, options) => {
143
- if (err) {
144
- return callback(err);
145
- }
146
- // sort options according to option id. option id are supposed to preseve the order as in Poll.add(),
147
- // when the user create the poll
148
- options.sort((a, b) => a - b);
149
- async.map(options, (option, next) => {
150
- Poll.getOption(pollId, option, withVotes, next);
151
- }, callback);
152
- });
153
- };
154
-
155
- Poll.getOption = function (pollId, option, withVotes, callback) {
156
- async.parallel({
157
- option: function (next) {
158
- NodeBB.db.getObject(`poll:${pollId}:options:${option}`, next);
159
- },
160
- votes: function (next) {
161
- if (withVotes) {
162
- NodeBB.db.getSortedSetRange(`poll:${pollId}:options:${option}:votes`, 0, -1, next);
163
- } else {
164
- next();
165
- }
166
- },
167
- voteCount: function (next) {
168
- Poll.getOptionVoteCount(pollId, option, next);
169
- },
170
- }, (err, results) => {
171
- if (err) {
172
- return callback(err);
173
- }
174
- if (results.votes) {
175
- results.option.votes = results.votes;
176
- }
177
- results.option.voteCount = parseInt(results.voteCount, 10) || 0;
178
-
179
- callback(null, results.option);
180
- });
181
- };
182
-
183
- Poll.getVotersCount = function (pollId, callback) {
184
- NodeBB.db.sortedSetCard(`poll:${pollId}:voters`, callback);
185
- };
186
-
187
- Poll.getVoteCount = function (pollId, callback) {
188
- NodeBB.db.getSetMembers(`poll:${pollId}:options`, (err, options) => {
189
- if (err) {
190
- console.error(err);
191
- }
192
-
193
- async.map(options, (option, next) => {
194
- Poll.getOptionVoteCount(pollId, option, next);
195
- }, (err, results) => {
196
- if (err) {
197
- return callback(err);
198
- }
199
- callback(null, results.reduce((a, b) => a + b, 0));
200
- });
201
- });
202
- };
203
-
204
- Poll.getOptionVoteCount = function (pollId, option, callback) {
205
- NodeBB.db.sortedSetCard(`poll:${pollId}:options:${option}:votes`, callback);
206
- };
207
-
208
- Poll.hasOption = function (pollId, option, callback) {
209
- NodeBB.db.isSetMember(`poll:${pollId}:options`, option, callback);
210
- };
211
-
212
- Poll.hasOptions = function (pollId, options, callback) {
213
- NodeBB.db.isSetMembers(`poll:${pollId}:options`, options, callback);
214
- };
215
-
216
- Poll.getSettings = function (pollId, callback) {
217
- NodeBB.db.getObject(`poll:${pollId}:settings`, callback);
218
- };
219
-
220
- Poll.isDeleted = function (pollId, callback) {
221
- Poll.getField(pollId, 'deleted', (err, result) => {
222
- callback(err, parseInt(result, 10) === 1);
223
- });
224
- };
225
-
226
- Poll.delete = function (pollId) {
227
- Poll.setField(pollId, 'deleted', 1);
228
- };
8
+ const Poll = exports;
9
+
10
+ const POLL_DATA_VERSION = 2;
11
+
12
+ Poll.add = async function (pollData, postData) {
13
+ const pollId = await NodeBB.db.incrObjectField('global', 'nextPollId');
14
+
15
+ const poll = {
16
+ pollId: pollId,
17
+ uid: postData.editor || postData.uid,
18
+ tid: postData.tid,
19
+ pid: postData.pid,
20
+ deleted: 0,
21
+ ended: 0,
22
+ timestamp: postData.edited || postData.timestamp,
23
+ version: POLL_DATA_VERSION,
24
+ };
25
+
26
+ pollData.options = pollData.options.map((val, i) => ({
27
+ id: i,
28
+ title: val,
29
+ }));
30
+
31
+ await Promise.all([
32
+ NodeBB.db.setObject(`poll:${pollId}`, poll),
33
+ NodeBB.db.setObject(`poll:${pollId}:settings`, pollData.settings),
34
+ NodeBB.db.listAppend('polls', pollId),
35
+ NodeBB.db.setObjectField(`topic:${poll.tid}`, 'pollId', pollId),
36
+ NodeBB.db.setAdd(`poll:${pollId}:options`, pollData.options.map(option => option.id)),
37
+ NodeBB.db.setObjectBulk(pollData.options.map(
38
+ option => ([`poll:${pollId}:options:${option.id}`, option])
39
+ )),
40
+ ]);
41
+
42
+ // Check if this poll is scheduled to end
43
+ if (parseInt(pollData.settings.end, 10) > 0) {
44
+ Poll.schedule(pollId);
45
+ }
46
+
47
+ return pollId;
48
+ };
49
+
50
+ Poll.get = async function (pollId, uid, withVotes) {
51
+ const [info, options, settings, hasVoted, vote] = await Promise.all([
52
+ Poll.getInfo(pollId),
53
+ Poll.getOptions(pollId, withVotes),
54
+ Poll.getSettings(pollId),
55
+ uid ? Vote.hasUidVoted(uid, pollId) : false,
56
+ uid ? Vote.getUidVote(uid, pollId) : null,
57
+ ]);
58
+
59
+ options.forEach((option) => {
60
+ const percentage = ((option.voteCount / info.voteCount) * 100).toFixed(0);
61
+ option.percentage = isNaN(percentage) ? 0 : percentage;
62
+ });
63
+
64
+ settings.disallowVoteUpdate = parseInt(settings.disallowVoteUpdate, 10);
65
+ return {
66
+ info,
67
+ options,
68
+ settings,
69
+ hasVoted,
70
+ vote,
71
+ };
72
+ };
73
+
74
+ Poll.getPollIdByTid = async function (tid) {
75
+ return await NodeBB.db.getObjectField(`topic:${tid}`, 'pollId');
76
+ };
77
+
78
+ Poll.getPollIdByPid = async function (pid) {
79
+ return await NodeBB.db.getObjectField(`post:${pid}`, 'pollId');
80
+ };
81
+
82
+ Poll.getInfo = async function (pollId) {
83
+ const [poll, voteCount] = await Promise.all([
84
+ NodeBB.db.getObject(`poll:${pollId}`),
85
+ Poll.getVoteCount(pollId),
86
+ ]);
87
+ poll.voteCount = parseInt(voteCount, 10) || 0;
88
+ return poll;
89
+ };
90
+
91
+ Poll.getOptions = async function (pollId, withVotes = false) {
92
+ const options = await NodeBB.db.getSetMembers(`poll:${pollId}:options`);
93
+ // sort options according to option id. option id are supposed to preseve the order as in Poll.add(),
94
+ // when the user create the poll
95
+ options.sort((a, b) => a - b);
96
+ return await Promise.all(
97
+ options.map(option => Poll.getOption(pollId, option, withVotes))
98
+ );
99
+ };
100
+
101
+ Poll.getOption = async function (pollId, option, withVotes = false) {
102
+ const [optionData, votes, voteCount] = await Promise.all([
103
+ NodeBB.db.getObject(`poll:${pollId}:options:${option}`),
104
+ withVotes ?
105
+ NodeBB.db.getSortedSetRange(`poll:${pollId}:options:${option}:votes`, 0, -1) :
106
+ null,
107
+ Poll.getOptionVoteCount(pollId, option),
108
+ ]);
109
+
110
+ if (votes) {
111
+ optionData.votes = votes;
112
+ }
113
+ optionData.voteCount = parseInt(voteCount, 10) || 0;
114
+ return optionData;
115
+ };
116
+
117
+ Poll.getVotersCount = async function (pollId) {
118
+ return await NodeBB.db.sortedSetCard(`poll:${pollId}:voters`);
119
+ };
120
+
121
+ Poll.getVoteCount = async function (pollId) {
122
+ const options = await NodeBB.db.getSetMembers(`poll:${pollId}:options`);
123
+ return await NodeBB.db.sortedSetsCardSum(
124
+ options.map(option => `poll:${pollId}:options:${option}:votes`)
125
+ );
126
+ };
127
+
128
+ Poll.getOptionVoteCount = async function (pollId, option) {
129
+ return await NodeBB.db.sortedSetCard(`poll:${pollId}:options:${option}:votes`);
130
+ };
131
+
132
+ Poll.hasOption = async function (pollId, option) {
133
+ return await NodeBB.db.isSetMember(`poll:${pollId}:options`, option);
134
+ };
135
+
136
+ Poll.hasOptions = async function (pollId, options) {
137
+ return await NodeBB.db.isSetMembers(`poll:${pollId}:options`, options);
138
+ };
139
+
140
+ Poll.getSettings = async function (pollId) {
141
+ return await NodeBB.db.getObject(`poll:${pollId}:settings`);
142
+ };
143
+
144
+ Poll.isDeleted = async function (pollId) {
145
+ const result = await Poll.getField(pollId, 'deleted');
146
+ return parseInt(result, 10) === 1;
147
+ };
148
+
149
+ Poll.delete = async function (pollId) {
150
+ await Poll.setField(pollId, 'deleted', 1);
151
+ };
152
+
153
+ Poll.restore = async function (pollId) {
154
+ await Poll.setFields(pollId, {
155
+ edited: 0,
156
+ deleted: 0,
157
+ });
158
+ };
159
+
160
+ Poll.schedule = async function (pollId) {
161
+ await NodeBB.db.setAdd('polls:scheduled', pollId);
162
+ Scheduler.add(pollId);
163
+ };
164
+
165
+ Poll.getScheduled = async function () {
166
+ return await NodeBB.db.getSetMembers('polls:scheduled');
167
+ };
168
+
169
+ Poll.hasEnded = async function (pollId) {
170
+ const result = await Poll.getField(pollId, 'ended');
171
+ return parseInt(result, 10) === 1;
172
+ };
173
+
174
+ Poll.doesDisallowVoteUpdate = async function (pollId) {
175
+ const result = await Poll.getSettingsField(pollId, 'disallowVoteUpdate');
176
+ return parseInt(result, 10) === 1;
177
+ };
178
+
179
+ Poll.doesAllowVoteUpdate = async function (pollId) {
180
+ const result = await Poll.getSettingsField(pollId, 'disallowVoteUpdate');
181
+ return parseInt(result, 10) !== 1;
182
+ };
183
+
184
+ Poll.end = async function (pollId) {
185
+ await NodeBB.db.setRemove('polls:scheduled', pollId);
186
+ await Poll.setField(pollId, 'ended', 1);
187
+ };
188
+
189
+ Poll.changePid = async function (pollId, pid) {
190
+ await Promise.all([
191
+ Poll.setField(pollId, 'pid', pid),
192
+ NodeBB.db.setObjectField(`post:${pid}`, 'pollId', pollId),
193
+ ]);
194
+ };
195
+
196
+ Poll.changeTid = async function (pollId, tid) {
197
+ await Promise.all([
198
+ Poll.setField(pollId, 'tid', tid),
199
+ NodeBB.db.setObjectField(`topic:${tid}`, 'pollId', pollId),
200
+ ]);
201
+ };
202
+
203
+ Poll.setField = async function (pollId, field, value) {
204
+ await NodeBB.db.setObjectField(`poll:${pollId}`, field, value);
205
+ };
206
+
207
+ Poll.setFields = async function (pollId, data) {
208
+ await NodeBB.db.setObject(`poll:${pollId}`, data);
209
+ };
210
+
211
+ Poll.getField = async function (pollId, field) {
212
+ return await NodeBB.db.getObjectField(`poll:${pollId}`, field);
213
+ };
214
+
215
+ Poll.getFields = async function (pollId, fields) {
216
+ return await NodeBB.db.getObjectFields(`poll:${pollId}`, fields);
217
+ };
218
+
219
+ Poll.getSettingsField = async function (pollId, field) {
220
+ return await NodeBB.db.getObjectField(`poll:${pollId}:settings`, field);
221
+ };
222
+
223
+ Poll.setSettingsField = async function (pollId, field, value) {
224
+ await NodeBB.db.setObjectField(`poll:${pollId}:settings`, field, value);
225
+ };
229
226
 
230
- Poll.restore = function (pollId) {
231
- Poll.setField(pollId, 'edited', 0);
232
- Poll.setField(pollId, 'deleted', 0);
233
- };
234
-
235
- Poll.schedule = function (pollId) {
236
- NodeBB.db.setAdd('polls:scheduled', pollId);
237
- Scheduler.add(pollId);
238
- };
239
-
240
- Poll.getScheduled = function (callback) {
241
- NodeBB.db.getSetMembers('polls:scheduled', callback);
242
- };
243
-
244
- Poll.hasEnded = function (pollId, callback) {
245
- Poll.getField(pollId, 'ended', (err, result) => {
246
- callback(err, parseInt(result, 10) === 1);
247
- });
248
- };
249
-
250
- Poll.doesDisallowVoteUpdate = function (pollId, callback) {
251
- Poll.getSettingsField(pollId, 'disallowVoteUpdate', (err, result) => {
252
- callback(err, parseInt(result, 10) === 1);
253
- });
254
- };
255
-
256
- Poll.doesAllowVoteUpdate = function (pollId, callback) {
257
- Poll.getSettingsField(pollId, 'disallowVoteUpdate', (err, result) => {
258
- callback(err, parseInt(result, 10) !== 1);
259
- });
260
- };
261
-
262
- Poll.end = function (pollId) {
263
- NodeBB.db.setRemove('polls:scheduled', pollId);
264
- Poll.setField(pollId, 'ended', 1);
265
- };
266
-
267
- Poll.changePid = function (pollId, pid, callback) {
268
- async.parallel([
269
- function (next) {
270
- Poll.setField(pollId, 'pid', pid, next);
271
- },
272
- function (next) {
273
- NodeBB.db.setObjectField(`post:${pid}`, 'pollId', pollId, next);
274
- },
275
- ], callback);
276
- };
277
-
278
- Poll.changeTid = function (pollId, tid, callback) {
279
- async.parallel([
280
- function (next) {
281
- Poll.setField(pollId, 'tid', tid, next);
282
- },
283
- function (next) {
284
- NodeBB.db.setObjectField(`topic:${tid}`, 'pollId', pollId, next);
285
- },
286
- ], callback);
287
- };
288
-
289
- Poll.setField = function (pollId, field, value, callback) {
290
- NodeBB.db.setObjectField(`poll:${pollId}`, field, value, callback);
291
- };
292
-
293
- Poll.setFields = function (pollId, fields, values, callback) {
294
- NodeBB.db.setObjectFields(`poll:${pollId}`, fields, values, callback);
295
- };
296
-
297
- Poll.getField = function (pollId, field, callback) {
298
- NodeBB.db.getObjectField(`poll:${pollId}`, field, callback);
299
- };
300
-
301
- Poll.getFields = function (pollId, fields, callback) {
302
- NodeBB.db.getObjectFields(`poll:${pollId}`, fields, callback);
303
- };
304
-
305
- Poll.getSettingsField = function (pollId, field, callback) {
306
- NodeBB.db.getObjectField(`poll:${pollId}:settings`, field, callback);
307
- };
308
-
309
- Poll.setSettingsField = function (pollId, field, value, callback) {
310
- NodeBB.db.setObjectField(`poll:${pollId}:settings`, field, callback);
311
- };
312
- }(exports));
package/lib/scheduler.js CHANGED
@@ -5,59 +5,48 @@ const NodeBB = require('./nodebb');
5
5
 
6
6
  const Poll = require('./poll');
7
7
 
8
- (function (Scheduler) {
9
- const jobs = {};
10
-
11
- Scheduler.start = function () {
12
- Poll.getScheduled((err, pollIds) => {
13
- if (err) {
14
- console.error(err);
15
- }
16
- pollIds.forEach((pollId) => {
17
- Scheduler.add(pollId);
18
- });
19
- });
20
- };
21
-
22
- Scheduler.add = function (pollId) {
23
- if (Object.keys(jobs).indexOf(pollId.toString()) !== -1) {
24
- return;
25
- }
26
-
27
- Poll.getSettings(pollId, (err, settings) => {
28
- if (err) {
29
- return NodeBB.winston.error(err);
30
- }
31
-
32
- if (!settings) {
33
- return NodeBB.winston.error(`[nodebb-plugin-poll/scheduler] Poll ID ${pollId} has no settings!`);
34
- }
35
-
36
- const now = Date.now();
37
- const end = parseInt(settings.end, 10);
38
-
39
- if (end < now) {
40
- Scheduler.end(pollId);
41
- } else {
42
- const date = new Date(end);
43
- NodeBB.winston.verbose(`[nodebb-plugin-poll/scheduler] Starting scheduler for poll with ID ${pollId} to end on ${date}`);
44
- jobs[pollId] = new cron(date, (() => {
45
- Scheduler.end(pollId);
46
- }), null, true);
47
- }
48
- });
49
- };
50
-
51
- Scheduler.end = function (pollId) {
52
- NodeBB.winston.verbose(`[nodebb-plugin-poll/scheduler] Ending poll with ID ${pollId}`);
53
-
54
- const index = Object.keys(jobs).indexOf(pollId.toString());
55
-
56
- if (index !== -1 && jobs[pollId] !== undefined) {
57
- jobs[pollId].stop();
58
- delete jobs[pollId];
59
- }
60
-
61
- Poll.end(pollId);
62
- };
63
- }(exports));
8
+ const Scheduler = exports;
9
+ const jobs = {};
10
+
11
+ Scheduler.start = async function () {
12
+ const pollIds = await Poll.getScheduled();
13
+ await Promise.all(pollIds.map(Scheduler.add));
14
+ };
15
+
16
+ Scheduler.add = async function (pollId) {
17
+ if (Object.keys(jobs).indexOf(pollId.toString()) !== -1) {
18
+ return;
19
+ }
20
+
21
+ const settings = await Poll.getSettings(pollId);
22
+ if (!settings) {
23
+ return NodeBB.winston.error(`[nodebb-plugin-poll/scheduler] Poll ID ${pollId} has no settings!`);
24
+ }
25
+
26
+ const now = Date.now();
27
+ const end = parseInt(settings.end, 10);
28
+
29
+ if (end < now) {
30
+ Scheduler.end(pollId);
31
+ } else {
32
+ const date = new Date(end);
33
+ NodeBB.winston.verbose(`[nodebb-plugin-poll/scheduler] Starting scheduler for poll with ID ${pollId} to end on ${date}`);
34
+ jobs[pollId] = new cron(date, (() => {
35
+ Scheduler.end(pollId);
36
+ }), null, true);
37
+ }
38
+ };
39
+
40
+ Scheduler.end = async function (pollId) {
41
+ NodeBB.winston.verbose(`[nodebb-plugin-poll/scheduler] Ending poll with ID ${pollId}`);
42
+
43
+ const index = Object.keys(jobs).indexOf(pollId.toString());
44
+
45
+ if (index !== -1 && jobs[pollId] !== undefined) {
46
+ jobs[pollId].stop();
47
+ delete jobs[pollId];
48
+ }
49
+
50
+ await Poll.end(pollId);
51
+ };
52
+