package: build with babel for ES2015 support
* Rename lib/ -> src/ * Add `postinstall` npm target for compiling src files to lib * Add `build-watch` npm target for development with babel --watch * Add `lib/` to .gitignore * Add `source-map-support` module for babel-generated sourcemaps
This commit is contained in:
parent
d042619b21
commit
0109a87e55
55 changed files with 9 additions and 3 deletions
564
src/database/accounts.js
Normal file
564
src/database/accounts.js
Normal file
|
|
@ -0,0 +1,564 @@
|
|||
var $util = require("../utilities");
|
||||
var bcrypt = require("bcrypt");
|
||||
var db = require("../database");
|
||||
var Config = require("../config");
|
||||
var Logger = require("../logger");
|
||||
|
||||
var registrationLock = {};
|
||||
var blackHole = function () { };
|
||||
|
||||
/**
|
||||
* Replaces look-alike characters with "_" (single character wildcard) for
|
||||
* use in LIKE queries. This prevents guests from taking names that look
|
||||
* visually identical to existing names in certain fonts.
|
||||
*/
|
||||
function wildcardSimilarChars(name) {
|
||||
return name.replace(/_/g, "\\_").replace(/[Il1oO0]/g, "_");
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function () {
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a username is taken
|
||||
*/
|
||||
isUsernameTaken: function (name, callback) {
|
||||
db.query("SELECT name FROM `users` WHERE name LIKE ? ESCAPE '\\\\'",
|
||||
[wildcardSimilarChars(name)],
|
||||
function (err, rows) {
|
||||
if (err) {
|
||||
callback(err, true);
|
||||
return;
|
||||
}
|
||||
callback(null, rows.length > 0);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Search for a user by name
|
||||
*/
|
||||
search: function (name, fields, callback) {
|
||||
/* This bit allows it to accept varargs
|
||||
Function can be called as (name, callback) or
|
||||
(name, fields, callback)
|
||||
*/
|
||||
if (typeof callback !== "function") {
|
||||
if (typeof fields === "function") {
|
||||
callback = fields;
|
||||
fields = ["name"];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't allow search to return password hashes
|
||||
if (fields.indexOf("password") !== -1) {
|
||||
fields.splice(fields.indexOf("password"));
|
||||
}
|
||||
|
||||
db.query("SELECT " + fields.join(",") + " FROM `users` WHERE name LIKE ?",
|
||||
["%"+name+"%"],
|
||||
function (err, rows) {
|
||||
if (err) {
|
||||
callback(err, true);
|
||||
return;
|
||||
}
|
||||
callback(null, rows);
|
||||
});
|
||||
},
|
||||
|
||||
getUser: function (name, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT * FROM `users` WHERE name = ?", [name], function (err, rows) {
|
||||
if (err) {
|
||||
callback(err, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rows.length !== 1) {
|
||||
return callback("User does not exist");
|
||||
}
|
||||
|
||||
callback(null, rows[0]);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers a new user account
|
||||
*/
|
||||
register: function (name, pw, email, ip, callback) {
|
||||
// Start off with a boatload of error checking
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
if (typeof name !== "string" || typeof pw !== "string") {
|
||||
callback("You must provide a nonempty username and password", null);
|
||||
return;
|
||||
}
|
||||
var lname = name.toLowerCase();
|
||||
|
||||
if (registrationLock[lname]) {
|
||||
callback("There is already a registration in progress for "+name,
|
||||
null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$util.isValidUserName(name)) {
|
||||
callback("Invalid username. Usernames may consist of 1-20 " +
|
||||
"characters a-z, A-Z, 0-9, -, _, and accented letters.",
|
||||
null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof email !== "string") {
|
||||
email = "";
|
||||
}
|
||||
|
||||
if (typeof ip !== "string") {
|
||||
ip = "";
|
||||
}
|
||||
|
||||
// From this point forward, actual registration happens
|
||||
// registrationLock prevents concurrent database activity
|
||||
// on the same user account
|
||||
registrationLock[lname] = true;
|
||||
|
||||
this.getAccounts(ip, function (err, accts) {
|
||||
if (err) {
|
||||
delete registrationLock[lname];
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (accts.length >= Config.get("max-accounts-per-ip")) {
|
||||
delete registrationLock[lname];
|
||||
callback("You have registered too many accounts from this "+
|
||||
"computer.", null);
|
||||
return;
|
||||
}
|
||||
|
||||
module.exports.isUsernameTaken(name, function (err, taken) {
|
||||
if (err) {
|
||||
delete registrationLock[lname];
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (taken) {
|
||||
delete registrationLock[lname];
|
||||
callback("Username is already registered", null);
|
||||
return;
|
||||
}
|
||||
|
||||
bcrypt.hash(pw, 10, function (err, hash) {
|
||||
if (err) {
|
||||
delete registrationLock[lname];
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("INSERT INTO `users` " +
|
||||
"(`name`, `password`, `global_rank`, `email`, `profile`, `ip`, `time`)" +
|
||||
" VALUES " +
|
||||
"(?, ?, ?, ?, '', ?, ?)",
|
||||
[name, hash, 1, email, ip, Date.now()],
|
||||
function (err, res) {
|
||||
delete registrationLock[lname];
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
} else {
|
||||
callback(null, {
|
||||
name: name,
|
||||
hash: hash
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Verify a username/password pair
|
||||
*/
|
||||
verifyLogin: function (name, pw, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof name !== "string" || typeof pw !== "string") {
|
||||
callback("Invalid username/password combination", null);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Passwords are capped at 100 characters to prevent a potential
|
||||
denial of service vector through causing the server to hash
|
||||
ridiculously long strings.
|
||||
*/
|
||||
pw = pw.substring(0, 100);
|
||||
|
||||
/* Note: rather than hash the password and then query based on name and
|
||||
password, I query by name, then use bcrypt.compare() to check that
|
||||
the hashes match.
|
||||
*/
|
||||
|
||||
db.query("SELECT name,password,global_rank FROM `users` WHERE name=?",
|
||||
[name],
|
||||
function (err, rows) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rows.length === 0) {
|
||||
callback("User does not exist", null);
|
||||
return;
|
||||
}
|
||||
|
||||
bcrypt.compare(pw, rows[0].password, function (err, match) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
} else if (!match) {
|
||||
callback("Invalid username/password combination", null);
|
||||
} else {
|
||||
callback(null, rows[0]);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Verify an auth string of the form name:hash
|
||||
*/
|
||||
verifyAuth: function (auth, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof auth !== "string") {
|
||||
callback("Invalid auth string", null);
|
||||
return;
|
||||
}
|
||||
|
||||
var split = auth.split(":");
|
||||
if (split.length !== 2) {
|
||||
callback("Invalid auth string", null);
|
||||
return;
|
||||
}
|
||||
|
||||
var name = split[0];
|
||||
var hash = split[1];
|
||||
db.query("SELECT name,password,global_rank FROM `users` WHERE " +
|
||||
"name=? and password=?", [name, hash],
|
||||
function (err, rows) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rows.length === 0) {
|
||||
callback("Auth string does not match an existing user", null);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, {
|
||||
name: rows[0].name,
|
||||
hash: rows[0].password,
|
||||
global_rank: rows[0].global_rank
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Change a user's password
|
||||
*/
|
||||
setPassword: function (name, pw, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
if (typeof name !== "string" || typeof pw !== "string") {
|
||||
callback("Invalid username/password combination", null);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Passwords are capped at 100 characters to prevent a potential
|
||||
denial of service vector through causing the server to hash
|
||||
ridiculously long strings.
|
||||
*/
|
||||
pw = pw.substring(0, 100);
|
||||
|
||||
bcrypt.hash(pw, 10, function (err, hash) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("UPDATE `users` SET password=? WHERE name=?",
|
||||
[hash, name],
|
||||
function (err, result) {
|
||||
callback(err, err ? null : true);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Lookup a user's global rank
|
||||
*/
|
||||
getGlobalRank: function (name, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof name !== "string") {
|
||||
callback("Invalid username", null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
callback(null, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT global_rank FROM `users` WHERE name=?", [name],
|
||||
function (err, rows) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
} else if (rows.length === 0) {
|
||||
callback(null, 0);
|
||||
} else {
|
||||
callback(null, rows[0].global_rank);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates a user's global rank
|
||||
*/
|
||||
setGlobalRank: function (name, rank, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
if (typeof name !== "string") {
|
||||
callback("Invalid username", null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof rank !== "number") {
|
||||
callback("Invalid rank", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("UPDATE `users` SET global_rank=? WHERE name=?", [rank, name],
|
||||
function (err, result) {
|
||||
callback(err, err ? null : true);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Lookup multiple users' global rank in one query
|
||||
*/
|
||||
getGlobalRanks: function (names, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(names instanceof Array)) {
|
||||
callback("Expected array of names, got " + typeof names, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (names.length === 0) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
var list = "(" + names.map(function () { return "?";}).join(",") + ")";
|
||||
|
||||
db.query("SELECT global_rank FROM `users` WHERE name IN " + list, names,
|
||||
function (err, rows) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
} else if (rows.length === 0) {
|
||||
callback(null, []);
|
||||
} else {
|
||||
callback(null, rows.map(function (x) { return x.global_rank; }));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Lookup a user's email
|
||||
*/
|
||||
getEmail: function (name, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof name !== "string") {
|
||||
callback("Invalid username", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT email FROM `users` WHERE name=?", [name],
|
||||
function (err, rows) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
} else if (rows.length === 0) {
|
||||
callback("User does not exist", null);
|
||||
} else {
|
||||
callback(null, rows[0].email);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates a user's email
|
||||
*/
|
||||
setEmail: function (name, email, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
if (typeof name !== "string") {
|
||||
callback("Invalid username", null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof email !== "string") {
|
||||
callback("Invalid email", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("UPDATE `users` SET email=? WHERE name=?", [email, name],
|
||||
function (err, result) {
|
||||
callback(err, err ? null : true);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Lookup a user's profile
|
||||
*/
|
||||
getProfile: function (name, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof name !== "string") {
|
||||
callback("Invalid username", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT profile FROM `users` WHERE name=?", [name],
|
||||
function (err, rows) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
} else if (rows.length === 0) {
|
||||
callback("User does not exist", null);
|
||||
} else {
|
||||
var userprof = {
|
||||
image: "",
|
||||
text: ""
|
||||
};
|
||||
|
||||
if (rows[0].profile === "") {
|
||||
callback(null, userprof);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var profile = JSON.parse(rows[0].profile);
|
||||
userprof.image = profile.image || "";
|
||||
userprof.text = profile.text || "";
|
||||
callback(null, userprof);
|
||||
} catch (e) {
|
||||
Logger.errlog.log("Corrupt profile: " + rows[0].profile +
|
||||
" (user: " + name + ")");
|
||||
callback(null, userprof);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates a user's profile
|
||||
*/
|
||||
setProfile: function (name, profile, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
if (typeof name !== "string") {
|
||||
callback("Invalid username", null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof profile !== "object") {
|
||||
callback("Invalid profile", null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cast to string to guarantee string type
|
||||
profile.image += "";
|
||||
profile.text += "";
|
||||
|
||||
// Limit size
|
||||
profile.image = profile.image.substring(0, 255);
|
||||
profile.text = profile.text.substring(0, 255);
|
||||
|
||||
// Stringify the literal to guarantee I only get the keys I want
|
||||
var profilejson = JSON.stringify({
|
||||
image: profile.image,
|
||||
text: profile.text
|
||||
});
|
||||
|
||||
db.query("UPDATE `users` SET profile=? WHERE name=?", [profilejson, name],
|
||||
function (err, result) {
|
||||
callback(err, err ? null : true);
|
||||
});
|
||||
},
|
||||
|
||||
generatePasswordReset: function (ip, name, email, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
callback("generatePasswordReset is not implemented", null);
|
||||
},
|
||||
|
||||
recoverPassword: function (hash, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
callback("recoverPassword is not implemented", null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve a list of channels owned by a user
|
||||
*/
|
||||
getChannels: function (name, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT * FROM `channels` WHERE owner=?", [name], callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves all names registered from a given IP
|
||||
*/
|
||||
getAccounts: function (ip, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT name,global_rank FROM `users` WHERE `ip`=?", [ip],
|
||||
callback);
|
||||
}
|
||||
};
|
||||
621
src/database/channels.js
Normal file
621
src/database/channels.js
Normal file
|
|
@ -0,0 +1,621 @@
|
|||
var db = require("../database");
|
||||
var valid = require("../utilities").isValidChannelName;
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var Logger = require("../logger");
|
||||
var tables = require("./tables");
|
||||
var Flags = require("../flags");
|
||||
var util = require("../utilities");
|
||||
|
||||
var blackHole = function () { };
|
||||
|
||||
function dropTable(name, callback) {
|
||||
db.query("DROP TABLE `" + name + "`", callback);
|
||||
}
|
||||
|
||||
function initTables(name, owner, callback) {
|
||||
if (!valid(name)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: function () {
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the given channel name is registered
|
||||
*/
|
||||
isChannelTaken: function (name, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!valid(name)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT name FROM `channels` WHERE name=?",
|
||||
[name],
|
||||
function (err, rows) {
|
||||
if (err) {
|
||||
callback(err, true);
|
||||
return;
|
||||
}
|
||||
callback(null, rows.length > 0);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Looks up a channel
|
||||
*/
|
||||
lookup: function (name, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!valid(name)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT * FROM `channels` WHERE name=?",
|
||||
[name],
|
||||
function (err, rows) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rows.length === 0) {
|
||||
callback("No such channel", null);
|
||||
} else {
|
||||
callback(null, rows[0]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Searches for a channel
|
||||
*/
|
||||
search: function (name, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT * FROM `channels` WHERE name LIKE ?",
|
||||
["%" + name + "%"],
|
||||
function (err, rows) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
callback(null, rows);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Searches for a channel by owner
|
||||
*/
|
||||
searchOwner: function (name, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT * FROM `channels` WHERE owner LIKE ?",
|
||||
["%" + name + "%"],
|
||||
function (err, rows) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
callback(null, rows);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Validates and registers a new channel
|
||||
*/
|
||||
register: function (name, owner, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
if (typeof name !== "string" || typeof owner !== "string") {
|
||||
callback("Name and owner are required for channel registration", null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!valid(name)) {
|
||||
callback("Invalid channel name. Channel names may consist of 1-30 " +
|
||||
"characters a-z, A-Z, 0-9, -, and _", null);
|
||||
return;
|
||||
}
|
||||
|
||||
module.exports.isChannelTaken(name, function (err, taken) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (taken) {
|
||||
callback("Channel name " + name + " is already taken", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("INSERT INTO `channels` " +
|
||||
"(`name`, `owner`, `time`) VALUES (?, ?, ?)",
|
||||
[name, owner, Date.now()],
|
||||
function (err, res) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.users.getGlobalRank(owner, function (err, rank) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
|
||||
rank = Math.max(rank, 5);
|
||||
|
||||
module.exports.setRank(name, owner, rank, function (err) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, { name: name });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregisters a channel
|
||||
*/
|
||||
drop: function (name, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
if (!valid(name)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("DELETE FROM `channels` WHERE name=?", [name], function (err) {
|
||||
|
||||
module.exports.deleteBans(name, function (err) {
|
||||
if (err) {
|
||||
Logger.errlog.log("Failed to delete bans for " + name + ": " + err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.deleteLibrary(name, function (err) {
|
||||
if (err) {
|
||||
Logger.errlog.log("Failed to delete library for " + name + ": " + err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.deleteAllRanks(name, function (err) {
|
||||
if (err) {
|
||||
Logger.errlog.log("Failed to delete ranks for " + name + ": " + err);
|
||||
}
|
||||
});
|
||||
|
||||
fs.unlink(path.join(__dirname, "..", "..", "chandump", name),
|
||||
function (err) {
|
||||
if (err && err.code !== "ENOENT") {
|
||||
Logger.errlog.log("Deleting chandump failed:");
|
||||
Logger.errlog.log(err);
|
||||
}
|
||||
});
|
||||
|
||||
callback(err, !Boolean(err));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Looks up channels registered by a given user
|
||||
*/
|
||||
listUserChannels: function (owner, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT * FROM `channels` WHERE owner=?", [owner],
|
||||
function (err, res) {
|
||||
if (err) {
|
||||
callback(err, []);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(err, res);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads the channel from the database
|
||||
*/
|
||||
load: function (chan, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
if (!valid(chan.name)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT * FROM `channels` WHERE name=?", chan.name, function (err, res) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.length === 0) {
|
||||
callback("Channel is not registered", null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (chan.dead) {
|
||||
callback("Channel is dead", null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Note that before this line, chan.name might have a different capitalization
|
||||
// than the database has stored. Update accordingly.
|
||||
chan.name = res[0].name;
|
||||
chan.uniqueName = chan.name.toLowerCase();
|
||||
chan.setFlag(Flags.C_REGISTERED);
|
||||
chan.logger.log("[init] Loaded channel from database");
|
||||
callback(null, true);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Looks up a user's rank
|
||||
*/
|
||||
getRank: function (chan, name, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!valid(chan)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT * FROM `channel_ranks` WHERE name=? AND channel=?",
|
||||
[name, chan],
|
||||
function (err, rows) {
|
||||
if (err) {
|
||||
callback(err, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rows.length === 0) {
|
||||
callback(null, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, rows[0].rank);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Looks up multiple users' ranks at once
|
||||
*/
|
||||
getRanks: function (chan, names, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!valid(chan)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
var replace = "(" + names.map(function () { return "?"; }).join(",") + ")";
|
||||
|
||||
/* Last substitution is the channel to select ranks for */
|
||||
names.push(chan);
|
||||
|
||||
db.query("SELECT * FROM `channel_ranks` WHERE name IN " +
|
||||
replace + " AND channel=?", names,
|
||||
function (err, rows) {
|
||||
if (err) {
|
||||
callback(err, []);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, rows.map(function (r) { return r.rank; }));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Query all user ranks at once
|
||||
*/
|
||||
allRanks: function (chan, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!valid(chan)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT * FROM `channel_ranks` WHERE channel=?", [chan], callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates a user's rank
|
||||
*/
|
||||
setRank: function (chan, name, rank, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
if (rank < 2) {
|
||||
module.exports.deleteRank(chan, name, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!valid(chan)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("INSERT INTO `channel_ranks` VALUES (?, ?, ?) " +
|
||||
"ON DUPLICATE KEY UPDATE rank=?",
|
||||
[name, rank, chan, rank, chan], callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes a user's rank entry
|
||||
*/
|
||||
deleteRank: function (chan, name, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
if (!valid(chan)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("DELETE FROM `channel_ranks` WHERE name=? AND channel=?", [name, chan],
|
||||
callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes all ranks for a channel
|
||||
*/
|
||||
deleteAllRanks: function (chan, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
if (!valid(chan)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("DELETE FROM `channel_ranks` WHERE channel=?", [chan], callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a media item to the library
|
||||
*/
|
||||
addToLibrary: function (chan, media, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
if (!valid(chan)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
var meta = JSON.stringify({
|
||||
bitrate: media.meta.bitrate,
|
||||
codec: media.meta.codec,
|
||||
scuri: media.meta.scuri,
|
||||
embed: media.meta.embed
|
||||
});
|
||||
|
||||
db.query("INSERT INTO `channel_libraries` " +
|
||||
"(id, title, seconds, type, meta, channel) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE id=id",
|
||||
[media.id, media.title, media.seconds, media.type, meta, chan], callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves a media item from the library by id
|
||||
*/
|
||||
getLibraryItem: function (chan, id, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!valid(chan)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT * FROM `channel_libraries` WHERE id=? AND channel=?", [id, chan],
|
||||
function (err, rows) {
|
||||
if (err) {
|
||||
callback(err, null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rows.length === 0) {
|
||||
callback("Item not in library", null);
|
||||
} else {
|
||||
callback(null, rows[0]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Search the library by title
|
||||
*/
|
||||
searchLibrary: function (chan, search, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT * FROM `channel_libraries` WHERE title LIKE ? AND channel=?",
|
||||
["%" + search + "%", chan], callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes a media item from the library
|
||||
*/
|
||||
deleteFromLibrary: function (chan, id, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
if (!valid(chan)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("DELETE FROM `channel_libraries` WHERE id=? AND channel=?",
|
||||
[id, chan], callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes all library entries for a channel
|
||||
*/
|
||||
deleteLibrary: function (chan, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
if (!valid(chan)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("DELETE FROM `channel_libraries` WHERE channel=?", [chan], callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a ban to the banlist
|
||||
*/
|
||||
ban: function (chan, ip, name, note, bannedby, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
if (!valid(chan)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("INSERT INTO `channel_bans` (ip, name, reason, bannedby, channel) " +
|
||||
"VALUES (?, ?, ?, ?, ?)",
|
||||
[ip, name, note, bannedby, chan], callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if an IP address or range is banned
|
||||
*/
|
||||
isIPBanned: function (chan, ip, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!valid(chan)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
var range = util.getIPRange(ip);
|
||||
var wrange = util.getWideIPRange(ip);
|
||||
|
||||
db.query("SELECT * FROM `channel_bans` WHERE ip IN (?, ?, ?) AND channel=?",
|
||||
[ip, range, wrange, chan],
|
||||
function (err, rows) {
|
||||
callback(err, err ? false : rows.length > 0);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a username is banned
|
||||
*/
|
||||
isNameBanned: function (chan, name, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!valid(chan)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT * FROM `channel_bans` WHERE name=? AND channel=?", [name, chan],
|
||||
function (err, rows) {
|
||||
callback(err, err ? false : rows.length > 0);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Lists all bans
|
||||
*/
|
||||
listBans: function (chan, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!valid(chan)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("SELECT * FROM `channel_bans` WHERE channel=?", [chan], callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes a ban from the banlist
|
||||
*/
|
||||
unbanId: function (chan, id, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
if (!valid(chan)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("DELETE FROM `channel_bans` WHERE id=? AND channel=?",
|
||||
[id, chan], callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes all bans from a channel
|
||||
*/
|
||||
deleteBans: function (chan, id, callback) {
|
||||
if (typeof callback !== "function") {
|
||||
callback = blackHole;
|
||||
}
|
||||
|
||||
if (!valid(chan)) {
|
||||
callback("Invalid channel name", null);
|
||||
return;
|
||||
}
|
||||
|
||||
db.query("DELETE FROM `channel_bans` WHERE channel=?", [chan], callback);
|
||||
}
|
||||
};
|
||||
141
src/database/tables.js
Normal file
141
src/database/tables.js
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
const TBL_USERS = "" +
|
||||
"CREATE TABLE IF NOT EXISTS `users` (" +
|
||||
"`id` INT NOT NULL AUTO_INCREMENT," +
|
||||
"`name` VARCHAR(20) NOT NULL," +
|
||||
"`password` VARCHAR(64) NOT NULL," +
|
||||
"`global_rank` INT NOT NULL," +
|
||||
"`email` VARCHAR(255) NOT NULL," +
|
||||
"`profile` TEXT CHARACTER SET utf8mb4 NOT NULL," +
|
||||
"`ip` VARCHAR(39) NOT NULL," +
|
||||
"`time` BIGINT NOT NULL," +
|
||||
"PRIMARY KEY(`id`)," +
|
||||
"UNIQUE(`name`)) " +
|
||||
"CHARACTER SET utf8";
|
||||
|
||||
const TBL_CHANNELS = "" +
|
||||
"CREATE TABLE IF NOT EXISTS `channels` (" +
|
||||
"`id` INT NOT NULL AUTO_INCREMENT," +
|
||||
"`name` VARCHAR(30) NOT NULL," +
|
||||
"`owner` VARCHAR(20) NOT NULL," +
|
||||
"`time` BIGINT NOT NULL," +
|
||||
"PRIMARY KEY (`id`), UNIQUE(`name`), INDEX(`owner`))" +
|
||||
"CHARACTER SET utf8";
|
||||
|
||||
const TBL_GLOBAL_BANS = "" +
|
||||
"CREATE TABLE IF NOT EXISTS `global_bans` (" +
|
||||
"`ip` VARCHAR(39) NOT NULL," +
|
||||
"`reason` VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL," +
|
||||
"PRIMARY KEY (`ip`)) " +
|
||||
"CHARACTER SET utf8";
|
||||
|
||||
const TBL_PASSWORD_RESET = "" +
|
||||
"CREATE TABLE IF NOT EXISTS `password_reset` (" +
|
||||
"`ip` VARCHAR(39) NOT NULL," +
|
||||
"`name` VARCHAR(20) NOT NULL," +
|
||||
"`hash` VARCHAR(64) NOT NULL," +
|
||||
"`email` VARCHAR(255) NOT NULL," +
|
||||
"`expire` BIGINT NOT NULL," +
|
||||
"PRIMARY KEY (`name`))" +
|
||||
"CHARACTER SET utf8";
|
||||
|
||||
const TBL_USER_PLAYLISTS = "" +
|
||||
"CREATE TABLE IF NOT EXISTS `user_playlists` (" +
|
||||
"`user` VARCHAR(20) NOT NULL," +
|
||||
"`name` VARCHAR(255) NOT NULL," +
|
||||
"`contents` MEDIUMTEXT NOT NULL," +
|
||||
"`count` INT NOT NULL," +
|
||||
"`duration` INT NOT NULL," +
|
||||
"PRIMARY KEY (`user`, `name`))" +
|
||||
"CHARACTER SET utf8";
|
||||
|
||||
const TBL_ALIASES = "" +
|
||||
"CREATE TABLE IF NOT EXISTS `aliases` (" +
|
||||
"`visit_id` INT NOT NULL AUTO_INCREMENT," +
|
||||
"`ip` VARCHAR(39) NOT NULL," +
|
||||
"`name` VARCHAR(20) NOT NULL," +
|
||||
"`time` BIGINT NOT NULL," +
|
||||
"PRIMARY KEY (`visit_id`), INDEX (`ip`)" +
|
||||
")";
|
||||
|
||||
const TBL_STATS = "" +
|
||||
"CREATE TABLE IF NOT EXISTS `stats` (" +
|
||||
"`time` BIGINT NOT NULL," +
|
||||
"`usercount` INT NOT NULL," +
|
||||
"`chancount` INT NOT NULL," +
|
||||
"`mem` INT NOT NULL," +
|
||||
"PRIMARY KEY (`time`))" +
|
||||
"CHARACTER SET utf8";
|
||||
|
||||
const TBL_META = "" +
|
||||
"CREATE TABLE IF NOT EXISTS `meta` (" +
|
||||
"`key` VARCHAR(255) NOT NULL," +
|
||||
"`value` TEXT NOT NULL," +
|
||||
"PRIMARY KEY (`key`))" +
|
||||
"CHARACTER SET utf8";
|
||||
|
||||
const TBL_LIBRARIES = "" +
|
||||
"CREATE TABLE IF NOT EXISTS `channel_libraries` (" +
|
||||
"`id` VARCHAR(255) NOT NULL," +
|
||||
"`title` VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL," +
|
||||
"`seconds` INT NOT NULL," +
|
||||
"`type` VARCHAR(2) NOT NULL," +
|
||||
"`meta` TEXT NOT NULL," +
|
||||
"`channel` VARCHAR(30) NOT NULL," +
|
||||
"PRIMARY KEY(`id`, `channel`), INDEX(`channel`, `title`)" +
|
||||
") CHARACTER SET utf8";
|
||||
|
||||
const TBL_RANKS = "" +
|
||||
"CREATE TABLE IF NOT EXISTS `channel_ranks` (" +
|
||||
"`name` VARCHAR(20) NOT NULL," +
|
||||
"`rank` INT NOT NULL," +
|
||||
"`channel` VARCHAR(30) NOT NULL," +
|
||||
"PRIMARY KEY(`name`, `channel`)" +
|
||||
") CHARACTER SET utf8";
|
||||
|
||||
const TBL_BANS = "" +
|
||||
"CREATE TABLE IF NOT EXISTS `channel_bans` (" +
|
||||
"`id` INT NOT NULL AUTO_INCREMENT," +
|
||||
"`ip` VARCHAR(39) NOT NULL," +
|
||||
"`name` VARCHAR(20) NOT NULL," +
|
||||
"`bannedby` VARCHAR(20) NOT NULL," +
|
||||
"`reason` VARCHAR(255) CHARACTER SET utf8mb4 NOT NULL," +
|
||||
"`channel` VARCHAR(30) NOT NULL," +
|
||||
"PRIMARY KEY (`id`, `channel`), UNIQUE (`name`, `ip`, `channel`), " +
|
||||
"INDEX (`ip`, `channel`), INDEX (`name`, `channel`)" +
|
||||
") CHARACTER SET utf8";
|
||||
|
||||
module.exports.init = function (queryfn, cb) {
|
||||
var tables = {
|
||||
users: TBL_USERS,
|
||||
channels: TBL_CHANNELS,
|
||||
channel_libraries: TBL_LIBRARIES,
|
||||
channel_ranks: TBL_RANKS,
|
||||
channel_bans: TBL_BANS,
|
||||
global_bans: TBL_GLOBAL_BANS,
|
||||
password_reset: TBL_PASSWORD_RESET,
|
||||
user_playlists: TBL_USER_PLAYLISTS,
|
||||
aliases: TBL_ALIASES,
|
||||
stats: TBL_STATS,
|
||||
meta: TBL_META
|
||||
};
|
||||
|
||||
var AsyncQueue = require("../asyncqueue");
|
||||
var aq = new AsyncQueue();
|
||||
var hasError = false;
|
||||
Object.keys(tables).forEach(function (tbl) {
|
||||
aq.queue(function (lock) {
|
||||
queryfn(tables[tbl], function (err) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
hasError = true;
|
||||
}
|
||||
lock.release();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
aq.queue(function (lock) {
|
||||
lock.release();
|
||||
cb(hasError);
|
||||
});
|
||||
};
|
||||
332
src/database/update.js
Normal file
332
src/database/update.js
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
var db = require("../database");
|
||||
var Logger = require("../logger");
|
||||
var Q = require("q");
|
||||
|
||||
const DB_VERSION = 7;
|
||||
var hasUpdates = [];
|
||||
|
||||
module.exports.checkVersion = function () {
|
||||
db.query("SELECT `key`,`value` FROM `meta` WHERE `key`=?", ["db_version"], function (err, rows) {
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rows.length === 0) {
|
||||
Logger.errlog.log("[Warning] db_version key missing from database. Setting " +
|
||||
"db_version=" + DB_VERSION);
|
||||
db.query("INSERT INTO `meta` (`key`, `value`) VALUES ('db_version', ?)",
|
||||
[DB_VERSION],
|
||||
function (err) {
|
||||
});
|
||||
} else {
|
||||
var v = parseInt(rows[0].value);
|
||||
if (v >= DB_VERSION) {
|
||||
return;
|
||||
}
|
||||
var next = function () {
|
||||
hasUpdates.push(v);
|
||||
Logger.syslog.log("Updated database to version " + v);
|
||||
if (v < DB_VERSION) {
|
||||
update(v++, next);
|
||||
} else {
|
||||
db.query("UPDATE `meta` SET `value`=? WHERE `key`='db_version'",
|
||||
[DB_VERSION]);
|
||||
}
|
||||
};
|
||||
update(v++, next);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function update(version, cb) {
|
||||
if (version < 3 && hasUpdates.indexOf(2) < 0) {
|
||||
addMetaColumnToLibraries(cb);
|
||||
} else if (version < 4) {
|
||||
Q.allSettled([
|
||||
Q.nfcall(mergeChannelLibraries),
|
||||
Q.nfcall(mergeChannelRanks),
|
||||
Q.nfcall(mergeChannelBans)
|
||||
]).done(function () {
|
||||
Logger.syslog.log("Merged channel tables. Please verify that everything " +
|
||||
"is working correctly, and then type '/delete_old_tables'" +
|
||||
" into the CyTube process to remove the unused tables.");
|
||||
cb();
|
||||
})
|
||||
} else if (version < 5) {
|
||||
fixUtf8mb4(cb);
|
||||
} else if (version < 6) {
|
||||
fixCustomEmbeds(cb);
|
||||
} else if (version < 7) {
|
||||
fixCustomEmbedsInUserPlaylists(cb);
|
||||
}
|
||||
}
|
||||
|
||||
function addMetaColumnToLibraries(cb) {
|
||||
Logger.syslog.log("[database] db version indicates channel libraries don't have " +
|
||||
"meta column. Updating...");
|
||||
Q.nfcall(db.query, "SHOW TABLES")
|
||||
.then(function (rows) {
|
||||
rows = rows.map(function (r) {
|
||||
return r[Object.keys(r)[0]];
|
||||
}).filter(function (r) {
|
||||
return r.match(/_library$/);
|
||||
});
|
||||
|
||||
var queue = [];
|
||||
rows.forEach(function (table) {
|
||||
queue.push(Q.nfcall(db.query, "ALTER TABLE `" + table + "` ADD meta TEXT")
|
||||
.then(function () {
|
||||
Logger.syslog.log("Added meta column to " + table);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return Q.all(queue);
|
||||
}).catch(function (err) {
|
||||
Logger.errlog.log("Adding meta column to library tables failed: " + err);
|
||||
}).done(cb);
|
||||
}
|
||||
|
||||
function mergeChannelLibraries(cb) {
|
||||
Q.nfcall(db.query, "SHOW TABLES")
|
||||
.then(function (rows) {
|
||||
rows = rows.map(function (r) {
|
||||
return r[Object.keys(r)[0]];
|
||||
}).filter(function (r) {
|
||||
return r.match(/chan_(.*)?_library$/);
|
||||
});
|
||||
|
||||
var queue = [];
|
||||
rows.forEach(function (table) {
|
||||
var name = table.match(/chan_(.*?)_library$/)[1];
|
||||
queue.push(Q.nfcall(db.query,
|
||||
"INSERT INTO `channel_libraries` SELECT id, title, seconds, type, meta, ?" +
|
||||
" AS channel FROM `" + table + "`", [name])
|
||||
.then(function () {
|
||||
Logger.syslog.log("Copied " + table + " to channel_libraries");
|
||||
}).catch(function (err) {
|
||||
Logger.errlog.log("Copying " + table + " to channel_libraries failed: " +
|
||||
err);
|
||||
if (err.stack) {
|
||||
Logger.errlog.log(err.stack);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return Q.all(queue);
|
||||
}).catch(function (err) {
|
||||
Logger.errlog.log("Copying libraries to channel_libraries failed: " + err);
|
||||
if (err.stack) {
|
||||
Logger.errlog.log(err.stack);
|
||||
}
|
||||
}).done(function () { cb(null); });
|
||||
}
|
||||
|
||||
function mergeChannelRanks(cb) {
|
||||
Q.nfcall(db.query, "SHOW TABLES")
|
||||
.then(function (rows) {
|
||||
rows = rows.map(function (r) {
|
||||
return r[Object.keys(r)[0]];
|
||||
}).filter(function (r) {
|
||||
return r.match(/chan_(.*?)_ranks$/);
|
||||
});
|
||||
|
||||
var queue = [];
|
||||
rows.forEach(function (table) {
|
||||
var name = table.match(/chan_(.*?)_ranks$/)[1];
|
||||
queue.push(Q.nfcall(db.query,
|
||||
"INSERT INTO `channel_ranks` SELECT name, rank, ?" +
|
||||
" AS channel FROM `" + table + "`", [name])
|
||||
.then(function () {
|
||||
Logger.syslog.log("Copied " + table + " to channel_ranks");
|
||||
}).catch(function (err) {
|
||||
Logger.errlog.log("Copying " + table + " to channel_ranks failed: " +
|
||||
err);
|
||||
if (err.stack) {
|
||||
Logger.errlog.log(err.stack);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return Q.all(queue);
|
||||
}).catch(function (err) {
|
||||
Logger.errlog.log("Copying ranks to channel_ranks failed: " + err);
|
||||
if (err.stack) {
|
||||
Logger.errlog.log(err.stack);
|
||||
}
|
||||
}).done(function () { cb(null); });
|
||||
}
|
||||
|
||||
function mergeChannelBans(cb) {
|
||||
Q.nfcall(db.query, "SHOW TABLES")
|
||||
.then(function (rows) {
|
||||
rows = rows.map(function (r) {
|
||||
return r[Object.keys(r)[0]];
|
||||
}).filter(function (r) {
|
||||
return r.match(/chan_(.*?)_bans$/);
|
||||
});
|
||||
|
||||
var queue = [];
|
||||
rows.forEach(function (table) {
|
||||
var name = table.match(/chan_(.*?)_bans$/)[1];
|
||||
queue.push(Q.nfcall(db.query,
|
||||
"INSERT INTO `channel_bans` SELECT id, ip, name, bannedby, reason, ?" +
|
||||
" AS channel FROM `" + table + "`", [name])
|
||||
.then(function () {
|
||||
Logger.syslog.log("Copied " + table + " to channel_bans");
|
||||
}).catch(function (err) {
|
||||
Logger.errlog.log("Copying " + table + " to channel_bans failed: " +
|
||||
err);
|
||||
if (err.stack) {
|
||||
Logger.errlog.log(err.stack);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return Q.all(queue);
|
||||
}).catch(function (err) {
|
||||
Logger.errlog.log("Copying ranks to channel_bans failed: " + err);
|
||||
if (err.stack) {
|
||||
Logger.errlog.log(err.stack);
|
||||
}
|
||||
}).done(function () { cb(null); });
|
||||
}
|
||||
|
||||
module.exports.deleteOldChannelTables = function (cb) {
|
||||
Q.nfcall(db.query, "SHOW TABLES")
|
||||
.then(function (rows) {
|
||||
rows = rows.map(function (r) {
|
||||
return r[Object.keys(r)[0]];
|
||||
}).filter(function (r) {
|
||||
return r.match(/chan_(.*?)_(library|ranks|bans)$/);
|
||||
});
|
||||
|
||||
var queue = [];
|
||||
rows.forEach(function (table) {
|
||||
queue.push(Q.nfcall(db.query, "DROP TABLE `" + table + "`")
|
||||
.then(function () {
|
||||
Logger.syslog.log("Deleted " + table);
|
||||
}).catch(function (err) {
|
||||
Logger.errlog.log("Deleting " + table + " failed: " + err);
|
||||
if (err.stack) {
|
||||
Logger.errlog.log(err.stack);
|
||||
}
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return Q.all(queue);
|
||||
}).catch(function (err) {
|
||||
Logger.errlog.log("Deleting old tables failed: " + err);
|
||||
if (err.stack) {
|
||||
Logger.errlog.log(err.stack);
|
||||
}
|
||||
}).done(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.syslog.log("Fixed utf8mb4");
|
||||
cb();
|
||||
}).catch(function (e) {
|
||||
Logger.errlog.log("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.syslog.log("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.syslog.log("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.syslog.log('Fixed custom embeds in user_playlists');
|
||||
cb();
|
||||
});
|
||||
}).catch(function (err) {
|
||||
Logger.errlog.log(err.stack);
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue