deps: remove "q" (#731)

Insert Star Trek joke here.

Also did significant refactoring of the surrounding logic for the things
that depended on Q.
This commit is contained in:
Calvin Montgomery 2018-02-24 19:47:50 -08:00 committed by GitHub
parent d5f5c91b05
commit 79556d9365
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 738 additions and 296 deletions

View file

@ -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',
{}
);
});
});
});
});

View file

@ -2,7 +2,7 @@
"author": "Calvin Montgomery", "author": "Calvin Montgomery",
"name": "CyTube", "name": "CyTube",
"description": "Online media synchronizer and chat", "description": "Online media synchronizer and chat",
"version": "3.54.0", "version": "3.55.0",
"repository": { "repository": {
"url": "http://github.com/calzoneman/sync" "url": "http://github.com/calzoneman/sync"
}, },
@ -33,7 +33,6 @@
"prom-client": "^10.0.2", "prom-client": "^10.0.2",
"proxy-addr": "^2.0.2", "proxy-addr": "^2.0.2",
"pug": "^2.0.0-beta3", "pug": "^2.0.0-beta3",
"q": "^1.4.1",
"redis": "^2.4.2", "redis": "^2.4.2",
"sanitize-html": "^1.14.1", "sanitize-html": "^1.14.1",
"serve-static": "^1.13.2", "serve-static": "^1.13.2",

View file

@ -1,5 +1,11 @@
var db = require("./database"); import db from './database';
var Q = require("q"); 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: '' }); const DEFAULT_PROFILE = Object.freeze({ image: '', text: '' });
@ -33,71 +39,21 @@ class Account {
module.exports.Account = Account; module.exports.Account = Account;
module.exports.rankForName = function (name, opts, cb) { module.exports.rankForName = async function rankForNameAsync(name, channel) {
if (!cb) { const [globalRank, channelRank] = await Promise.all([
cb = opts; dbGetGlobalRank(name),
opts = {}; dbGetChannelRank(channel, name)
} ]);
var rank = 0; return Math.max(globalRank, channelRank);
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();
}; };
module.exports.rankForIP = function (ip, opts, cb) { module.exports.rankForIP = async function rankForIP(ip, channel) {
if (!cb) { const aliases = await dbGetAliases(ip);
cb = opts; const [globalRanks, channelRanks] = await Promise.all([
opts = {}; dbMultiGetGlobalRank(aliases),
} dbMultiGetChannelRank(channel, aliases)
]);
var globalRank, rank, names; return Math.max.apply(Math, globalRanks.concat(channelRanks));
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();
}
}; };

View file

