nodebb-plugin-poll 3.2.9 → 4.0.0

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,111 +1,263 @@
1
1
  'use strict';
2
2
 
3
- const NodeBB = require('./nodebb');
3
+ const db = require.main.require('./src/database');
4
+ const privileges = require.main.require('./src/privileges');
4
5
 
5
6
  const Vote = require('./vote');
6
- const Scheduler = require('./scheduler');
7
7
 
8
8
  const Poll = exports;
9
9
 
10
- const POLL_DATA_VERSION = 2;
11
-
12
- Poll.add = async function (pollData, postData) {
13
- const pollId = await NodeBB.db.incrObjectField('global', 'nextPollId');
10
+ Poll.add = async function (postData, polls) {
11
+ // postData is what is being saved to db
12
+ // polls is submited by user
13
+ const savePolls = [];
14
+ for (const pollData of polls) {
15
+ // eslint-disable-next-line no-await-in-loop
16
+ const pollId = await db.incrObjectField('global', 'nextPollId');
17
+
18
+ const poll = {
19
+ pollId: pollId,
20
+ title: pollData.title,
21
+ uid: postData.editor || postData.uid,
22
+ pid: postData.pid,
23
+ deleted: 0,
24
+ end: pollData.end || 0,
25
+ maximumVotesPerUser: pollData.maximumVotesPerUser || 1,
26
+ timestamp: postData.edited || postData.timestamp,
27
+ };
28
+ if (Object.hasOwn(pollData, 'allowAnonVoting')) {
29
+ poll.allowAnonVoting = pollData.allowAnonVoting;
30
+ }
31
+ if (Object.hasOwn(pollData, 'disallowVoteUpdate')) {
32
+ poll.disallowVoteUpdate = pollData.disallowVoteUpdate;
33
+ }
34
+ // [{id: '1772931625035', title: 'option 1'} {id: '1772931626635', title: 'option 2'}]
35
+ poll.options = JSON.stringify(pollData.options || []);
36
+
37
+ savePolls.push(poll);
38
+ }
14
39
 
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
- };
40
+ await Promise.all([
41
+ db.setObjectBulk(savePolls.map(p => [`poll:${p.pollId}`, p])),
42
+ db.sortedSetAdd('polls:createtime', savePolls.map(p => p.timestamp), savePolls.map(p => p.pollId)),
43
+ ]);
44
+ return savePolls;
45
+ };
25
46
 
26
- pollData.options = pollData.options.map((val, i) => ({
27
- id: i,
28
- title: val,
29
- }));
47
+ Poll.deletePolls = async function (pollIds) {
48
+ if (!pollIds.length) return;
49
+ const polls = await db.getObjects(pollIds.map(pollId => `poll:${pollId}`));
50
+ const bulkDelete = [];
51
+ polls.forEach((poll) => {
52
+ if (poll && poll.options) {
53
+ const options = tryParseOptions(poll.options);
54
+ options.forEach((option) => {
55
+ bulkDelete.push(`poll:${poll.pollId}:options:${option.id}:votes`);
56
+ });
57
+ }
58
+ });
30
59
 
60
+ bulkDelete.push(...pollIds.map(pollId => `poll:${pollId}`));
61
+ bulkDelete.push(...pollIds.map(pollId => `poll:${pollId}:voters`));
62
+ bulkDelete.push(...pollIds.map(pollId => `poll:${pollId}:anon:voters`));
31
63
  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
- )),
64
+ db.deleteAll(bulkDelete),
65
+ db.sortedSetRemove('polls:createtime', pollIds),
40
66
  ]);
67
+ };
41
68
 
