From 79556d9365c750c8f47498e17a1addac14b83482 Mon Sep 17 00:00:00 2001 From: Calvin Montgomery Date: Sat, 24 Feb 2018 19:47:50 -0800 Subject: [PATCH] deps: remove "q" (#731) Insert Star Trek joke here. Also did significant refactoring of the surrounding logic for the things that depended on Q. --- integration_test/channel/kickban.js | 581 ++++++++++++++++++++++++++++ package.json | 3 +- src/account.js | 86 +--- src/channel/kickban.js | 222 ++++++----- src/channel/ranks.js | 20 +- src/database.js | 5 +- src/database/channels.js | 4 +- src/database/update.js | 113 +----- 8 files changed, 738 insertions(+), 296 deletions(-) create mode 100644 integration_test/channel/kickban.js diff --git a/integration_test/channel/kickban.js b/integration_test/channel/kickban.js new file mode 100644 index 00000000..1fbb6758 --- /dev/null +++ b/integration_test/channel/kickban.js @@ -0,0 +1,581 @@ +const assert = require('assert'); +const KickbanModule = require('../../lib/channel/kickban'); +const database = require('../../lib/database'); +const Promise = require('bluebird'); +const testDB = require('../testutil/db').testDB; + +database.init(testDB); + +describe('KickbanModule', () => { + const channelName = `test_${Math.random().toString(31).substring(2)}`; + + let mockChannel; + let mockUser; + let kickban; + + beforeEach(() => { + mockChannel = { + name: channelName, + refCounter: { + ref() { }, + unref() { } + }, + logger: { + log() { } + }, + modules: { + permissions: { + canBan() { + return true; + } + } + }, + users: [] + }; + + mockUser = { + getName() { + return 'The_Admin'; + }, + + getLowerName() { + return 'the_admin'; + }, + + socket: { + emit(frame) { + if (frame === 'errorMsg') { + throw new Error(arguments[1].msg); + } + } + }, + + account: { + effectiveRank: 3 + } + }; + + kickban = new KickbanModule(mockChannel); + }); + + afterEach(async () => { + await database.getDB().runTransaction(async tx => { + await tx.table('channel_bans') + .where({ channel: channelName }) + .del(); + await tx.table('channel_ranks') + .where({ channel: channelName }) + .del(); + }); + }); + + describe('#handleCmdBan', () => { + it('inserts a valid ban', done => { + let kicked = false; + + mockChannel.refCounter.unref = () => { + assert(kicked, 'Expected user to be kicked'); + + database.getDB().runTransaction(async tx => { + const ban = await tx.table('channel_bans') + .where({ + channel: channelName, + name: 'test_user' + }) + .first(); + + assert.strictEqual(ban.ip, '*'); + assert.strictEqual(ban.reason, 'because reasons'); + assert.strictEqual(ban.bannedby, mockUser.getName()); + + done(); + }); + }; + + mockChannel.users = [{ + getLowerName() { + return 'test_user'; + }, + + kick(reason) { + assert.strictEqual(reason, "You're banned!"); + kicked = true; + } + }]; + + kickban.handleCmdBan( + mockUser, + '/ban test_user because reasons', + {} + ); + }); + + it('rejects if the user does not have ban permission', done => { + mockUser.socket.emit = (frame, obj) => { + if (frame === 'errorMsg') { + assert.strictEqual( + obj.msg, + 'You do not have ban permissions on this channel' + ); + + done(); + } + }; + + mockChannel.modules.permissions.canBan = () => false; + + kickban.handleCmdBan( + mockUser, + '/ban test_user because reasons', + {} + ); + }); + + it('rejects if the user tries to ban themselves', done => { + let costanza = false; + + mockUser.socket.emit = (frame, obj) => { + if (frame === 'errorMsg') { + assert.strictEqual( + obj.msg, + 'You cannot ban yourself' + ); + + if (!costanza) { + throw new Error('Expected costanza for banning self'); + } + + done(); + } else if (frame === 'costanza') { + assert.strictEqual( + obj.msg, + "You can't ban yourself" + ); + + costanza = true; + } + }; + + kickban.handleCmdBan( + mockUser, + '/ban the_Admin because reasons', + {} + ); + }); + + it('rejects if the user is ranked below the ban recipient', done => { + database.getDB().runTransaction(tx => { + return tx.table('channel_ranks') + .insert({ + channel: channelName, + name: 'test_user', + rank: 5 + }); + }).then(() => { + mockUser.socket.emit = (frame, obj) => { + if (frame === 'errorMsg') { + assert.strictEqual( + obj.msg, + "You don't have permission to ban test_user" + ); + + done(); + } + }; + + kickban.handleCmdBan( + mockUser, + '/ban test_user because reasons', + {} + ); + }); + }); + + it('rejects if the the ban recipient is already banned', done => { + database.getDB().runTransaction(tx => { + return tx.table('channel_bans') + .insert({ + channel: channelName, + name: 'test_user', + ip: '*', + bannedby: 'somebody', + reason: 'I dunno' + }); + }).then(() => { + mockUser.socket.emit = (frame, obj) => { + if (frame === 'errorMsg') { + assert.strictEqual( + obj.msg, + 'test_user is already banned' + ); + + done(); + } + }; + + kickban.handleCmdBan( + mockUser, + '/ban test_user because reasons', + {} + ); + }); + }); + }); + + describe('#handleCmdIPBan', () => { + beforeEach(async () => { + await database.getDB().runTransaction(async tx => { + await tx.table('aliases') + .insert([{ + name: 'test_user', + ip: '1.2.3.4' + }]); + }); + }); + + afterEach(async () => { + await database.getDB().runTransaction(async tx => { + await tx.table('aliases') + .where({ name: 'test_user' }) + .orWhere({ ip: '1.2.3.4' }) + .del(); + }); + }); + + it('inserts a valid ban', done => { + let firstUserKicked = false; + let secondUserKicked = false; + + mockChannel.refCounter.unref = () => { + assert(firstUserKicked, 'Expected banned user to be kicked'); + assert( + secondUserKicked, + 'Expected user with banned IP to be kicked' + ); + + database.getDB().runTransaction(async tx => { + const nameBan = await tx.table('channel_bans') + .where({ + channel: channelName, + name: 'test_user', + ip: '*' + }) + .first(); + + assert.strictEqual(nameBan.reason, 'because reasons'); + assert.strictEqual(nameBan.bannedby, mockUser.getName()); + + const ipBan = await tx.table('channel_bans') + .where({ + channel: channelName, + ip: '1.2.3.4' + }) + .first(); + + assert.strictEqual(ipBan.name, 'test_user'); + assert.strictEqual(ipBan.reason, 'because reasons'); + assert.strictEqual(ipBan.bannedby, mockUser.getName()); + + done(); + }); + }; + + mockChannel.users = [{ + getLowerName() { + return 'test_user'; + }, + + realip: '1.2.3.4', + + kick(reason) { + assert.strictEqual(reason, "You're banned!"); + firstUserKicked = true; + } + }, { + getLowerName() { + return 'second_user_same_ip'; + }, + + realip: '1.2.3.4', + + kick(reason) { + assert.strictEqual(reason, "You're banned!"); + secondUserKicked = true; + } + }]; + + kickban.handleCmdIPBan( + mockUser, + '/ipban test_user because reasons', + {} + ); + }); + + it('inserts a valid range ban', done => { + mockChannel.refCounter.unref = () => { + database.getDB().runTransaction(async tx => { + const ipBan = await tx.table('channel_bans') + .where({ + channel: channelName, + ip: '1.2.3' + }) + .first(); + + assert.strictEqual(ipBan.name, 'test_user'); + assert.strictEqual(ipBan.reason, 'because reasons'); + assert.strictEqual(ipBan.bannedby, mockUser.getName()); + + done(); + }); + }; + + kickban.handleCmdIPBan( + mockUser, + '/ipban test_user range because reasons', + {} + ); + }); + + it('inserts a valid wide-range ban', done => { + mockChannel.refCounter.unref = () => { + database.getDB().runTransaction(async tx => { + const ipBan = await tx.table('channel_bans') + .where({ + channel: channelName, + ip: '1.2' + }) + .first(); + + assert.strictEqual(ipBan.name, 'test_user'); + assert.strictEqual(ipBan.reason, 'because reasons'); + assert.strictEqual(ipBan.bannedby, mockUser.getName()); + + done(); + }); + }; + + kickban.handleCmdIPBan( + mockUser, + '/ipban test_user wrange because reasons', + {} + ); + }); + + it('inserts a valid IPv6 ban', done => { + const longIP = require('../../lib/utilities').expandIPv6('::abcd'); + + mockChannel.refCounter.unref = () => { + database.getDB().runTransaction(async tx => { + const ipBan = await tx.table('channel_bans') + .where({ + channel: channelName, + ip: longIP + }) + .first(); + + assert.strictEqual(ipBan.name, 'test_user'); + assert.strictEqual(ipBan.reason, 'because reasons'); + assert.strictEqual(ipBan.bannedby, mockUser.getName()); + + done(); + }); + }; + + database.getDB().runTransaction(async tx => { + await tx.table('aliases') + .insert({ + name: 'test_user', + ip: longIP + }); + }).then(() => { + kickban.handleCmdIPBan( + mockUser, + '/ipban test_user because reasons', + {} + ); + }); + }); + + it('rejects if the user does not have ban permission', done => { + mockUser.socket.emit = (frame, obj) => { + if (frame === 'errorMsg') { + assert.strictEqual( + obj.msg, + 'You do not have ban permissions on this channel' + ); + + done(); + } + }; + + mockChannel.modules.permissions.canBan = () => false; + + kickban.handleCmdIPBan( + mockUser, + '/ipban test_user because reasons', + {} + ); + }); + + it('rejects if the user tries to ban themselves', done => { + let costanza = false; + + mockUser.socket.emit = (frame, obj) => { + if (frame === 'errorMsg') { + assert.strictEqual( + obj.msg, + 'You cannot ban yourself' + ); + + if (!costanza) { + throw new Error('Expected costanza for banning self'); + } + + done(); + } else if (frame === 'costanza') { + assert.strictEqual( + obj.msg, + "You can't ban yourself" + ); + + costanza = true; + } + }; + + kickban.handleCmdIPBan( + mockUser, + '/ipban the_Admin because reasons', + {} + ); + }); + + it('rejects if the user is ranked below the ban recipient', done => { + database.getDB().runTransaction(tx => { + return tx.table('channel_ranks') + .insert({ + channel: channelName, + name: 'test_user', + rank: 5 + }); + }).then(() => { + mockUser.socket.emit = (frame, obj) => { + if (frame === 'errorMsg') { + assert.strictEqual( + obj.msg, + "You don't have permission to ban IP " + + "09l.TFb.5To.HBB" + ); + + done(); + } + }; + + kickban.handleCmdIPBan( + mockUser, + '/ipban test_user because reasons', + {} + ); + }); + }); + + it('rejects if the user is ranked below an alias of the ban recipient', done => { + database.getDB().runTransaction(async tx => { + await tx.table('channel_ranks') + .insert({ + channel: channelName, + name: 'another_user', + rank: 5 + }); + await tx.table('aliases') + .insert({ + name: 'another_user', + ip: '1.2.3.3' // different IP, same /24 range + }); + }).then(() => { + mockUser.socket.emit = (frame, obj) => { + if (frame === 'errorMsg') { + assert.strictEqual( + obj.msg, + "You don't have permission to ban IP " + + "09l.TFb.5To.*" + ); + + done(); + } + }; + + kickban.handleCmdIPBan( + mockUser, + '/ipban test_user range because reasons', + {} + ); + }); + }); + + it('rejects if the the ban recipient IP is already banned', done => { + database.getDB().runTransaction(tx => { + return tx.table('channel_bans') + .insert({ + channel: channelName, + name: 'another_user', + ip: '1.2.3.4', + bannedby: 'somebody', + reason: 'I dunno' + }); + }).then(() => { + mockUser.socket.emit = (frame, obj) => { + if (frame === 'errorMsg') { + assert.strictEqual( + obj.msg, + '09l.TFb.5To.HBB is already banned' + ); + + done(); + } + }; + + kickban.handleCmdIPBan( + mockUser, + '/ipban test_user because reasons', + {} + ); + }); + }); + + it('still adds the IP ban even if the name is already banned', done => { + mockChannel.refCounter.unref = () => { + database.getDB().runTransaction(async tx => { + const ipBan = await tx.table('channel_bans') + .where({ + channel: channelName, + ip: '1.2.3.4' + }) + .first(); + + assert.strictEqual(ipBan.name, 'test_user'); + assert.strictEqual(ipBan.reason, 'because reasons'); + assert.strictEqual(ipBan.bannedby, mockUser.getName()); + + done(); + }); + }; + + database.getDB().runTransaction(tx => { + return tx.table('channel_bans') + .insert({ + channel: channelName, + name: 'test_user', + ip: '*', + bannedby: 'somebody', + reason: 'I dunno' + }); + }).then(() => { + kickban.handleCmdIPBan( + mockUser, + '/ipban test_user because reasons', + {} + ); + }); + }); + }); +}); diff --git a/package.json b/package.json index b4809afc..4cfd165e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Calvin Montgomery", "name": "CyTube", "description": "Online media synchronizer and chat", - "version": "3.54.0", + "version": "3.55.0", "repository": { "url": "http://github.com/calzoneman/sync" }, @@ -33,7 +33,6 @@ "prom-client": "^10.0.2", "proxy-addr": "^2.0.2", "pug": "^2.0.0-beta3", - "q": "^1.4.1", "redis": "^2.4.2", "sanitize-html": "^1.14.1", "serve-static": "^1.13.2", diff --git a/src/account.js b/src/account.js index 559d771a..2aa3e5bd 100644 --- a/src/account.js +++ b/src/account.js @@ -1,5 +1,11 @@ -var db = require("./database"); -var Q = require("q"); +import db from './database'; +import Promise from 'bluebird'; + +const dbGetGlobalRank = Promise.promisify(db.users.getGlobalRank); +const dbMultiGetGlobalRank = Promise.promisify(db.users.getGlobalRanks); +const dbGetChannelRank = Promise.promisify(db.channels.getRank); +const dbMultiGetChannelRank = Promise.promisify(db.channels.getRanks); +const dbGetAliases = Promise.promisify(db.getAliases); const DEFAULT_PROFILE = Object.freeze({ image: '', text: '' }); @@ -33,71 +39,21 @@ class Account { module.exports.Account = Account; -module.exports.rankForName = function (name, opts, cb) { - if (!cb) { - cb = opts; - opts = {}; - } +module.exports.rankForName = async function rankForNameAsync(name, channel) { + const [globalRank, channelRank] = await Promise.all([ + dbGetGlobalRank(name), + dbGetChannelRank(channel, name) + ]); - var rank = 0; - Q.fcall(function () { - return Q.nfcall(db.users.getGlobalRank, name); - }).then(function (globalRank) { - rank = globalRank; - if (opts.channel) { - return Q.nfcall(db.channels.getRank, opts.channel, name); - } else { - return globalRank > 0 ? 1 : 0; - } - }).then(function (chanRank) { - setImmediate(function () { - cb(null, Math.max(rank, chanRank)); - }); - }).catch(function (err) { - cb(err, 0); - }).done(); + return Math.max(globalRank, channelRank); }; -module.exports.rankForIP = function (ip, opts, cb) { - if (!cb) { - cb = opts; - opts = {}; - } +module.exports.rankForIP = async function rankForIP(ip, channel) { + const aliases = await dbGetAliases(ip); + const [globalRanks, channelRanks] = await Promise.all([ + dbMultiGetGlobalRank(aliases), + dbMultiGetChannelRank(channel, aliases) + ]); - var globalRank, rank, names; - - var promise = Q.nfcall(db.getAliases, ip) - .then(function (_names) { - names = _names; - return Q.nfcall(db.users.getGlobalRanks, names); - }).then(function (ranks) { - ranks.push(0); - globalRank = Math.max.apply(Math, ranks); - rank = globalRank; - }); - - if (!opts.channel) { - promise.then(function () { - setImmediate(function () { - cb(null, globalRank); - }); - }).catch(function (err) { - cb(err, null); - }).done(); - } else { - promise.then(function () { - return Q.nfcall(db.channels.getRanks, opts.channel, names); - }).then(function (ranks) { - ranks.push(globalRank); - rank = Math.max.apply(Math, ranks); - }).then(function () { - setImmediate(function () { - cb(null, rank); - }); - }).catch(function (err) { - setImmediate(function () { - cb(err, null); - }); - }).done(); - } + return Math.max.apply(Math, globalRanks.concat(channelRanks)); }; diff --git a/src/channel/kickban.js b/src/channel/kickban.js index c349e9de..3feb0dbd 100644 --- a/src/channel/kickban.js +++ b/src/channel/kickban.js @@ -3,7 +3,12 @@ var db = require("../database"); var Flags = require("../flags"); var util = require("../utilities"); var Account = require("../account"); -var Q = require("q"); +import Promise from 'bluebird'; + +const dbIsNameBanned = Promise.promisify(db.channels.isNameBanned); +const dbIsIPBanned = Promise.promisify(db.channels.isIPBanned); +const dbAddBan = Promise.promisify(db.channels.ban); +const dbGetIPs = Promise.promisify(db.getIPs); const TYPE_UNBAN = { id: "number", @@ -234,7 +239,11 @@ KickBanModule.prototype.handleCmdBan = function (user, msg, meta) { const chan = this.channel; chan.refCounter.ref("KickBanModule::handleCmdBan"); - this.banName(user, name, reason, function (err) { + + this.banName(user, name, reason).catch(error => { + const message = error.message || error; + user.socket.emit("errorMsg", { msg: message }); + }).finally(() => { chan.refCounter.unref("KickBanModule::handleCmdBan"); }); }; @@ -261,23 +270,29 @@ KickBanModule.prototype.handleCmdIPBan = function (user, msg, meta) { const chan = this.channel; chan.refCounter.ref("KickBanModule::handleCmdIPBan"); - this.banAll(user, name, range, reason, function (err) { + + this.banAll(user, name, range, reason).catch(error => { + //console.log('!!!', error.stack); + const message = error.message || error; + user.socket.emit("errorMsg", { msg: message }); + }).finally(() => { chan.refCounter.unref("KickBanModule::handleCmdIPBan"); }); }; -KickBanModule.prototype.banName = function (actor, name, reason, cb) { - var self = this; +KickBanModule.prototype.checkChannelAlive = function checkChannelAlive() { + if (!this.channel || this.channel.dead) { + throw new Error("Channel not live"); + } +}; + +KickBanModule.prototype.banName = async function banName(actor, name, reason) { reason = reason.substring(0, 255); var chan = this.channel; - var error = function (what) { - actor.socket.emit("errorMsg", { msg: what }); - cb(what); - }; if (!chan.modules.permissions.canBan(actor)) { - return error("You do not have ban permissions on this channel"); + throw new Error("You do not have ban permissions on this channel"); } name = name.toLowerCase(); @@ -285,129 +300,126 @@ KickBanModule.prototype.banName = function (actor, name, reason, cb) { actor.socket.emit("costanza", { msg: "You can't ban yourself" }); - return cb("Attempted to ban self"); + + throw new Error("You cannot ban yourself"); } - Q.nfcall(Account.rankForName, name, { channel: chan.name }) - .then(function (rank) { - if (rank >= actor.account.effectiveRank) { - throw "You don't have permission to ban " + name; - } + const rank = await Account.rankForName(name, chan.name); + this.checkChannelAlive(); - return Q.nfcall(db.channels.isNameBanned, chan.name, name); - }).then(function (banned) { - if (banned) { - throw name + " is already banned"; - } + if (rank >= actor.account.effectiveRank) { + throw new Error("You don't have permission to ban " + name); + } - if (chan.dead) { throw null; } + const isBanned = await dbIsNameBanned(chan.name, name); + this.checkChannelAlive(); - return Q.nfcall(db.channels.ban, chan.name, "*", name, reason, actor.getName()); - }).then(function () { - chan.logger.log("[mod] " + actor.getName() + " namebanned " + name); - if (chan.modules.chat) { - chan.modules.chat.sendModMessage(actor.getName() + " namebanned " + name, - chan.modules.permissions.permissions.ban); - } - return true; - }).then(function () { - self.kickBanTarget(name, null); - setImmediate(function () { - cb(null); - }); - }).catch(error).done(); + if (isBanned) { + throw new Error(name + " is already banned"); + } + + await dbAddBan(chan.name, "*", name, reason, actor.getName()); + this.checkChannelAlive(); + + chan.logger.log("[mod] " + actor.getName() + " namebanned " + name); + + if (chan.modules.chat) { + chan.modules.chat.sendModMessage( + actor.getName() + " namebanned " + name, + chan.modules.permissions.permissions.ban + ); + } + + this.kickBanTarget(name, null); }; -KickBanModule.prototype.banIP = function (actor, ip, name, reason, cb) { - var self = this; +KickBanModule.prototype.banIP = async function banIP(actor, ip, name, reason) { reason = reason.substring(0, 255); var masked = util.cloakIP(ip); var chan = this.channel; - var error = function (what) { - actor.socket.emit("errorMsg", { msg: what }); - cb(what); - }; if (!chan.modules.permissions.canBan(actor)) { - return error("You do not have ban permissions on this channel"); + throw new Error("You do not have ban permissions on this channel"); } - Q.nfcall(Account.rankForIP, ip, { channel: chan.name }).then(function (rank) { - if (rank >= actor.account.effectiveRank) { - throw "You don't have permission to ban IP " + masked; - } + const rank = await Account.rankForIP(ip, chan.name); + this.checkChannelAlive(); - return Q.nfcall(db.channels.isIPBanned, chan.name, ip); - }).then(function (banned) { - if (banned) { - throw masked + " is already banned"; - } + if (rank >= actor.account.effectiveRank) { + // TODO: this message should be made friendlier + throw new Error("You don't have permission to ban IP " + masked); + } - if (chan.dead) { throw null; } + const isBanned = await dbIsIPBanned(chan.name, ip); + this.checkChannelAlive(); - return Q.nfcall(db.channels.ban, chan.name, ip, name, reason, actor.getName()); - }).then(function () { - var cloaked = util.cloakIP(ip); - chan.logger.log("[mod] " + actor.getName() + " banned " + cloaked + " (" + name + ")"); - if (chan.modules.chat) { - chan.modules.chat.sendModMessage(actor.getName() + " banned " + - cloaked + " (" + name + ")", - chan.modules.permissions.permissions.ban); - } - }).then(function () { - self.kickBanTarget(name, ip); - setImmediate(function () { - cb(null); - }); - }).catch(error).done(); + if (isBanned) { + // TODO: this message should be made friendlier + throw new Error(masked + " is already banned"); + } + + await dbAddBan(chan.name, ip, name, reason, actor.getName()); + this.checkChannelAlive(); + + var cloaked = util.cloakIP(ip); + chan.logger.log( + "[mod] " + actor.getName() + " banned " + cloaked + + " (" + name + ")" + ); + + if (chan.modules.chat) { + chan.modules.chat.sendModMessage( + actor.getName() + " banned " + cloaked + " (" + name + ")", + chan.modules.permissions.permissions.ban + ); + } + + this.kickBanTarget(name, ip); }; -KickBanModule.prototype.banAll = function (actor, name, range, reason, cb) { - var self = this; +KickBanModule.prototype.banAll = async function banAll( + actor, + name, + range, + reason +) { reason = reason.substring(0, 255); - var chan = self.channel; - var error = function (what) { - cb(what); - }; + var chan = this.channel; if (!chan.modules.permissions.canBan(actor)) { - return error("You do not have ban permissions on this channel"); + throw new Error("You do not have ban permissions on this channel"); } - self.banName(actor, name, reason, function (err) { - if (err && err.indexOf("is already banned") === -1) { - cb(err); - } else { - db.getIPs(name, function (err, ips) { - if (err) { - return error(err); - } + const ips = await dbGetIPs(name); + this.checkChannelAlive(); - var seenIPs = {}; - var all = ips.map(function (ip) { - if (range === "range") { - ip = util.getIPRange(ip); - } else if (range === "wrange") { - ip = util.getWideIPRange(ip); - } - - if (seenIPs.hasOwnProperty(ip)) { - return; - } else { - seenIPs[ip] = true; - } - - return Q.nfcall(self.banIP.bind(self), actor, ip, name, reason); - }); - - Q.all(all).then(function () { - setImmediate(cb); - }).catch(error).done(); - }); + const toBan = new Set(); + for (let ip of ips) { + switch (range) { + case "range": + toBan.add(util.getIPRange(ip)); + break; + case "wrange": + toBan.add(util.getWideIPRange(ip)); + break; + default: + toBan.add(ip); + break; } - }); + } + + const promises = Array.from(toBan).map(ip => + this.banIP(actor, ip, name, reason) + ); + + if (!await dbIsNameBanned(chan.name, name)) { + promises.push(this.banName(actor, name, reason)); + } + + await Promise.all(promises); + this.checkChannelAlive(); }; KickBanModule.prototype.kickBanTarget = function (name, ip) { diff --git a/src/channel/ranks.js b/src/channel/ranks.js index 9d847a32..bdfeb9f9 100644 --- a/src/channel/ranks.js +++ b/src/channel/ranks.js @@ -2,6 +2,9 @@ var ChannelModule = require("./module"); var Flags = require("../flags"); var Account = require("../account"); var db = require("../database"); +import Promise from 'bluebird'; + +const dbSetChannelRank = Promise.promisify(db.channels.setRank); const TYPE_SET_CHANNEL_RANK = { name: "string", @@ -177,17 +180,20 @@ RankModule.prototype.handleRankChange = function (user, data) { RankModule.prototype.updateDatabase = function (data, cb) { var chan = this.channel; - Account.rankForName(data.name, { channel: this.channel.name }, function (err, rank) { - if (err) { - return cb(err); - } - + Account.rankForName(data.name, this.channel.name).then(rank => { if (rank >= data.userrank && !(rank === 4 && data.userrank === 4)) { - cb("You can't promote or demote someone with equal or higher rank than you."); + throw new Error( + "You can't promote or demote someone" + + " with equal or higher rank than you." + ); return; } - db.channels.setRank(chan.name, data.name, data.rank, cb); + return dbSetChannelRank(chan.name, data.name, data.rank); + }).then(() => { + process.nextTick(cb); + }).catch(error => { + process.nextTick(cb, error.message || error); }); }; diff --git a/src/database.js b/src/database.js index 82bfdb6c..ebb1cd22 100644 --- a/src/database.js +++ b/src/database.js @@ -71,6 +71,8 @@ class Database { } module.exports.Database = Database; +module.exports.users = require("./database/accounts"); +module.exports.channels = require("./database/channels"); module.exports.init = function (newDB) { if (newDB) { @@ -85,9 +87,6 @@ module.exports.init = function (newDB) { }).then(() => { process.nextTick(legacySetup); }); - - module.exports.users = require("./database/accounts"); - module.exports.channels = require("./database/channels"); }; module.exports.getDB = function getDB() { diff --git a/src/database/channels.js b/src/database/channels.js index dead5225..67b77df8 100644 --- a/src/database/channels.js +++ b/src/database/channels.js @@ -328,10 +328,10 @@ module.exports = { var replace = "(" + names.map(function () { return "?"; }).join(",") + ")"; /* Last substitution is the channel to select ranks for */ - names.push(chan); + const sub = names.concat([chan]); db.query("SELECT * FROM `channel_ranks` WHERE name IN " + - replace + " AND channel=?", names, + replace + " AND channel=?", sub, function (err, rows) { if (err) { callback(err, []); diff --git a/src/database/update.js b/src/database/update.js index 7fc387f5..df6294d8 100644 --- a/src/database/update.js +++ b/src/database/update.js @@ -1,5 +1,4 @@ var db = require("../database"); -var Q = require("q"); import Promise from 'bluebird'; const LOGGER = require('@calzoneman/jsli')('database/update'); @@ -41,15 +40,9 @@ module.exports.checkVersion = function () { }; function update(version, cb) { - if (version < 4) { + if (version < 7) { LOGGER.error('Cannot auto-upgrade: db_version 4 is too old!'); process.exit(1); - } else if (version < 5) { - fixUtf8mb4(cb); - } else if (version < 6) { - fixCustomEmbeds(cb); - } else if (version < 7) { - fixCustomEmbedsInUserPlaylists(cb); } else if (version < 8) { addUsernameDedupeColumn(cb); } else if (version < 9) { @@ -61,110 +54,6 @@ function update(version, cb) { } } -function fixUtf8mb4(cb) { - var queries = [ - "ALTER TABLE `users` MODIFY `profile` TEXT CHARACTER SET utf8mb4 NOT NULL", - "ALTER TABLE `global_bans` MODIFY `reason` VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL", - "ALTER TABLE `channel_libraries` MODIFY `title` VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL", - "ALTER TABLE `channel_bans` MODIFY `reason` VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL" - ]; - - Q.allSettled(queries.map(function (query) { - return Q.nfcall(db.query, query); - })).then(function () { - LOGGER.info("Fixed utf8mb4"); - cb(); - }).catch(function (e) { - LOGGER.error("Failed to fix utf8mb4: " + e); - }); -}; - -function fixCustomEmbeds(cb) { - var CustomEmbedFilter = require("../customembed").filter; - - Q.nfcall(db.query, "SELECT * FROM `channel_libraries` WHERE type='cu'") - .then(function (rows) { - var all = []; - rows.forEach(function (row) { - if (row.id.indexOf("cu:") === 0) return; - - all.push(Q.nfcall(db.query, "DELETE FROM `channel_libraries` WHERE `id`=? AND `channel`=?", - [row.id, row.channel])); - - try { - var media = CustomEmbedFilter(row.id); - - all.push(Q.nfcall(db.channels.addToLibrary, row.channel, media)); - } catch(e) { - console.error("WARNING: Unable to convert " + row.id); - } - }); - - Q.allSettled(all).then(function () { - LOGGER.info("Converted custom embeds."); - cb(); - }); - }); -} - -function fixCustomEmbedsInUserPlaylists(cb) { - var CustomEmbedFilter = require("../customembed").filter; - Q.nfcall(db.query, "SELECT * FROM `user_playlists` WHERE `contents` LIKE '%\"type\":\"cu\"%'") - .then(function (rows) { - var all = []; - rows.forEach(function (row) { - var data; - try { - data = JSON.parse(row.contents); - } catch (e) { - return; - } - - var updated = []; - var item; - while ((item = data.shift()) !== undefined) { - if (item.type !== "cu") { - updated.push(item); - continue; - } - - if (/^cu:/.test(item.id)) { - updated.push(item); - continue; - } - - var media; - try { - media = CustomEmbedFilter(item.id); - } catch (e) { - LOGGER.info("WARNING: Unable to convert " + item.id); - continue; - } - - updated.push({ - id: media.id, - title: item.title, - seconds: media.seconds, - type: media.type, - meta: { - embed: media.meta.embed - } - }); - - all.push(Q.nfcall(db.query, "UPDATE `user_playlists` SET `contents`=?, `count`=? WHERE `user`=? AND `name`=?", - [JSON.stringify(updated), updated.length, row.user, row.name])); - } - }); - - Q.allSettled(all).then(function () { - LOGGER.info('Fixed custom embeds in user_playlists'); - cb(); - }); - }).catch(function (err) { - LOGGER.error(err.stack); - }); -} - function addUsernameDedupeColumn(cb) { LOGGER.info("Adding name_dedupe column on the users table"); db.query("ALTER TABLE users ADD COLUMN name_dedupe VARCHAR(20) UNIQUE DEFAULT NULL", (error) => {