refactor: remove async.waterfall from remaining upgrade scripts

isekai-main
Barış Soner Uşaklı 4 years ago
parent 04ee5c0dfb
commit 6b34065f2a

@ -1,107 +1,52 @@
'use strict';
/* eslint-disable no-await-in-loop */
'use strict';
const async = require('async');
const winston = require('winston');
const db = require('../../database');
module.exports = {
name: 'Granting edit/delete/delete topic on existing categories',
timestamp: Date.UTC(2016, 7, 7),
method: function (callback) {
method: async function () {
const groupsAPI = require('../../groups');
const privilegesAPI = require('../../privileges');
db.getSortedSetRange('categories:cid', 0, -1, (err, cids) => {
if (err) {
return callback(err);
const cids = await db.getSortedSetRange('categories:cid', 0, -1);
for (const cid of cids) {
const data = await privilegesAPI.categories.list(cid);
const { groups, users } = data;
for (const group of groups) {
if (group.privileges['groups:topics:reply']) {
await Promise.all([
groupsAPI.join(`cid:${cid}:privileges:groups:posts:edit`, group.name),
groupsAPI.join(`cid:${cid}:privileges:groups:posts:delete`, group.name),
]);
winston.verbose(`cid:${cid}:privileges:groups:posts:edit, cid:${cid}:privileges:groups:posts:delete granted to gid: ${group.name}`);
}
if (group.privileges['groups:topics:create']) {
await groupsAPI.join(`cid:${cid}:privileges:groups:topics:delete`, group.name);
winston.verbose(`cid:${cid}:privileges:groups:topics:delete granted to gid: ${group.name}`);
}
}
async.eachSeries(cids, (cid, next) => {
privilegesAPI.categories.list(cid, (err, data) => {
if (err) {
return next(err);
}
const { groups } = data;
const { users } = data;
async.waterfall([
function (next) {
async.eachSeries(groups, (group, next) => {
if (group.privileges['groups:topics:reply']) {
return async.parallel([
async.apply(groupsAPI.join, `cid:${cid}:privileges:groups:posts:edit`, group.name),
async.apply(groupsAPI.join, `cid:${cid}:privileges:groups:posts:delete`, group.name),
], (err) => {
if (!err) {
winston.verbose(`cid:${cid}:privileges:groups:posts:edit, cid:${cid}:privileges:groups:posts:delete granted to gid: ${group.name}`);
}
return next(err);
});
}
next(null);
}, next);
},
function (next) {
async.eachSeries(groups, (group, next) => {
if (group.privileges['groups:topics:create']) {
return groupsAPI.join(`cid:${cid}:privileges:groups:topics:delete`, group.name, (err) => {
if (!err) {
winston.verbose(`cid:${cid}:privileges:groups:topics:delete granted to gid: ${group.name}`);
}
return next(err);
});
}
next(null);
}, next);
},
function (next) {
async.eachSeries(users, (user, next) => {
if (user.privileges['topics:reply']) {
return async.parallel([
async.apply(groupsAPI.join, `cid:${cid}:privileges:posts:edit`, user.uid),
async.apply(groupsAPI.join, `cid:${cid}:privileges:posts:delete`, user.uid),
], (err) => {
if (!err) {
winston.verbose(`cid:${cid}:privileges:posts:edit, cid:${cid}:privileges:posts:delete granted to uid: ${user.uid}`);
}
return next(err);
});
}
next(null);
}, next);
},
function (next) {
async.eachSeries(users, (user, next) => {
if (user.privileges['topics:create']) {
return groupsAPI.join(`cid:${cid}:privileges:topics:delete`, user.uid, (err) => {
if (!err) {
winston.verbose(`cid:${cid}:privileges:topics:delete granted to uid: ${user.uid}`);
}
return next(err);
});
}
next(null);
}, next);
},
], (err) => {
if (!err) {
winston.verbose(`-- cid ${cid} upgraded`);
}
next(err);
});
});
}, callback);
});
for (const user of users) {
if (user.privileges['topics:reply']) {
await Promise.all([
groupsAPI.join(`cid:${cid}:privileges:posts:edit`, user.uid),
groupsAPI.join(`cid:${cid}:privileges:posts:delete`, user.uid),
]);
winston.verbose(`cid:${cid}:privileges:posts:edit, cid:${cid}:privileges:posts:delete granted to uid: ${user.uid}`);
}
if (user.privileges['topics:create']) {
await groupsAPI.join(`cid:${cid}:privileges:topics:delete`, user.uid);
winston.verbose(`cid:${cid}:privileges:topics:delete granted to uid: ${user.uid}`);
}
}
winston.verbose(`-- cid ${cid} upgraded`);
}
},
};

@ -1,56 +1,39 @@
'use strict';
const async = require('async');
const db = require('../../database');
module.exports = {
name: 'Favourites to Bookmarks',
timestamp: Date.UTC(2016, 9, 8),
method: function (callback) {
method: async function () {
const { progress } = this;
const batch = require('../../batch');
function upgradePosts(next) {
const batch = require('../../batch');
batch.processSortedSet('posts:pid', (ids, next) => {
async.each(ids, (id, next) => {
async function upgradePosts() {
await batch.processSortedSet('posts:pid', async (ids) => {
await Promise.all(ids.map(async (id) => {
progress.incr();
async.waterfall([
function (next) {
db.rename(`pid:${id}:users_favourited`, `pid:${id}:users_bookmarked`, next);
},
function (next) {
db.getObjectField(`post:${id}`, 'reputation', next);
},
function (reputation, next) {
if (parseInt(reputation, 10)) {
db.setObjectField(`post:${id}`, 'bookmarks', reputation, next);
} else {
next();
}
},
function (next) {
db.deleteObjectField(`post:${id}`, 'reputation', next);
},
], next);
}, next);
await db.rename(`pid:${id}:users_favourited`, `pid:${id}:users_bookmarked`);
const reputation = await db.getObjectField(`post:${id}`, 'reputation');
if (parseInt(reputation, 10)) {
await db.setObjectField(`post:${id}`, 'bookmarks', reputation);
}
await db.deleteObjectField(`post:${id}`, 'reputation');
}));
}, {
progress: progress,
}, next);
});
}
function upgradeUsers(next) {
const batch = require('../../batch');
batch.processSortedSet('users:joindate', (ids, next) => {
async.each(ids, (id, next) => {
db.rename(`uid:${id}:favourites`, `uid:${id}:bookmarks`, next);
}, next);
}, {}, next);
async function upgradeUsers() {
await batch.processSortedSet('users:joindate', async (ids) => {
await Promise.all(ids.map(async (id) => {
await db.rename(`uid:${id}:favourites`, `uid:${id}:bookmarks`);
}));
}, {});
}
async.series([upgradePosts, upgradeUsers], callback);
await upgradePosts();
await upgradeUsers();
},
};

@ -1,57 +1,37 @@
'use strict';
const async = require('async');
const db = require('../../database');
module.exports = {
name: 'Update global and user language keys',
timestamp: Date.UTC(2016, 10, 22),
method: function (callback) {
method: async function () {
const { progress } = this;
const user = require('../../user');
const meta = require('../../meta');
const batch = require('../../batch');
let newLanguage;
async.parallel([
function (next) {
meta.configs.get('defaultLang', (err, defaultLang) => {
if (err) {
return next(err);
}
if (!defaultLang) {
return setImmediate(next);
}
const defaultLang = await meta.configs.get('defaultLang');
if (defaultLang) {
const newLanguage = defaultLang.replace('_', '-').replace('@', '-x-');
if (newLanguage !== defaultLang) {
await meta.configs.set('defaultLang', newLanguage);
}
}
newLanguage = defaultLang.replace('_', '-').replace('@', '-x-');
if (newLanguage !== defaultLang) {
meta.configs.set('defaultLang', newLanguage, next);
} else {
setImmediate(next);
await batch.processSortedSet('users:joindate', async (ids) => {
await Promise.all(ids.map(async (uid) => {
progress.incr();
const language = await db.getObjectField(`user:${uid}:settings`, 'userLang');
if (language) {
const newLanguage = language.replace('_', '-').replace('@', '-x-');
if (newLanguage !== language) {
await user.setSetting(uid, 'userLang', newLanguage);
}
});
},
function (next) {
batch.processSortedSet('users:joindate', (ids, next) => {
async.each(ids, (uid, next) => {
async.waterfall([
async.apply(db.getObjectField, `user:${uid}:settings`, 'userLang'),
function (language, next) {
if (!language) {
return setImmediate(next);
}
newLanguage = language.replace('_', '-').replace('@', '-x-');
if (newLanguage !== language) {
user.setSetting(uid, 'userLang', newLanguage, next);
} else {
setImmediate(next);
}
},
], next);
}, next);
}, next);
},
], callback);
}
}));
}, {
progress: progress,
});
},
};

@ -1,36 +1,34 @@
'use strict';
const async = require('async');
const db = require('../../database');
const db = require('../../database');
module.exports = {
name: 'Upgrading config urls to use assets route',
timestamp: Date.UTC(2017, 1, 28),
method: function (callback) {
async.waterfall([
function (cb) {
db.getObject('config', cb);
},
function (config, cb) {
if (!config) {
return cb();
method: async function () {
const config = await db.getObject('config');
if (config) {
const keys = [
'brand:favicon',
'brand:touchicon',
'og:image',
'brand:logo:url',
'defaultAvatar',
'profile:defaultCovers',
];
keys.forEach((key) => {
const oldValue = config[key];
if (!oldValue || typeof oldValue !== 'string') {
return;
}
const keys = ['brand:favicon', 'brand:touchicon', 'og:image', 'brand:logo:url', 'defaultAvatar', 'profile:defaultCovers'];
keys.forEach((key) => {
const oldValue = config[key];
if (!oldValue || typeof oldValue !== 'string') {
return;
}
config[key] = oldValue.replace(/(?:\/assets)?\/(images|uploads)\//g, '/assets/$1/');
});
config[key] = oldValue.replace(/(?:\/assets)?\/(images|uploads)\//g, '/assets/$1/');
});
db.setObject('config', config, cb);
},
], callback);
await db.setObject('config', config);
}
},
};

@ -1,7 +1,5 @@
'use strict';
const async = require('async');
const nconf = require('nconf');
const db = require('../../database');
const batch = require('../../batch');
@ -9,7 +7,7 @@ const batch = require('../../batch');
module.exports = {
name: 'Delete accidentally long-lived sessions',
timestamp: Date.UTC(2017, 3, 16),
method: function (callback) {
method: async function () {
let configJSON;
try {
configJSON = require('../../../config.json') || { [process.env.database]: true };
@ -20,44 +18,24 @@ module.exports = {
const isRedisSessionStore = configJSON.hasOwnProperty('redis');
const { progress } = this;
async.waterfall([
function (next) {
if (isRedisSessionStore) {
const connection = require('../../database/redis/connection');
let client;
async.waterfall([
function (next) {
connection.connect(nconf.get('redis'), next);
},
function (_client, next) {
client = _client;
client.keys('sess:*', next);
},
function (sessionKeys, next) {
progress.total = sessionKeys.length;
if (isRedisSessionStore) {
const connection = require('../../database/redis/connection');
const client = await connection.connect(nconf.get('redis'));
const sessionKeys = await client.keys('sess:*');
progress.total = sessionKeys.length;
batch.processArray(sessionKeys, (keys, next) => {
const multi = client.multi();
keys.forEach((key) => {
progress.incr();
multi.del(key);
});
multi.exec(next);
}, {
batch: 1000,
}, next);
},
], (err) => {
next(err);
});
} else if (db.client && db.client.collection) {
db.client.collection('sessions').deleteMany({}, {}, (err) => {
next(err);
});
} else {
next();
}
},
], callback);
await batch.processArray(sessionKeys, async (keys) => {
const multi = client.multi();
keys.forEach((key) => {
progress.incr();
multi.del(key);
});
await multi.exec();
}, {
batch: 1000,
});
} else if (db.client && db.client.collection) {
await db.client.collection('sessions').deleteMany({}, {});
}
},
};

@ -1,88 +1,56 @@
'use strict';
const async = require('async');
const db = require('../../database');
module.exports = {
name: 'Migrating flags to new schema',
timestamp: Date.UTC(2016, 11, 7),
method: function (callback) {
method: async function () {
const batch = require('../../batch');
const posts = require('../../posts');
const flags = require('../../flags');
const { progress } = this;
batch.processSortedSet('posts:pid', (ids, next) => {
posts.getPostsByPids(ids, 1, (err, posts) => {
if (err) {
return next(err);
}
posts = posts.filter(post => post.hasOwnProperty('flags'));
async.each(posts, (post, next) => {
progress.incr();
async.parallel({
uids: async.apply(db.getSortedSetRangeWithScores, `pid:${post.pid}:flag:uids`, 0, -1),
reasons: async.apply(db.getSortedSetRange, `pid:${post.pid}:flag:uid:reason`, 0, -1),
}, (err, data) => {
if (err) {
return next(err);
await batch.processSortedSet('posts:pid', async (ids) => {
let postData = await posts.getPostsByPids(ids, 1);
postData = postData.filter(post => post.hasOwnProperty('flags'));
await Promise.all(postData.map(async (post) => {
progress.incr();
const [uids, reasons] = await Promise.all([
db.getSortedSetRangeWithScores(`pid:${post.pid}:flag:uids`, 0, -1),
db.getSortedSetRange(`pid:${post.pid}:flag:uid:reason`, 0, -1),
]);
// Adding in another check here in case a post was improperly dismissed (flag count > 1 but no flags in db)
if (uids.length && reasons.length) {
// Just take the first entry
const datetime = uids[0].score;
const reason = reasons[0].split(':')[1];
try {
const flagObj = await flags.create('post', post.pid, uids[0].value, reason, datetime);
if (post['flag:state'] || post['flag:assignee']) {
await flags.update(flagObj.flagId, 1, {
state: post['flag:state'],
assignee: post['flag:assignee'],
datetime: datetime,
});
}
// Adding in another check here in case a post was improperly dismissed (flag count > 1 but no flags in db)
if (!data.uids.length || !data.reasons.length) {
return setImmediate(next);
if (post.hasOwnProperty('flag:notes') && post['flag:notes'].length) {
let history = JSON.parse(post['flag:history']);
history = history.filter(event => event.type === 'notes')[0];
await flags.appendNote(flagObj.flagId, history.uid, post['flag:notes'], history.timestamp);
}
// Just take the first entry
const datetime = data.uids[0].score;
const reason = data.reasons[0].split(':')[1];
let flagObj;
async.waterfall([
async.apply(flags.create, 'post', post.pid, data.uids[0].value, reason, datetime),
function (_flagObj, next) {
flagObj = _flagObj;
if (post['flag:state'] || post['flag:assignee']) {
flags.update(flagObj.flagId, 1, {
state: post['flag:state'],
assignee: post['flag:assignee'],
datetime: datetime,
}, next);
} else {
setImmediate(next);
}
},
function (next) {
if (post.hasOwnProperty('flag:notes') && post['flag:notes'].length) {
try {
let history = JSON.parse(post['flag:history']);
history = history.filter(event => event.type === 'notes')[0];
flags.appendNote(flagObj.flagId, history.uid, post['flag:notes'], history.timestamp, next);
} catch (e) {
next(e);
}
} else {
setImmediate(next);
}
},
], (err) => {
if (err && err.message === '[[error:post-already-flagged]]') {
// Already flagged, no need to parse, but not an error
next();
} else {
next(err);
}
});
});
}, next);
});
} catch (err) {
if (err.message !== '[[error:post-already-flagged]]') {
throw err;
}
}
}
}));
}, {
progress: this.progress,
}, callback);
});
},
};

@ -1,36 +1,26 @@
'use strict';
const async = require('async');
const db = require('../../database');
const batch = require('../../batch');
module.exports = {
name: 'Remove relative_path from uploaded profile cover urls',
timestamp: Date.UTC(2017, 3, 26),
method: function (callback) {
method: async function () {
const { progress } = this;
batch.processSortedSet('users:joindate', (ids, done) => {
async.each(ids, (uid, cb) => {
async.waterfall([
function (next) {
db.getObjectField(`user:${uid}`, 'cover:url', next);
},
function (url, next) {
progress.incr();
if (!url) {
return next();
}
await batch.processSortedSet('users:joindate', async (ids) => {
await Promise.all(ids.map(async (uid) => {
const url = await db.getObjectField(`user:${uid}`, 'cover:url');
progress.incr();
const newUrl = url.replace(/^.*?\/uploads\//, '/assets/uploads/');
db.setObjectField(`user:${uid}`, 'cover:url', newUrl, next);
},
], cb);
}, done);
if (url) {
const newUrl = url.replace(/^.*?\/uploads\//, '/assets/uploads/');
await db.setObjectField(`user:${uid}`, 'cover:url', newUrl);
}
}));
}, {
progress: this.progress,
}, callback);
});
},
};

@ -1,31 +1,21 @@
'use strict';
const async = require('async');
const crypto = require('crypto');
const meta = require('../../meta');
module.exports = {
name: 'Clearing stale digest templates that were accidentally saved as custom',
timestamp: Date.UTC(2017, 8, 6),
method: function (callback) {
method: async function () {
const matches = [
'112e541b40023d6530dd44df4b0d9c5d', // digest @ 75917e25b3b5ad7bed8ed0c36433fb35c9ab33eb
'110b8805f70395b0282fd10555059e9f', // digest @ 9b02bb8f51f0e47c6e335578f776ffc17bc03537
'9538e7249edb369b2a25b03f2bd3282b', // digest @ 3314ab4b83138c7ae579ac1f1f463098b8c2d414
];
async.waterfall([
async.apply(meta.configs.getFields, ['email:custom:digest']),
function (fieldset, next) {
const hash = fieldset['email:custom:digest'] ? crypto.createHash('md5').update(fieldset['email:custom:digest']).digest('hex') : null;
if (matches.includes(hash)) {
meta.configs.remove('email:custom:digest', next);
} else {
setImmediate(next);
}
},
], callback);
const fieldset = await meta.configs.getFields(['email:custom:digest']);
const hash = fieldset['email:custom:digest'] ? crypto.createHash('md5').update(fieldset['email:custom:digest']).digest('hex') : null;
if (matches.includes(hash)) {
await meta.configs.remove('email:custom:digest');
}
},
};

@ -1,25 +1,13 @@
'use strict';
const async = require('async');
const db = require('../../database');
module.exports = {
name: 'Changing ip blacklist storage to object',
timestamp: Date.UTC(2017, 8, 7),
method: function (callback) {
let rules;
async.waterfall([
function (next) {
db.get('ip-blacklist-rules', next);
},
function (_rules, next) {
rules = _rules;
db.delete('ip-blacklist-rules', rules ? next : callback);
},
function (next) {
db.setObject('ip-blacklist-rules', { rules: rules }, next);
},
], callback);
method: async function () {
const rules = await db.get('ip-blacklist-rules');
await db.delete('ip-blacklist-rules');
await db.setObject('ip-blacklist-rules', { rules: rules });
},
};

@ -1,35 +1,21 @@
'use strict';
const async = require('async');
const db = require('../../database');
module.exports = {
name: 'Fix incorrect robots.txt schema',
timestamp: Date.UTC(2017, 6, 10),
method: function (callback) {
async.waterfall([
function (next) {
db.getObject('config', next);
},
function (config, next) {
if (!config) {
return callback();
}
// fix mongo nested data
if (config.robots && config.robots.txt) {
db.setObjectField('config', 'robots:txt', config.robots.txt, next);
} else if (typeof config['robots.txt'] === 'string' && config['robots.txt']) {
db.setObjectField('config', 'robots:txt', config['robots.txt'], next);
} else {
next();
}
},
function (next) {
db.deleteObjectField('config', 'robots', next);
},
function (next) {
db.deleteObjectField('config', 'robots.txt', next);
},
], callback);
method: async function () {
const config = await db.getObject('config');
if (config) {
// fix mongo nested data
if (config.robots && config.robots.txt) {
await db.setObjectField('config', 'robots:txt', config.robots.txt);
} else if (typeof config['robots.txt'] === 'string' && config['robots.txt']) {
await db.setObjectField('config', 'robots:txt', config['robots.txt']);
}
await db.deleteObjectField('config', 'robots');
await db.deleteObjectField('config', 'robots.txt');
}
},
};

@ -1,48 +1,31 @@
'use strict';
const async = require('async');
const batch = require('../../batch');
const db = require('../../database');
module.exports = {
name: 'Convert old notification digest settings',
timestamp: Date.UTC(2017, 10, 15),
method: function (callback) {
method: async function () {
const { progress } = this;
batch.processSortedSet('users:joindate', (uids, next) => {
async.eachLimit(uids, 500, (uid, next) => {
await batch.processSortedSet('users:joindate', async (uids) => {
await Promise.all(uids.map(async (uid) => {
progress.incr();
async.waterfall([
function (next) {
db.getObjectFields(`user:${uid}:settings`, ['sendChatNotifications', 'sendPostNotifications'], next);
},
function (userSettings, _next) {
if (!userSettings) {
return next();
}
const tasks = [];
if (parseInt(userSettings.sendChatNotifications, 10) === 1) {
tasks.push(async.apply(db.setObjectField, `user:${uid}:settings`, 'notificationType_new-chat', 'notificationemail'));
}
if (parseInt(userSettings.sendPostNotifications, 10) === 1) {
tasks.push(async.apply(db.setObjectField, `user:${uid}:settings`, 'notificationType_new-reply', 'notificationemail'));
}
if (!tasks.length) {
return next();
}
async.series(tasks, (err) => {
_next(err);
});
},
function (next) {
db.deleteObjectFields(`user:${uid}:settings`, ['sendChatNotifications', 'sendPostNotifications'], next);
},
], next);
}, next);
const userSettings = await db.getObjectFields(`user:${uid}:settings`, ['sendChatNotifications', 'sendPostNotifications']);
if (userSettings) {
if (parseInt(userSettings.sendChatNotifications, 10) === 1) {
await db.setObjectField(`user:${uid}:settings`, 'notificationType_new-chat', 'notificationemail');
}
if (parseInt(userSettings.sendPostNotifications, 10) === 1) {
await db.setObjectField(`user:${uid}:settings`, 'notificationType_new-reply', 'notificationemail');
}
}
await db.deleteObjectFields(`user:${uid}:settings`, ['sendChatNotifications', 'sendPostNotifications']);
}));
}, {
progress: progress,
}, callback);
batch: 500,
});
},
};

@ -1,13 +1,13 @@
'use strict';
/* eslint-disable no-await-in-loop */
const async = require('async');
'use strict';
const db = require('../../database');
module.exports = {
name: 'Change the schema of simple keys so they don\'t use value field (mongodb only)',
timestamp: Date.UTC(2017, 11, 18),
method: function (callback) {
method: async function () {
let configJSON;
try {
configJSON = require('../../../config.json') || { [process.env.database]: true, database: process.env.database };
@ -17,56 +17,29 @@ module.exports = {
const isMongo = configJSON.hasOwnProperty('mongo') && configJSON.database === 'mongo';
const { progress } = this;
if (!isMongo) {
return callback();
return;
}
const { client } = db;
let cursor;
async.waterfall([
function (next) {
client.collection('objects').countDocuments({
_key: { $exists: true },
value: { $exists: true },
score: { $exists: false },
}, next);
},
function (count, next) {
progress.total = count;
cursor = client.collection('objects').find({
_key: { $exists: true },
value: { $exists: true },
score: { $exists: false },
}).batchSize(1000);
const query = {
_key: { $exists: true },
value: { $exists: true },
score: { $exists: false },
};
progress.total = await client.collection('objects').countDocuments(query);
const cursor = await client.collection('objects').find(query).batchSize(1000);
let done = false;
async.whilst(
(next) => {
next(null, !done);
},
(next) => {
async.waterfall([
function (next) {
cursor.next(next);
},
function (item, next) {
progress.incr();
if (item === null) {
done = true;
return next();
}
delete item.expireAt;
if (Object.keys(item).length === 3 && item.hasOwnProperty('_key') && item.hasOwnProperty('value')) {
client.collection('objects').updateOne({ _key: item._key }, { $rename: { value: 'data' } }, next);
} else {
next();
}
},
], (err) => {
next(err);
});
},
next
);
},
], callback);
let done = false;
while (!done) {
const item = await cursor.next();
progress.incr();
if (item === null) {
done = true;
} else {
delete item.expireAt;
if (Object.keys(item).length === 3 && item.hasOwnProperty('_key') && item.hasOwnProperty('value')) {
await client.collection('objects').updateOne({ _key: item._key }, { $rename: { value: 'data' } });
}
}
}
},
};

@ -1,34 +1,22 @@
'use strict';
const async = require('async');
const batch = require('../../batch');
const db = require('../../database');
module.exports = {
name: 'Add votes to topics',
timestamp: Date.UTC(2017, 11, 8),
method: function (callback) {
method: async function () {
const { progress } = this;
batch.processSortedSet('topics:tid', (tids, next) => {
async.eachLimit(tids, 500, (tid, _next) => {
batch.processSortedSet('topics:tid', async (tids) => {
await Promise.all(tids.map(async (tid) => {
progress.incr();
let topicData;
async.waterfall([
function (next) {
db.getObjectFields(`topic:${tid}`, ['mainPid', 'cid', 'pinned'], next);
},
function (_topicData, next) {
topicData = _topicData;
if (!topicData.mainPid || !topicData.cid) {
return _next();
}
db.getObject(`post:${topicData.mainPid}`, next);
},
function (postData, next) {
if (!postData) {
return _next();
}
const topicData = await db.getObjectFields(`topic:${tid}`, ['mainPid', 'cid', 'pinned']);
if (topicData.mainPid && topicData.cid) {
const postData = await db.getObject(`post:${topicData.mainPid}`);
if (postData) {
const upvotes = parseInt(postData.upvotes, 10) || 0;
const downvotes = parseInt(postData.downvotes, 10) || 0;
const data = {
@ -36,29 +24,19 @@ module.exports = {
downvotes: downvotes,
};
const votes = upvotes - downvotes;
async.parallel([
function (next) {
db.setObject(`topic:${tid}`, data, next);
},
function (next) {
db.sortedSetAdd('topics:votes', votes, tid, next);
},
function (next) {
if (parseInt(topicData.pinned, 10) !== 1) {
db.sortedSetAdd(`cid:${topicData.cid}:tids:votes`, votes, tid, next);
} else {
next();
}
},
], (err) => {
next(err);
});
},
], _next);
}, next);
await Promise.all([
db.setObject(`topic:${tid}`, data),
db.sortedSetAdd('topics:votes', votes, tid),
]);
if (parseInt(topicData.pinned, 10) !== 1) {
await db.sortedSetAdd(`cid:${topicData.cid}:tids:votes`, votes, tid);
}
}
}
}));
}, {
progress: progress,
batch: 500,
}, callback);
});
},
};

@ -1,53 +1,31 @@
'use strict';
const async = require('async');
const batch = require('../../batch');
const db = require('../../database');
module.exports = {
name: 'Fix sort by votes for moved topics',
timestamp: Date.UTC(2018, 0, 8),
method: function (callback) {
method: async function () {
const { progress } = this;
batch.processSortedSet('topics:tid', (tids, next) => {
async.eachLimit(tids, 500, (tid, _next) => {
await batch.processSortedSet('topics:tid', async (tids) => {
await Promise.all(tids.map(async (tid) => {
progress.incr();
let topicData;
async.waterfall([
function (next) {
db.getObjectFields(`topic:${tid}`, ['cid', 'oldCid', 'upvotes', 'downvotes', 'pinned'], next);
},
function (_topicData, next) {
topicData = _topicData;
if (!topicData.cid || !topicData.oldCid) {
return _next();
}
const upvotes = parseInt(topicData.upvotes, 10) || 0;
const downvotes = parseInt(topicData.downvotes, 10) || 0;
const votes = upvotes - downvotes;
async.series([
function (next) {
db.sortedSetRemove(`cid:${topicData.oldCid}:tids:votes`, tid, next);
},
function (next) {
if (parseInt(topicData.pinned, 10) !== 1) {
db.sortedSetAdd(`cid:${topicData.cid}:tids:votes`, votes, tid, next);
} else {
next();
}
},
], (err) => {
next(err);
});
},
], _next);
}, next);
const topicData = await db.getObjectFields(`topic:${tid}`, ['cid', 'oldCid', 'upvotes', 'downvotes', 'pinned']);
if (topicData.cid && topicData.oldCid) {
const upvotes = parseInt(topicData.upvotes, 10) || 0;
const downvotes = parseInt(topicData.downvotes, 10) || 0;
const votes = upvotes - downvotes;
await db.sortedSetRemove(`cid:${topicData.oldCid}:tids:votes`, tid);
if (parseInt(topicData.pinned, 10) !== 1) {
await db.sortedSetAdd(`cid:${topicData.cid}:tids:votes`, votes, tid);
}
}
}));
}, {
progress: progress,
batch: 500,
}, callback);
});
},
};

@ -1,52 +1,29 @@
'use strict';
const async = require('async');
const batch = require('../../batch');
const db = require('../../database');
module.exports = {
name: 'Fix topics in categories per user if they were moved',
timestamp: Date.UTC(2018, 0, 22),
method: function (callback) {
method: async function () {
const { progress } = this;
batch.processSortedSet('topics:tid', (tids, next) => {
async.eachLimit(tids, 500, (tid, _next) => {
await batch.processSortedSet('topics:tid', async (tids) => {
await Promise.all(tids.map(async (tid) => {
progress.incr();
let topicData;
async.waterfall([
function (next) {
db.getObjectFields(`topic:${tid}`, ['cid', 'tid', 'uid', 'oldCid', 'timestamp'], next);
},
function (_topicData, next) {
topicData = _topicData;
if (!topicData.cid || !topicData.oldCid) {
return _next();
}
db.isSortedSetMember(`cid:${topicData.oldCid}:uid:${topicData.uid}`, topicData.tid, next);
},
function (isMember, next) {
if (isMember) {
async.series([
function (next) {
db.sortedSetRemove(`cid:${topicData.oldCid}:uid:${topicData.uid}:tids`, tid, next);
},
function (next) {
db.sortedSetAdd(`cid:${topicData.cid}:uid:${topicData.uid}:tids`, topicData.timestamp, tid, next);
},
], (err) => {
next(err);
});
} else {
next();
}
},
], _next);
}, next);
const topicData = await db.getObjectFields(`topic:${tid}`, ['cid', 'tid', 'uid', 'oldCid', 'timestamp']);
if (topicData.cid && topicData.oldCid) {
const isMember = await db.isSortedSetMember(`cid:${topicData.oldCid}:uid:${topicData.uid}`, topicData.tid);
if (isMember) {
await db.sortedSetRemove(`cid:${topicData.oldCid}:uid:${topicData.uid}:tids`, tid);
await db.sortedSetAdd(`cid:${topicData.cid}:uid:${topicData.uid}:tids`, topicData.timestamp, tid);
}
}
}));
}, {
progress: progress,
batch: 500,
}, callback);
});
},
};

@ -1,38 +1,24 @@
'use strict';
const async = require('async');
const db = require('../../database');
module.exports = {
name: 'Flatten navigation data',
timestamp: Date.UTC(2018, 1, 17),
method: function (callback) {
async.waterfall([
function (next) {
db.getSortedSetRangeWithScores('navigation:enabled', 0, -1, next);
},
function (data, next) {
const order = [];
const items = [];
data.forEach((item) => {
let navItem = JSON.parse(item.value);
const keys = Object.keys(navItem);
if (keys.length && parseInt(keys[0], 10) >= 0) {
navItem = navItem[keys[0]];
}
order.push(item.score);
items.push(JSON.stringify(navItem));
});
async.series([
function (next) {
db.delete('navigation:enabled', next);
},
function (next) {
db.sortedSetAdd('navigation:enabled', order, items, next);
},
], next);
},
], callback);
method: async function () {
const data = await db.getSortedSetRangeWithScores('navigation:enabled', 0, -1);
const order = [];
const items = [];
data.forEach((item) => {
let navItem = JSON.parse(item.value);
const keys = Object.keys(navItem);
if (keys.length && parseInt(keys[0], 10) >= 0) {
navItem = navItem[keys[0]];
}
order.push(item.score);
items.push(JSON.stringify(navItem));
});
await db.delete('navigation:enabled');
await db.sortedSetAdd('navigation:enabled', order, items);
},
};

@ -1,28 +1,21 @@
'use strict';
const async = require('async');
const db = require('../../database');
module.exports = {
name: 'Add default settings for notification delivery types',
timestamp: Date.UTC(2018, 1, 14),
method: function (callback) {
async.waterfall([
function (next) {
db.getObject('config', next);
},
function (config, next) {
const postNotifications = parseInt(config.sendPostNotifications, 10) === 1 ? 'notification' : 'none';
const chatNotifications = parseInt(config.sendChatNotifications, 10) === 1 ? 'notification' : 'none';
db.setObject('config', {
notificationType_upvote: config.notificationType_upvote || 'notification',
'notificationType_new-topic': config['notificationType_new-topic'] || 'notification',
'notificationType_new-reply': config['notificationType_new-reply'] || postNotifications,
notificationType_follow: config.notificationType_follow || 'notification',
'notificationType_new-chat': config['notificationType_new-chat'] || chatNotifications,
'notificationType_group-invite': config['notificationType_group-invite'] || 'notification',
}, next);
},
], callback);
method: async function () {
const config = await db.getObject('config');
const postNotifications = parseInt(config.sendPostNotifications, 10) === 1 ? 'notification' : 'none';
const chatNotifications = parseInt(config.sendChatNotifications, 10) === 1 ? 'notification' : 'none';
await db.setObject('config', {
notificationType_upvote: config.notificationType_upvote || 'notification',
'notificationType_new-topic': config['notificationType_new-topic'] || 'notification',
'notificationType_new-reply': config['notificationType_new-reply'] || postNotifications,
notificationType_follow: config.notificationType_follow || 'notification',
'notificationType_new-chat': config['notificationType_new-chat'] || chatNotifications,
'notificationType_group-invite': config['notificationType_group-invite'] || 'notification',
});
},
};

@ -1,22 +1,14 @@
'use strict';
const async = require('async');
const db = require('../../database');
module.exports = {
name: 'Revising minimum password strength to 1 (from 0)',
timestamp: Date.UTC(2018, 1, 21),
method: function (callback) {
async.waterfall([
async.apply(db.getObjectField.bind(db), 'config', 'minimumPasswordStrength'),
function (strength, next) {
if (!strength) {
return db.setObjectField('config', 'minimumPasswordStrength', 1, next);
}
setImmediate(next);
},
], callback);
method: async function () {
const strength = await db.getObjectField('config', 'minimumPasswordStrength');
if (!strength) {
await db.setObjectField('config', 'minimumPasswordStrength', 1);
}
},
};

Loading…
Cancel
Save