42
- // Check if this poll is scheduled to end
43
- if (parseInt(pollData.settings.end, 10) > 0) {
44
- Poll.schedule(pollId);
45
- }
69
+ Poll.edit = async function (postData, newPolls) {
70
+ // figure out which polls are new, which are edited and which are deleted
71
+ const newPollIds = newPolls.map(p => String(p.pollId));
72
+ const currentPollIds = await Poll.getPollIdsByPid(postData.pid);
73
+ const toAdd = newPolls.filter(p => !currentPollIds.includes(String(p.pollId)));
74
+ const toUpdate = newPolls.filter(p => currentPollIds.includes(String(p.pollId)));
75
+ const toDelete = currentPollIds.filter(pollId => !newPollIds.includes(String(pollId)));
76
+ const currentPolls = await db.getObjects(currentPollIds.map(pollId => `poll:${pollId}`));
77
+
78
+ await Poll.deletePolls(toDelete);
79
+
80
+ const savedPolls = await Poll.add(postData, toAdd);
81
+ toAdd.forEach((poll, index) => {
82
+ // overwrite with correct pollId generated in add function
83
+ // this also updates items in newPolls array with correct pollId so that it can be saved to post object at the end
84
+ poll.pollId = savedPolls[index].pollId;
85
+ });
46
86
 
47
- return pollId;
87
+ const bulkUpdateOptions = [];
88
+ const bulkDeleteOptions = [];
89
+ toUpdate.forEach((poll) => {
90
+ if (poll) {
91
+ const currentPoll = currentPolls.find(p => String(p.pollId) === String(poll.pollId));
92
+ if (currentPoll) {
93
+ const currentOptions = tryParseOptions(currentPoll.options);
94
+ const newOptions = poll.options || [];
95
+ currentOptions.forEach((option) => {
96
+ if (!newOptions.find(o => String(o.id) === String(option.id))) {
97
+ bulkDeleteOptions.push(`poll:${poll.pollId}:options:${option.id}:votes`);
98
+ }
99
+ });
100
+ }
101
+
102
+ bulkUpdateOptions.push([`poll:${poll.pollId}`, {
103
+ title: poll.title,
104
+ end: poll.end || 0,
105
+ maximumVotesPerUser: poll.maximumVotesPerUser || 1,
106
+ disallowVoteUpdate: poll.disallowVoteUpdate || 0,
107
+ allowAnonVoting: poll.allowAnonVoting || 0,
108
+ options: JSON.stringify(poll.options || []),
109
+ }]);
110
+ }
111
+ });
112
+ await db.setObjectBulk(bulkUpdateOptions);
113
+ await db.deleteAll(bulkDeleteOptions);
114
+
115
+ await Promise.all(toUpdate.map(poll => updateVotersFromOptions(poll)));
116
+
117
+ if (newPollIds.length) {
118
+ await db.setObjectField(
119
+ `post:${postData.pid}`,
120
+ 'pollIds',
121
+ JSON.stringify(newPolls.map(p => p.pollId))
122
+ );
123
+ } else {
124
+ await db.deleteObjectField(`post:${postData.pid}`, 'pollIds');
125
+ }
48
126
  };
