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/sockets.js CHANGED
@@ -1,258 +1,169 @@
1
1
  'use strict';
2
2
 
3
- const async = require('async');
4
-
5
3
  const NodeBB = require('./nodebb');
6
4
 
7
5
  const Config = require('./config');
8
6
  const Poll = require('./poll');
9
7
  const Vote = require('./vote');
10
8
 
11
- (function (Sockets) {
12
- Sockets.getConfig = function (socket, data, callback) {
13
- callback(null, Config.settings.get());
14
- };
15
-
16
- Sockets.get = function (socket, data, callback) {
17
- const allowAnon = Config.settings.get('toggles.allowAnon');
18
-
19
- if (!data) {
20
- return callback(new Error('Invalid request, request data is not defined'));
21
- }
22
- if (isNaN(parseInt(data.pollId, 10))) {
23
- return callback(new Error('Invalid request, pollId is required'));
24
- }
25
- if ((!socket.uid && !allowAnon)) {
26
- return callback(new Error('Invalid request, anonymous access is not allowed'));
27
- }
28
-
29
- const pollId = parseInt(data.pollId, 10);
30
- Poll.get(pollId, socket.uid, !!socket.uid, (err, pollData) => {
31
- if (err) {
32
- console.error(err);
33
- }
34
- if (!pollData.info.version) {
35
- return callback(new Error('Legacy polls are not supported'));
36
- }
37
-
38
- pollData.optionType = parseInt(pollData.settings.maxvotes, 10) > 1 ? 'checkbox' : 'radio';
39
- callback(null, pollData);
40
- });
41
- };
42
-
43
- Sockets.vote = function (socket, data, callback) {
44
- if (!socket.uid) {
45
- return callback(new Error('You need to be logged in to vote'));
46
- }
47
- if (!data || isNaN(parseInt(data.pollId, 10)) || !data.options || !data.options.length) {
48
- return callback(new Error('Invalid vote'));
49
- }
50
-
51
- data.uid = socket.uid;
52
-
53
- async.parallel({
54
- canVote: function (next) {
55
- Vote.canVote(data.uid, data.pollId, next);
56
- },
57
- optionsFilter: function (next) {
58
- Poll.hasOptions(data.pollId, data.options, next);
59
- },
60
- settings: function (next) {
61
- Poll.getSettings(data.pollId, next);
62
- },
63
- }, (err, result) => {
64
- // Filter the options on their existence
65
- data.options = data.options.filter((el, index) => result.optionsFilter[index]);
66
-
67
- // Give an error if there are too many votes
68
- if (data.options.length > parseInt(result.settings.maxvotes, 10)) {
69
- return callback(new Error(`You can only vote for ${result.settings.maxvotes} options on this poll.`));
70
- }
71
-
72
- if (err || !result.canVote || !data.options.length) {
73
- return callback(new Error('Already voted or invalid option'));
74
- }
75
-
76
- Vote.add(data, (err) => {
77
- if (err) {
78
- return callback(new Error('Error during voting'));
79
- }
80
-
81
- Poll.get(data.pollId, socket.uid, false, (err, pollData) => {
82
- if (err) {
83
- console.error(err);
84
- }
85
-
86
- NodeBB.SocketIndex.server.sockets.emit('event:poll.voteChange', pollData);
87
-
88
- callback();
89
- });
90
- });
91
- });
92
- };
93
-
94
- Sockets.updateVote = function (socket, data, callback) {
95
- if (!socket.uid) {
96
- return callback(new Error('You need to be logged in to make changes'));
97
- }
98
- if (!data || isNaN(parseInt(data.pollId, 10)) || !data.options || !data.options.length) {
99
- return callback(new Error('Invalid vote'));
100
- }
101
- data.uid = socket.uid;
102
-
103
- async.parallel({
104
- canUpdateVote: function (next) {
105
- Vote.canUpdateVote(data.uid, data.pollId, next);
106
- },
107
- optionsFilter: function (next) {
108
- Poll.hasOptions(data.pollId, data.options, next);
109
- },
110
- settings: function (next) {
111
- Poll.getSettings(data.pollId, next);
112
- },
113
- }, (err, result) => {
114
- // Filter the options on their existence
115
- data.options = data.options.filter((el, index) => result.optionsFilter[index]);
116
-
117
- // Give an error if there are too many votes
118
- if (data.options.length > parseInt(result.settings.maxvotes, 10)) {
119
- return callback(new Error(`You can only vote for ${result.settings.maxvotes} options on this poll.`));
120
- }
121
- if (!result.canUpdateVote) {
122
- return callback(new Error('Can\'t update vote'));
123
- }
124
- if (err || !data.options.length) {
125
- if (err) {
126
- console.error(err);
127
- }
128
- return callback(new Error('Invalid option'));
129
- }
130
-
131
- Vote.update(data, (err) => {
132
- if (err) {
133
- return callback(new Error('Error during updating vote'));
134
- }
135
-
136
- Poll.get(data.pollId, socket.uid, false, (err, pollData) => {
137
- if (err) {
138
- console.error(err);
139
- }
140
-
141
- NodeBB.SocketIndex.server.sockets.emit('event:poll.voteChange', pollData);
142
-
143
- callback();
144
- });
145
- });
146
- });
147
- };
148
-
149
- Sockets.removeVote = function (socket, data, callback) {
150
- if (!socket.uid) {
151
- return callback(new Error('You need to be logged in to make changes'));
152
- }
153
- if (!data || isNaN(parseInt(data.pollId, 10))) {
154
- return callback(new Error('Invalid request'));
155
- }
156
- data.uid = socket.uid;
157
-
158
- async.parallel({
159
- canUpdateVote: function (next) {
160
- Vote.canUpdateVote(data.uid, data.pollId, next);
161
- },
162
- }, (err, result) => {
163
- if (!result.canUpdateVote) {
164
- return callback(new Error('Can\'t remove vote'));
165
- }
166
- if (err) {
167
- console.error(err);
168
- return callback(new Error('Something went wrong'));
169
- }
170
- Vote.remove(data, (err) => {
171
- if (err) {
172
- return callback(new Error('Error during removing vote'));
173
- }
174
- Poll.get(data.pollId, socket.uid, false, (err, pollData) => {
175
- if (err) {
176
- console.error(err);
177
- }
178
-
179
- NodeBB.SocketIndex.server.sockets.emit('event:poll.voteChange', pollData);
180
- callback();
181
- });
182
- });
183
- });
184
- };
185
-
186
- Sockets.getOptionDetails = function (socket, data, callback) {
187
- if (!socket.uid || !data || isNaN(parseInt(data.pollId, 10)) || isNaN(parseInt(data.optionId, 10))) {
188
- return callback(new Error('Invalid request'));
189
- }
190
- async.parallel({
191
- poll: function (next) {
192
- Poll.getInfo(data.pollId, next);
193
- },
194
- option: function (next) {
195
- Poll.getOption(data.pollId, data.optionId, true, next);
196
- },
197
- }, (err, result) => {
198
- if (err) {
199
- return callback(new Error('Something went wrong!'));
200
- }
201
- const { poll, option } = result;
202
- if (!option.votes || !option.votes.length) {
203
- return callback(null, option);
204
- }
205
-
206
- NodeBB.User.getUsersFields(option.votes, ['uid', 'username', 'userslug', 'picture'], (err, userData) => {
207
- if (err) {
208
- console.error(err);
209
- return callback(err);
210
- }
211
- option.votes = userData;
212
- NodeBB.Plugins.hooks.fire('filter:poll.getOptionDetails', {
213
- poll,
214
- option,
215
- }, (err, result) => {
216
- callback(err, result.option);
217
- });
218
- });
219
- });
220
- };
221
-
222
- Sockets.canCreate = function (socket, data, callback) {
223
- if (!socket.uid || !data || (isNaN(parseInt(data.cid, 10)) && isNaN(parseInt(data.pid, 10)))) {
224
- return callback(new Error('Invalid request'));
225
- }
226
-
227
- if (!data.cid) {
228
- async.waterfall([
229
- function (next) {
230
- NodeBB.Posts.getPostField(data.pid, 'tid', next);
231
- },
232
- function (tid, next) {
233
- NodeBB.Topics.getTopicField(tid, 'cid', (err, cid) => {
234
- if (err) {
235
- console.error(err);
236
- return callback(new Error('Invalid request'));
237
- }
238
- next(null, cid);
239
- });
240
- },
241
- function (cid) {
242
- checkPrivs(cid, socket.uid, callback);
243
- },
244
- ]);
245
- } else {
246
- checkPrivs(data.cid, socket.uid, callback);
247
- }
248
- };
249
-
250
- function checkPrivs(cid, socketUid, callback) {
251
- NodeBB.Privileges.categories.can('poll:create', cid, socketUid, (err, can) => {
252
- if (err || !can) {
253
- return callback(new Error('[[poll:error.privilege.create]]'), can);
254
- }
255
- return callback(null, can);
256
- });
9
+ const Sockets = exports;
10
+
11
+ Sockets.getConfig = function (socket, data, callback) {
12
+ callback(null, Config.settings.get());
13
+ };
14
+
15
+ Sockets.get = async function (socket, data) {
16
+ const allowAnon = Config.settings.get('toggles.allowAnon');
17
+
18
+ if (!data) {
19
+ throw new Error('Invalid request, request data is not defined');
20
+ }
21
+ if (isNaN(parseInt(data.pollId, 10))) {
22
+ throw new Error('Invalid request, pollId is required');
23
+ }
24
+ if ((!socket.uid && !allowAnon)) {
25
+ throw new Error('Invalid request, anonymous access is not allowed');
26
+ }
27
+
28
+ const pollId = parseInt(data.pollId, 10);
29
+ const pollData = await Poll.get(pollId, socket.uid, !!socket.uid);
30
+ if (!pollData.info.version) {
31
+ throw new Error('Legacy polls are not supported');
32
+ }
33
+
34
+ pollData.optionType = parseInt(pollData.settings.maxvotes, 10) > 1 ? 'checkbox' : 'radio';
35
+ return pollData;
36
+ };
37
+
38
+ Sockets.vote = async function (socket, data) {
39
+ if (!socket.uid) {
40
+ throw new Error('You need to be logged in to vote');
41
+ }
42
+ if (!data || isNaN(parseInt(data.pollId, 10)) || !data.options || !data.options.length) {
43
+ throw new Error('Invalid vote');
44
+ }
45
+
46
+ data.uid = socket.uid;
47
+
48
+ const [canVote, optionsFilter, settings] = await Promise.all([
49
+ Vote.canVote(data.uid, data.pollId),
50
+ Poll.hasOptions(data.pollId, data.options),
51
+ Poll.getSettings(data.pollId),
52
+ ]);
53
+
54
+ // Filter the options on their existence
55
+ data.options = data.options.filter((el, index) => optionsFilter[index]);
56
+
57
+ // Give an error if there are too many votes
58
+ if (data.options.length > parseInt(settings.maxvotes, 10)) {
59
+ throw new Error(`You can only vote for ${settings.maxvotes} options on this poll.`);
60
+ }
61
+
62
+ if (!canVote || !data.options.length) {
63
+ throw new Error('Already voted or invalid option');
64
+ }
65
+
66
+ await Vote.add(data);
67
+
68
+ const pollData = await Poll.get(data.pollId, socket.uid, false);
69
+ NodeBB.SocketIndex.server.sockets.emit('event:poll.voteChange', pollData);
70
+ };
71
+
72
+ Sockets.updateVote = async function (socket, data) {
73
+ if (!socket.uid) {
74
+ throw new Error('You need to be logged in to make changes');
75
+ }
76
+ if (!data || isNaN(parseInt(data.pollId, 10)) || !data.options || !data.options.length) {
77
+ throw new Error('Invalid vote');
78
+ }
79
+ data.uid = socket.uid;
80
+
81
+ const [canUpdateVote, optionsFilter, settings] = await Promise.all([
82
+ Vote.canUpdateVote(data.uid, data.pollId),
83
+ Poll.hasOptions(data.pollId, data.options),
84
+ Poll.getSettings(data.pollId),
85
+ ]);
86
+
87
+ // Filter the options on their existence
88
+ data.options = data.options.filter((el, index) => optionsFilter[index]);
89
+
90
+ // Give an error if there are too many votes
91
+ if (data.options.length > parseInt(settings.maxvotes, 10)) {
92
+ throw new Error(`You can only vote for ${settings.maxvotes} options on this poll.`);
93
+ }
94
+
95
+ if (!canUpdateVote) {
96
+ throw new Error('Can\'t update vote');
97
+ }
98
+
99
+ if (!data.options.length) {
100
+ throw new Error('Invalid option');
101
+ }
102
+
103
+ await Vote.update(data);
104
+ const pollData = await Poll.get(data.pollId, socket.uid, false);
105
+ NodeBB.SocketIndex.server.sockets.emit('event:poll.voteChange', pollData);
106
+ };
107
+
108
+ Sockets.removeVote = async function (socket, data) {
109
+ if (!socket.uid) {
110
+ throw new Error('You need to be logged in to make changes');
111
+ }
112
+ if (!data || isNaN(parseInt(data.pollId, 10))) {
113
+ throw new Error('Invalid request');
114
+ }
115
+ data.uid = socket.uid;
116
+
117
+ const canUpdateVote = await Vote.canUpdateVote(data.uid, data.pollId);
118
+ if (!canUpdateVote) {
119
+ throw new Error('Can\'t remove vote');
120
+ }
121
+
122
+ await Vote.remove(data);
123
+ const pollData = await Poll.get(data.pollId, socket.uid, false);
124
+ NodeBB.SocketIndex.server.sockets.emit('event:poll.voteChange', pollData);
125
+ };
126
+
127
+ Sockets.getOptionDetails = async function (socket, data) {
128
+ if (!socket.uid || !data || isNaN(parseInt(data.pollId, 10)) || isNaN(parseInt(data.optionId, 10))) {
129
+ throw new Error('Invalid request');
130
+ }
131
+ const [poll, option] = await Promise.all([
132
+ Poll.getInfo(data.pollId),
133
+ Poll.getOption(data.pollId, data.optionId, true),
134
+ ]);
135
+
136
+ if (!option.votes || !option.votes.length) {
137
+ return option;
138
+ }
139
+ const userData = await NodeBB.User.getUsersFields(option.votes, [
140
+ 'uid', 'username', 'userslug', 'picture',
141
+ ]);
142
+ option.votes = userData;
143
+
144
+ const result = await NodeBB.Plugins.hooks.fire('filter:poll.getOptionDetails', {
145
+ poll,
146
+ option,
147
+ });
148
+ return result.option;
149
+ };
150
+
151
+ Sockets.canCreate = async function (socket, data) {
152
+ if (!socket.uid || !data || (isNaN(parseInt(data.cid, 10)) && isNaN(parseInt(data.pid, 10)))) {
153
+ throw new Error('Invalid request');
154
+ }
155
+
156
+ if (!data.cid) {
157
+ const tid = await NodeBB.Posts.getPostField(data.pid, 'tid');
158
+ data.cid = await NodeBB.Topics.getTopicField(tid, 'cid');
159
+ }
160
+ return await checkPrivs(data.cid, socket.uid);
161
+ };
162
+
163
+ async function checkPrivs(cid, socketUid) {
164
+ const can = await NodeBB.Privileges.categories.can('poll:create', cid, socketUid);
165
+ if (!can) {
166
+ throw new Error('[[poll:error.privilege.create]]');
257
167
  }
258
- }(exports));
168
+ return true;
169
+ }
package/lib/vote.js CHANGED
@@ -1,138 +1,83 @@
1
1
  'use strict';
2
2
 
3
- const async = require('async');
4
3
  const NodeBB = require('./nodebb');
5
4
  const Poll = require('./poll');
6
5
 
7
- (function (Vote) {
8
- Vote.add = function (voteData, callback) {
9
- const { pollId } = voteData;
10
- const { options } = voteData;
11
- const { uid } = voteData;
12
- const timestamp = +new Date();
6
+ const Vote = exports;
13
7
 
14
- async.parallel([
15
- function (next) {
16
- async.each(options, (option, next) => {
17
- // Add uid to list of votes
18
- NodeBB.db.sortedSetAdd(`poll:${pollId}:options:${option}:votes`, timestamp, uid, next);
19
- }, next);
20
- },
21
- function (next) {
22
- NodeBB.db.sortedSetAdd(`poll:${pollId}:voters`, timestamp, uid, next);
23
- },
24
- ], (err) => {
25
- if (err) {
26
- return callback(err, false);
27
- }
8
+ Vote.add = async function (voteData) {
9
+ const { pollId, options, uid } = voteData;
10
+ const timestamp = +new Date();
28
11
 
29
- callback(null, true);
30
- });
31
- };
12
+ await Promise.all([
13
+ NodeBB.db.sortedSetAdd(`poll:${pollId}:voters`, timestamp, uid),
14
+ NodeBB.db.sortedSetAddBulk(
15
+ options.map(option => ([`poll:${pollId}:options:${option}:votes`, timestamp, uid]))
16
+ ),
17
+ ]);
18
+ };
32
19
 
33
- Vote.remove = function (voteData, callback) {
34
- return Vote.removeUidVote(voteData.uid, voteData.pollId, callback);
35
- };
20
+ Vote.remove = async function (voteData) {
21
+ return await Vote.removeUidVote(voteData.uid, voteData.pollId);
22
+ };
36
23
 
37
- Vote.get = function (voteData, callback) {
38
- return Vote.getUidVote(voteData.uid, voteData.pollId, callback);
39
- };
24
+ Vote.get = async function (voteData) {
25
+ return await Vote.getUidVote(voteData.uid, voteData.pollId);
26
+ };
40
27
 
41
- Vote.removeUidVote = function (uid, pollId, callback) {
42
- Vote.getUidVote(uid, pollId, (err, vote) => {
43
- if (err) {
44
- console.error(err);
45
- }
28
+ Vote.removeUidVote = async function (uid, pollId) {
29
+ const vote = await Vote.getUidVote(uid, pollId);
30
+ const options = vote.options || [];
31
+ await Promise.all([
32
+ NodeBB.db.sortedSetRemove(`poll:${pollId}:voters`, uid),
33
+ NodeBB.db.sortedSetsRemove(
34
+ options.map(option => `poll:${pollId}:options:${option}:votes`),
35
+ uid,
36
+ ),
37
+ ]);
38
+ };
46
39
 
47
- async.parallel([
48
- function (next) {
49
- async.each(vote.options || [], (option, next) => {
50
- NodeBB.db.sortedSetRemove(`poll:${pollId}:options:${option}:votes`, uid, next);
51
- }, next);
52
- },
53
- function (next) {
54
- NodeBB.db.sortedSetRemove(`poll:${pollId}:voters`, uid, next);
55
- },
56
- ], (err) => {
57
- if (err) {
58
- return callback(err, false);
59
- }
60
-
61
- callback(null, true);
62
- });
63
- });
40
+ Vote.getUidVote = async function (uid, pollId) {
41
+ const options = await Poll.getOptions(pollId, true);
42
+ return {
43
+ pollId: pollId,
44
+ uid: uid,
45
+ options: options
46
+ .filter(option => option.votes.some(_uid => parseInt(_uid, 10) === parseInt(uid, 10)))
47
+ .map(option => option.id),
64
48
  };
49
+ };
65
50
 
66
- Vote.getUidVote = function (uid, pollId, callback) {
67
- Poll.getOptions(pollId, true, (err, options) => {
68
- callback(err, {
69
- pollId: pollId,
70
- uid: uid,
71
- options: options
72
- .filter(option => option.votes.some(_uid => parseInt(_uid, 10) === parseInt(uid, 10)))
73
- .map(option => option.id),
74
- });
75
- });
76
- };
51
+ Vote.update = async function (voteData) {
52
+ await Vote.remove(voteData);
53
+ await Vote.add(voteData);
54
+ };
77
55
 
78
- Vote.update = function (voteData, callback) {
79
- async.series([
80
- function (next) {
81
- Vote.remove(voteData, next);
82
- },
83
- function (next) {
84
- Vote.add(voteData, next);
85
- },
86
- ], callback);
87
- };
56
+ Vote.canUpdateVote = async function (uid, pollId) {
57
+ const [ended, deleted, updateDisabled] = await Promise.all([
58
+ Poll.hasEnded(pollId),
59
+ Poll.isDeleted(pollId),
60
+ Poll.doesDisallowVoteUpdate(pollId),
61
+ ]);
62
+ return !ended && !deleted && !updateDisabled;
63
+ };
88
64
 
89
- Vote.canUpdateVote = function (uid, pollId, callback) {
90
- async.parallel([
91
- function (next) {
92
- // Ended?
93
- Poll.hasEnded(pollId, next);
94
- },
95
- function (next) {
96
- // Deleted?
97
- Poll.isDeleted(pollId, next);
98
- },
99
- function (next) {
100
- // voteUpdate?
101
- Poll.doesDisallowVoteUpdate(pollId, next);
102
- },
103
- ], (err, result) => {
104
- callback(err, result.indexOf(true) === -1);
105
- });
106
- };
65
+ Vote.canVote = async function (uid, pollId) {
66
+ const [ended, deleted, hasVoted] = await Promise.all([
67
+ Poll.hasEnded(pollId),
68
+ Poll.isDeleted(pollId),
69
+ Vote.hasUidVoted(uid, pollId),
70
+ ]);
71
+ return !ended && !deleted && !hasVoted;
72
+ };
107
73
 
108
- Vote.canVote = function (uid, pollId, callback) {
109
- async.parallel([
110
- function (next) {
111
- // Ended?
112
- Poll.hasEnded(pollId, next);
113
- },
114
- function (next) {
115
- // Deleted?
116
- Poll.isDeleted(pollId, next);
117
- },
118
- function (next) {
119
- // Already voted?
120
- Vote.hasUidVoted(uid, pollId, next);
121
- },
122
- ], (err, result) => {
123
- callback(err, result.indexOf(true) === -1);
124
- });
125
- };
74
+ Vote.hasUidVoted = async function (uid, pollId) {
75
+ const score = await NodeBB.db.sortedSetScore(`poll:${pollId}:voters`, uid);
76
+ return !!score;
77
+ };
126
78
 
127
- Vote.hasUidVoted = function (uid, pollId, callback) {
128
- NodeBB.db.sortedSetScore(`poll:${pollId}:voters`, uid, (err, score) => {
129
- callback(err, !!score);
130
- });
131
- };
79
+ Vote.hasUidVotedOnOption = async function (uid, pollId, option) {
80
+ const score = await NodeBB.db.sortedSetScore(`poll:${pollId}:options:${option}:votes`, uid);
81
+ return !!score;
82
+ };
132
83
 
133
- Vote.hasUidVotedOnOption = function (uid, pollId, option, callback) {
134
- NodeBB.db.sortedSetScore(`poll:${pollId}:options:${option}:votes`, uid, (err, score) => {
135
- callback(err, !!score);
136
- });
137
- };
138
- }(exports));