@ -3,7 +3,12 @@ var db = require("../database");
var Flags = require("../flags"); var Flags = require("../flags");
var util = require("../utilities"); var util = require("../utilities");
var Account = require("../account"); 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 = { const TYPE_UNBAN = {
id: "number", id: "number",
@ -234,7 +239,11 @@ KickBanModule.prototype.handleCmdBan = function (user, msg, meta) {
const chan = this.channel; const chan = this.channel;
chan.refCounter.ref("KickBanModule::handleCmdBan"); 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"); chan.refCounter.unref("KickBanModule::handleCmdBan");
}); });
}; };
@ -261,23 +270,29 @@ KickBanModule.prototype.handleCmdIPBan = function (user, msg, meta) {
const chan = this.channel; const chan = this.channel;
chan.refCounter.ref("KickBanModule::handleCmdIPBan"); 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"); chan.refCounter.unref("KickBanModule::handleCmdIPBan");
}); });
}; };
KickBanModule.prototype.banName = function (actor, name, reason, cb) { KickBanModule.prototype.checkChannelAlive = function checkChannelAlive() {
var self = this; 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); reason = reason.substring(0, 255);
var chan = this.channel; var chan = this.channel;
var error = function (what) {
actor.socket.emit("errorMsg", { msg: what });
cb(what);
};
if (!chan.modules.permissions.canBan(actor)) { 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(); name = name.toLowerCase();
@ -285,129 +300,126 @@ KickBanModule.prototype.banName = function (actor, name, reason, cb) {
actor.socket.emit("costanza", { actor.socket.emit("costanza", {
msg: "You can't ban yourself" 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 }) const rank = await Account.rankForName(name, chan.name);
.then(function (rank) { this.checkChannelAlive();
if (rank >= actor.account.effectiveRank) {
throw "You don't have permission to ban " + name;
}
return Q.nfcall(db.channels.isNameBanned, chan.name, name); if (rank >= actor.account.effectiveRank) {
}).then(function (banned) { throw new Error("You don't have permission to ban " + name);
if (banned) { }
throw name + " is already banned";
}
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()); if (isBanned) {
}).then(function () { throw new Error(name + " is already banned");
chan.logger.log("[mod] " + actor.getName() + " namebanned " + name); }
if (chan.modules.chat) {
chan.modules.chat.sendModMessage(actor.getName() + " namebanned " + name, await dbAddBan(chan.name, "*", name, reason, actor.getName());
chan.modules.permissions.permissions.ban); this.checkChannelAlive();
}
return true; chan.logger.log("[mod] " + actor.getName() + " namebanned " + name);
}).then(function () {
self.kickBanTarget(name, null); if (chan.modules.chat) {
setImmediate(function () { chan.modules.chat.sendModMessage(
cb(null); actor.getName() + " namebanned " + name,
}); chan.modules.permissions.permissions.ban
}).catch(error).done(); );
}
this.kickBanTarget(name, null);
}; };
KickBanModule.prototype.banIP = function (actor, ip, name, reason, cb) { KickBanModule.prototype.banIP = async function banIP(actor, ip, name, reason) {
var self = this;
reason = reason.substring(0, 255); reason = reason.substring(0, 255);
var masked = util.cloakIP(ip); var masked = util.cloakIP(ip);
var chan = this.channel; var chan = this.channel;
var error = function (what) {
actor.socket.emit("errorMsg", { msg: what });
cb(what);
};
if (!chan.modules.permissions.canBan(actor)) { 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) { const rank = await Account.rankForIP(ip, chan.name);
if (rank >= actor.account.effectiveRank) { this.checkChannelAlive();
throw "You don't have permission to ban IP " + masked;
}
return Q.nfcall(db.channels.isIPBanned, chan.name, ip); if (rank >= actor.account.effectiveRank) {
}).then(function (banned) { // TODO: this message should be made friendlier
if (banned) { throw new Error("You don't have permission to ban IP " + masked);
throw masked + " is already banned"; }
}
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()); if (isBanned) {
}).then(function () { // TODO: this message should be made friendlier
var cloaked = util.cloakIP(ip); throw new Error(masked + " is already banned");
chan.logger.log("[mod] " + actor.getName() + " banned " + cloaked + " (" + name + ")"); }
if (chan.modules.chat) {
chan.modules.chat.sendModMessage(actor.getName() + " banned " + await dbAddBan(chan.name, ip, name, reason, actor.getName());
cloaked + " (" + name + ")", this.checkChannelAlive();
chan.modules.permissions.permissions.ban);
} var cloaked = util.cloakIP(ip);
}).then(function () { chan.logger.log(
self.kickBanTarget(name, ip); "[mod] " + actor.getName() + " banned " + cloaked +
setImmediate(function () { " (" + name + ")"
cb(null); );
});
}).catch(error).done(); 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) { KickBanModule.prototype.banAll = async function banAll(
var self = this; actor,
name,
range,
reason
) {
reason = reason.substring(0, 255); reason = reason.substring(0, 255);
var chan = self.channel; var chan = this.channel;
var error = function (what) {
cb(what);
};
if (!chan.modules.permissions.canBan(actor)) { 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) { const ips = await dbGetIPs(name);
if (err && err.indexOf("is already banned") === -1) { this.checkChannelAlive();
cb(err);
} else {
db.getIPs(name, function (err, ips) {
if (err) {
return error(err);
}
var seenIPs = {}; const toBan = new Set();
var all = ips.map(function (ip) { for (let ip of ips) {
if (range === "range") { switch (range) {
ip = util.getIPRange(ip); case "range":
} else if (range === "wrange") { toBan.add(util.getIPRange(ip));
ip = util.getWideIPRange(ip); break;
} case "wrange":
toBan.add(util.getWideIPRange(ip));
if (seenIPs.hasOwnProperty(ip)) { break;
return; default:
} else { toBan.add(ip);
seenIPs[ip] = true; break;
}
return Q.nfcall(self.banIP.bind(self), actor, ip, name, reason);
});
Q.all(all).then(function () {
setImmediate(cb);
}).catch(error).done();
});
} }
}); }
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) { KickBanModule.prototype.kickBanTarget = function (name, ip) {

View file

@ -2,6 +2,9 @@ var ChannelModule = require("./module");
var Flags = require("../flags"); var Flags = require("../flags");
var Account = require("../account"); var Account = require("../account");
var db = require("../database"); var db = require("../database");
import Promise from 'bluebird';
const dbSetChannelRank = Promise.promisify(db.channels.setRank);
const TYPE_SET_CHANNEL_RANK = { const TYPE_SET_CHANNEL_RANK = {
name: "string", name: "string",
@ -177,17 +180,20 @@ RankModule.prototype.handleRankChange = function (user, data) {
RankModule.prototype.updateDatabase = function (data, cb) { RankModule.prototype.updateDatabase = function (data, cb) {
var chan = this.channel; var chan = this.channel;
Account.rankForName(data.name, { channel: this.channel.name }, function (err, rank) { Account.rankForName(data.name, this.channel.name).then(rank => {
if (err) {
return cb(err);
}
if (rank >= data.userrank && !(rank === 4 && data.userrank === 4)) { 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; 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);
}); });
}; };

View file

@ -71,6 +71,8 @@ class Database {
} }
module.exports.Database = Database; module.exports.Database = Database;
module.exports.users = require("./database/accounts");
module.exports.channels = require("./database/channels");
module.exports.init = function (newDB) { module.exports.init = function (newDB) {
if (newDB) { if (newDB) {
@ -85,9 +87,6 @@ module.exports.init = function (newDB) {
}).then(() => { }).then(() => {
process.nextTick(legacySetup); process.nextTick(legacySetup);
}); });
module.exports.users = require("./database/accounts");
module.exports.channels = require("./database/channels");
}; };
module.exports.getDB = function getDB() { module.exports.getDB = function getDB() {

View file

@ -328,10 +328,10 @@ module.exports = {
var replace = "(" + names.map(function () { return "?"; }).join(",") + ")"; var replace = "(" + names.map(function () { return "?"; }).join(",") + ")";
/* Last substitution is the channel to select ranks for */ /* 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 " + db.query("SELECT * FROM `channel_ranks` WHERE name IN " +
replace + " AND channel=?", names, replace + " AND channel=?", sub,
function (err, rows) { function (err, rows) {
if (err) { if (err) {
callback(err, []); callback(err, []);

View file

@ -1,5 +1,4 @@
var db = require("../database"); var db = require("../database");
var Q = require("q");
import Promise from 'bluebird'; import Promise from 'bluebird';
const LOGGER = require('@calzoneman/jsli')('database/update'); const LOGGER = require('@calzoneman/jsli')('database/update');
@ -41,15 +40,9 @@ module.exports.checkVersion = function () {
}; };
function update(version, cb) { function update(version, cb) {
if (version < 4) { if (version < 7) {
LOGGER.error('Cannot auto-upgrade: db_version 4 is too old!'); LOGGER.error('Cannot auto-upgrade: db_version 4 is too old!');
process.exit(1); 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) { } else if (version < 8) {
addUsernameDedupeColumn(cb); addUsernameDedupeColumn(cb);
} else if (version < 9) { } 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) { function addUsernameDedupeColumn(cb) {
LOGGER.info("Adding name_dedupe column on the users table"); 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) => { db.query("ALTER TABLE users ADD COLUMN name_dedupe VARCHAR(20) UNIQUE DEFAULT NULL", (error) => {