49
127
 
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),
128
+ async function updateVotersFromOptions(poll) {
129
+ // this function updates `poll:${pollId}:voters` sorted set based on the votes on options,
130
+ // this is needed when options are removed from a poll via edit
131
+ const options = poll.options || [];
132
+
133
+ const currentVoters = await db.getSortedSetsMembers(
134
+ options.map(option => `poll:${poll.pollId}:options:${option.id}:votes`)
135
+ );
136
+ const currentVoterSet = [...new Set(currentVoters.flat())];
137
+ const timestamps = await db.sortedSetScores(`poll:${poll.pollId}:voters`, currentVoterSet);
138
+ await db.delete(`poll:${poll.pollId}:voters`);
139
+ await db.sortedSetAdd(`poll:${poll.pollId}:voters`, timestamps, currentVoterSet);
140
+ }
141
+
142
+ Poll.get = async function (pollId, uid, withVotes = false) {
143
+ const [info, hasVoted, vote] = await Promise.all([
144
+ Poll.getInfo(pollId, withVotes),
55
145
  uid ? Vote.hasUidVoted(uid, pollId) : false,
56
146
  uid ? Vote.getUidVote(uid, pollId) : null,
57
147
  ]);
58
-
59
- options.forEach((option) => {
148
+ if (!info) {
149
+ return null;
150
+ }
151
+ const canRead = await privileges.posts.can('topics:read', info.pid, uid);
152
+ if (!canRead) {
153
+ throw new Error('[[error:no-privileges]]');
154
+ }
155
+ info.options.forEach((option) => {
60
156
  const percentage = ((option.voteCount / info.voteCount) * 100).toFixed(0);
61
157
  option.percentage = isNaN(percentage) ? 0 : percentage;
62
158
  });
63
159
 
64
- settings.disallowVoteUpdate = parseInt(settings.disallowVoteUpdate, 10);
160
+ info.disallowVoteUpdate = parseInt(info.disallowVoteUpdate, 10);
65
161
  return {
66
162
  info,
67
- options,
68
- settings,
163
+ options: info.options,
69
164
  hasVoted,
70
165
  vote,
71
166
  };
72
167
  };
73
168
 
74
- Poll.getPollIdByTid = async function (tid) {
75
- return await NodeBB.db.getObjectField(`topic:${tid}`, 'pollId');
169
+ Poll.getPollIdsByPid = async function (pid) {
170
+ const pollIdsJson = await db.getObjectField(`post:${pid}`, 'pollIds');
171
+ if (!pollIdsJson) {
172
+ return [];
173
+ }
174
+ try {
175
+ const pollIds = JSON.parse(pollIdsJson || '[]');
176
+ return Array.isArray(pollIds) ? pollIds.map(p => String(p)) : [];
177
+ } catch (err) {
178
+ return [];
179
+ }
76
180
  };
77
181
 
78
- Poll.getPollIdByPid = async function (pid) {
79
- return await NodeBB.db.getObjectField(`post:${pid}`, 'pollId');
182
+ Poll.getPollIdsByPids = async function (pids) {
183
+ const postData = await db.getObjectsFields(pids.map(pid => `post:${pid}`), ['pollIds']);
184
+ return postData.map((post) => {
185
+ if (!post || !post.pollIds) {
186
+ return [];
187
+ }
188
+ try {
189
+ const pollIds = JSON.parse(post.pollIds || '[]');
190
+ return Array.isArray(pollIds) ? pollIds.map(p => String(p)) : [];
191
+ } catch (err) {
192
+ return [];
193
+ }
194
+ });
80
195
  };
81
196
 
82
- Poll.getInfo = async function (pollId) {
197
+ Poll.getInfo = async function (pollId, withVotes = false) {
83
198
  const [poll, voteCount] = await Promise.all([
84
- NodeBB.db.getObject(`poll:${pollId}`),
199
+ db.getObject(`poll:${pollId}`),
85
200
  Poll.getVoteCount(pollId),
86
201
  ]);
202
+ if (!poll) {
203
+ return null;
204
+ }
87
205
  poll.voteCount = parseInt(voteCount, 10) || 0;
206
+ const end = parseInt(poll.end, 10);
207
+ poll.ended = end > 0 && Date.now() > end;
208
+ poll.options = await loadOptions(pollId, poll.options, withVotes);
88
209
  return poll;
89
210
  };
90
211
 
91
212
  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
- );
213
+ const optionsJson = await db.getObjectField(`poll:${pollId}`, 'options');
214
+ return await loadOptions(pollId, optionsJson, withVotes);
215
+ };
216
+
217
+ Poll.getPollOptionIds = async function (pollId) {
218
+ const optionsJson = await db.getObjectField(`poll:${pollId}`, 'options');
219
+ return tryParseOptions(optionsJson).map(opts => opts && String(opts.id));
99
220
  };
100
221
 
222
+ async function loadOptions(pollId, optionsJson, withVotes = false) {
223
+ const options = tryParseOptions(optionsJson);
224
+
225
+ const votes = await db.getSortedSetsMembers(options.map(o => `poll:${pollId}:options:${o.id}:votes`));
226
+
227
+ options.forEach((option, index) => {
228
+ if (option) {
229
+ option.voteCount = votes[index].length;
230
+ if (withVotes) {
231
+ option.votes = votes[index];
232
+ }
233
+ }
234
+ });
235
+ return options;
236
+ }
237
+
238
+ function tryParseOptions(optionsJson) {
239
+ if (!optionsJson) return [];
240
+ try {
241
+ const optionsArray = JSON.parse(optionsJson || '[]');
242
+ return Array.isArray(optionsArray) ? optionsArray : [];
243
+ } catch (err) {
244
+ return [];
245
+ }
246
+ }
247
+
101
248
  Poll.getOption = async function (pollId, option, withVotes = false) {
102
- const [optionData, votes, voteCount] = await Promise.all([
103
- NodeBB.db.getObject(`poll:${pollId}:options:${option}`),
249
+ const [optionsJson, votes, voteCount] = await Promise.all([
250
+ db.getObjectField(`poll:${pollId}`, 'options'),
104
251
  withVotes ?
105
- NodeBB.db.getSortedSetRange(`poll:${pollId}:options:${option}:votes`, 0, -1) :
252
+ db.getSortedSetRange(`poll:${pollId}:options:${option}:votes`, 0, -1) :
106
253
  null,
107
254
  Poll.getOptionVoteCount(pollId, option),
108
255
  ]);
256
+ const options = tryParseOptions(optionsJson);
257
+ const optionData = options.find(opt => String(opt.id) === String(option));
258
+ if (!optionData) {
259
+ return null;
260
+ }
109
261
 
110
262
  if (votes) {
111
263
  optionData.votes = votes;
@@ -115,30 +267,28 @@ Poll.getOption = async function (pollId, option, withVotes = false) {
115
267
  };
116
268
 
117
269
  Poll.getVotersCount = async function (pollId) {
118
- return await NodeBB.db.sortedSetCard(`poll:${pollId}:voters`);
270
+ return await db.sortedSetCard(`poll:${pollId}:voters`);
119
271
  };
120
272
 
121
273
  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`)
274
+ const optionIds = await Poll.getPollOptionIds(pollId);
275
+ return await db.sortedSetsCardSum(
276
+ optionIds.map(option => `poll:${pollId}:options:${option}:votes`)
125
277
  );
126
278
  };
127
279
 
128
280
  Poll.getOptionVoteCount = async function (pollId, option) {
129
- return await NodeBB.db.sortedSetCard(`poll:${pollId}:options:${option}:votes`);
281
+ return await db.sortedSetCard(`poll:${pollId}:options:${option}:votes`);
130
282
  };
131
283
 
132
284
  Poll.hasOption = async function (pollId, option) {
133
- return await NodeBB.db.isSetMember(`poll:${pollId}:options`, option);
285
+ const optionIds = new Set(await Poll.getPollOptionIds(pollId));
286
+ return optionIds.has(String(option));
134
287
  };
135
288
 
136
289
  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`);
290
+ const optionIds = new Set(await Poll.getPollOptionIds(pollId));
291
+ return options.map(option => optionIds.has(String(option)));
142
292
  };
143
293
 
144
294
  Poll.isDeleted = async function (pollId) {
@@ -146,81 +296,52 @@ Poll.isDeleted = async function (pollId) {
146
296
  return parseInt(result, 10) === 1;
147
297
  };
148
298
 
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);
299
+ Poll.delete = async function (pollIds) {
300
+ await db.setObject(
301
+ pollIds.map(pollId => `poll:${pollId}`),
302
+ { deleted: 1 },
303
+ );
163
304
  };
164
305
 
165
- Poll.getScheduled = async function () {
166
- return await NodeBB.db.getSetMembers('polls:scheduled');
306
+ Poll.restore = async function (pollIds) {
307
+ await db.setObject(
308
+ pollIds.map(pollId => `poll:${pollId}`),
309
+ {
310
+ edited: 0,
311
+ deleted: 0,
312
+ },
313
+ );
167
314
  };
168
315
 
169
316
  Poll.hasEnded = async function (pollId) {
170
- const result = await Poll.getField(pollId, 'ended');
171
- return parseInt(result, 10) === 1;
317
+ let end = await Poll.getField(pollId, 'end');
318
+ end = parseInt(end, 10);
319
+ return end > 0 && Date.now() > end;
172
320
  };
173
321
 
174
322
  Poll.doesDisallowVoteUpdate = async function (pollId) {
175
- const result = await Poll.getSettingsField(pollId, 'disallowVoteUpdate');
323
+ const result = await Poll.getField(pollId, 'disallowVoteUpdate');
176
324
  return parseInt(result, 10) === 1;
177
325
  };
178
326
 
179
327
  Poll.doesAllowVoteUpdate = async function (pollId) {
180
- const result = await Poll.getSettingsField(pollId, 'disallowVoteUpdate');
328
+ const result = await Poll.getField(pollId, 'disallowVoteUpdate');
181
329
  return parseInt(result, 10) !== 1;
182
330
  };
183
331
 
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
332
  Poll.setField = async function (pollId, field, value) {
204
- await NodeBB.db.setObjectField(`poll:${pollId}`, field, value);
333
+ await db.setObjectField(`poll:${pollId}`, field, value);
205
334
  };
206
335
 
207
336
  Poll.setFields = async function (pollId, data) {
208
- await NodeBB.db.setObject(`poll:${pollId}`, data);
337
+ await db.setObject(`poll:${pollId}`, data);
209
338
  };
210
339
 
211
340
  Poll.getField = async function (pollId, field) {
212
- return await NodeBB.db.getObjectField(`poll:${pollId}`, field);
341
+ return await db.getObjectField(`poll:${pollId}`, field);
213
342
  };
214
343
 
215
344
  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);
345
+ return await db.getObjectFields(`poll:${pollId}`, fields);
225
346
  };
226
347
 
package/lib/sockets.js CHANGED
@@ -1,43 +1,44 @@
1
1
  'use strict';
2
2
 
3
3
  const _ = require.main.require('lodash');
4
- const NodeBB = require('./nodebb');
4
+ const db = require.main.require('./src/database');
5
+ const posts = require.main.require('./src/posts');
6
+ const topics = require.main.require('./src/topics');
7
+ const privileges = require.main.require('./src/privileges');
5
8
 
9
+ const NodeBB = require('./nodebb');
6
10
  const Config = require('./config');
7
11
  const Poll = require('./poll');
8
12
  const Vote = require('./vote');
9
13
 
10
14
  const Sockets = exports;
11
15
 
12
- Sockets.getConfig = function (socket, data, callback) {
13
- callback(null, Config.settings.get());
14
- };
15
-
16
16
  Sockets.get = async function (socket, data) {
17
- const allowAnon = Config.settings.get('toggles.allowAnon');
17
+ const settings = await Config.getSettings();
18
+ const allowAnon = parseInt(settings.allowGuestsToViewResults, 10) === 1;
18
19
 
19
20
  if (!data) {
20
21
  throw new Error('Invalid request, request data is not defined');
21
22
  }
22
- if (isNaN(parseInt(data.pollId, 10))) {
23
+ if (!Array.isArray(data.pollIds) || !data.pollIds.length) {
23
24
  throw new Error('Invalid request, pollId is required');
24
25
  }
25
26
  if ((!socket.uid && !allowAnon)) {
26
27
  throw new Error('Invalid request, anonymous access is not allowed');
27
28
  }
29
+ return await Promise.all(data.pollIds.map(async (pollId) => {
30
+ const pollData = await Poll.get(pollId, socket.uid, !!socket.uid);
31
+ if (!pollData) {
32
+ return null;
33
+ }
28
34
 
29
- const pollId = parseInt(data.pollId, 10);
30
- const pollData = await Poll.get(pollId, socket.uid, !!socket.uid);
31
- if (!pollData.info.version) {
32
- throw new Error('Legacy polls are not supported');
33
- }
34
-
35
- if (parseInt(pollData.settings.allowAnonVoting, 10) === 1) {
36
- await anonymizeVoters(socket.uid, data.pollId, pollData.options);
37
- }
35
+ if (parseInt(pollData.allowAnonVoting, 10) === 1) {
36
+ await anonymizeVoters(socket.uid, pollId, pollData.options);
37
+ }
38
38
 
39
- pollData.optionType = parseInt(pollData.settings.maxvotes, 10) > 1 ? 'checkbox' : 'radio';
40
- return pollData;
39
+ pollData.optionType = parseInt(pollData.maximumVotesPerUser, 10) > 1 ? 'checkbox' : 'radio';
40
+ return pollData;
41
+ }));
41
42
  };
42
43
 
43
44
  Sockets.vote = async function (socket, data) {
@@ -50,21 +51,21 @@ Sockets.vote = async function (socket, data) {
50
51
 
51
52
  data.uid = socket.uid;
52
53
 
53
- const [canVote, optionsFilter, settings] = await Promise.all([
54
+ const [pollData, canVote, optionsFilter] = await Promise.all([
55
+ db.getObject(`poll:${data.pollId}`),
54
56
  Vote.canVote(data.uid, data.pollId),
55
57
  Poll.hasOptions(data.pollId, data.options),
56
- Poll.getSettings(data.pollId),
57
58
  ]);
58
59
 
59
60
  // Filter the options on their existence
60
61
  data.options = data.options.filter((el, index) => optionsFilter[index]);
61
62
 
62
63
  // Give an error if there are too many votes
63
- if (data.options.length > parseInt(settings.maxvotes, 10)) {
64
- throw new Error(`You can only vote for ${settings.maxvotes} options on this poll.`);
64
+ if (data.options.length > parseInt(pollData.maximumVotesPerUser, 10)) {
65
+ throw new Error(`You can only vote for ${pollData.maximumVotesPerUser} options on this poll.`);
65
66
  }
66
67
 
67
- if (data.voteAnon && parseInt(settings.allowAnonVoting, 10) !== 1) {
68
+ if (data.voteAnon && parseInt(pollData.allowAnonVoting, 10) !== 1) {
68
69
  throw new Error('[[poll:error.anon-voting-not-allowed]]');
69
70
  }
70
71
 
@@ -74,8 +75,11 @@ Sockets.vote = async function (socket, data) {
74
75
 
75
76
  await Vote.add(data);
76
77
 
77
- const pollData = await Poll.get(data.pollId, socket.uid, false);
78
- NodeBB.SocketIndex.server.sockets.emit('event:poll.voteChange', { pollData, uid: socket.uid });
78
+ const returnPoll = await Poll.get(data.pollId, socket.uid, false);
79
+ NodeBB.SocketIndex.server.sockets.emit('event:poll.voteChange', {
80
+ pollData: returnPoll,
81
+ uid: socket.uid,
82
+ });
79
83
  };
80
84
 
81
85
  Sockets.updateVote = async function (socket, data) {
@@ -87,18 +91,18 @@ Sockets.updateVote = async function (socket, data) {
87
91
  }
88
92
  data.uid = socket.uid;
89
93
 
90
- const [canUpdateVote, optionsFilter, settings] = await Promise.all([
94
+ const [pollData, canUpdateVote, optionsFilter] = await Promise.all([
95
+ db.getObject(`poll:${data.pollId}`),
91
96
  Vote.canUpdateVote(data.uid, data.pollId),
92
97
  Poll.hasOptions(data.pollId, data.options),
93
- Poll.getSettings(data.pollId),
94
98
  ]);
95
99
 
96
100
  // Filter the options on their existence
97
101
  data.options = data.options.filter((el, index) => optionsFilter[index]);
98
102
 
99
103
  // Give an error if there are too many votes
100
- if (data.options.length > parseInt(settings.maxvotes, 10)) {
101
- throw new Error(`You can only vote for ${settings.maxvotes} options on this poll.`);
104
+ if (data.options.length > parseInt(pollData.maximumVotesPerUser, 10)) {
105
+ throw new Error(`You can only vote for ${pollData.maximumVotesPerUser} options on this poll.`);
102
106
  }
103
107
 
104
108
  if (!canUpdateVote) {
@@ -110,8 +114,11 @@ Sockets.updateVote = async function (socket, data) {
110
114
  }
111
115
 
112
116
  await Vote.update(data);
113
- const pollData = await Poll.get(data.pollId, socket.uid, false);
114
- NodeBB.SocketIndex.server.sockets.emit('event:poll.voteChange', { pollData, uid: socket.uid });
117
+ const returnPoll = await Poll.get(data.pollId, socket.uid, false);
118
+ NodeBB.SocketIndex.server.sockets.emit('event:poll.voteChange', {
119
+ pollData: returnPoll,
120
+ uid: socket.uid,
121
+ });
115
122
  };
116
123
 
117
124
  Sockets.removeVote = async function (socket, data) {
@@ -130,24 +137,32 @@ Sockets.removeVote = async function (socket, data) {
130
137
 
131
138
  await Vote.remove(data);
132
139
  const pollData = await Poll.get(data.pollId, socket.uid, false);
133
- NodeBB.SocketIndex.server.sockets.emit('event:poll.voteChange', { pollData, uid: socket.uid });
140
+ NodeBB.SocketIndex.server.sockets.emit('event:poll.voteChange', {
141
+ pollData,
142
+ uid: socket.uid,
143
+ });
134
144
  };
135
145
 
136
146
  Sockets.getOptionDetails = async function (socket, data) {
137
147
  if (!socket.uid || !data || isNaN(parseInt(data.pollId, 10)) || isNaN(parseInt(data.optionId, 10))) {
138
148
  throw new Error('Invalid request');
139
149
  }
140
- const [poll, settings, option] = await Promise.all([
150
+ const [poll, option] = await Promise.all([
141
151
  Poll.getInfo(data.pollId),
142
- Poll.getSettings(data.pollId),
143
152
  Poll.getOption(data.pollId, data.optionId, true),
144
153
  ]);
145
-
146
- if (!option.votes || !option.votes.length) {
154
+ if (!poll) {
155
+ throw new Error('[[error:invalid-data]]');
156
+ }
157
+ const canRead = await privileges.posts.can('topics:read', poll.pid, socket.uid);
158
+ if (!canRead) {
159
+ throw new Error('[[error:no-privileges]]');
160
+ }
161
+ if (!option || !option.votes || !option.votes.length) {
147
162
  return option;
148
163
  }
149
164
 
150
- if (parseInt(settings.allowAnonVoting, 10) === 1) {
165
+ if (parseInt(poll.allowAnonVoting, 10) === 1) {
151
166
  await anonymizeVoters(socket.uid, data.pollId, [option]);
152
167
  }
153
168
 
@@ -164,21 +179,22 @@ Sockets.getOptionDetails = async function (socket, data) {
164
179
  };
165
180
 
166
181
  Sockets.canCreate = async function (socket, data) {
167
- if (!socket.uid || !data || (isNaN(parseInt(data.cid, 10)) && isNaN(parseInt(data.pid, 10)))) {
182
+ if (!socket.uid || !data) {
168
183
  throw new Error('Invalid request');
169
184
  }
170
-
171
- if (!data.cid) {
172
- const tid = await NodeBB.Posts.getPostField(data.pid, 'tid');
173
- data.cid = await NodeBB.Topics.getTopicField(tid, 'cid');
185
+ let { cid, tid, pid } = data;
186
+ if (pid) {
187
+ cid = await posts.getCidByPid(pid);
188
+ } else if (tid) {
189
+ cid = await topics.getTopicField(tid, 'cid');
174
190
  }
175
- return await checkPrivs(data.cid, socket.uid);
191
+ return await checkPrivs(cid, socket.uid);
176
192
  };
177
193
 
178
194
  async function anonymizeVoters(callerUid, pollId, options) {
179
195
  const uids = _.uniq(options.map(opt => opt.votes).flat());
180
196
  const [isAnon, isPrivileged] = await Promise.all([
181
- NodeBB.db.isSortedSetMembers(`poll:${pollId}:anon:voters`, uids),
197
+ db.isSortedSetMembers(`poll:${pollId}:anon:voters`, uids),
182
198
  NodeBB.User.isPrivileged(callerUid),
183
199
  ]);
184
200
  if (isPrivileged) {