From 47af1d4892f2de0684ef9c9d0203cbeab828e783 Mon Sep 17 00:00:00 2001 From: calzoneman Date: Fri, 13 Dec 2013 20:39:21 -0600 Subject: [PATCH] More database refactoring --- lib/channel.js | 4 +- lib/database.js | 446 ++++++++++++++------------------------- lib/database/accounts.js | 11 + 3 files changed, 173 insertions(+), 288 deletions(-) diff --git a/lib/channel.js b/lib/channel.js index b0e9287b..fb2524cb 100644 --- a/lib/channel.js +++ b/lib/channel.js @@ -515,7 +515,7 @@ Channel.prototype.saveInitialRank = function (user, callback) { Channel.prototype.getIPRank = function (ip, callback) { var self = this; - self.server.db.listAliases(ip, function (err, names) { + db.getAliases(ip, function (err, names) { if (self.dead) return; db.users.getGlobalRanks(names, function (err, res) { @@ -1139,7 +1139,7 @@ Channel.prototype.broadcastNewUser = function(user) { user.socket.emit("channelNotRegistered"); user.socket.emit("rank", user.rank); } - db.listAliases(user.ip, function (err, aliases) { + db.getAliases(user.ip, function (err, aliases) { if (self.dead) return; diff --git a/lib/database.js b/lib/database.js index bb417721..2f051f5b 100644 --- a/lib/database.js +++ b/lib/database.js @@ -4,12 +4,10 @@ var bcrypt = require("bcrypt"); var $util = require("./utilities"); var Logger = require("./logger"); -var cfg = {}; var pool = null; var global_ipbans = {}; module.exports.init = function (cfg) { - cfg = cfg; pool = mysql.createPool({ host: cfg["mysql-server"], user: cfg["mysql-user"], @@ -25,7 +23,7 @@ module.exports.init = function (cfg) { return; } else { // Refresh global IP bans - module.exports.listGlobalIPBans(); + module.exports.listGlobalBans(); } }); @@ -36,26 +34,31 @@ module.exports.init = function (cfg) { module.exports.channels.init(); }; +/** + * Execute a database query + */ module.exports.query = function (query, sub, callback) { // 2nd argument is optional - if(typeof sub === "function") { + if (typeof sub === "function") { callback = sub; sub = false; } - if(typeof callback !== "function") + if (typeof callback !== "function") { callback = blackHole; + } pool.getConnection(function (err, conn) { - if(err) { + if (err) { Logger.errlog.log("! DB connection failed: " + err); callback("Database failure", null); } else { function cback(err, res) { - if(err) { + if (err) { Logger.errlog.log("! DB query failed: " + query); - if(sub) + if (sub) { Logger.errlog.log("Substitutions: " + sub); + } Logger.errlog.log(err); callback("Database failure", null); } else { @@ -64,7 +67,7 @@ module.exports.query = function (query, sub, callback) { conn.end(); } - if(sub) { + if (sub) { conn.query(query, sub, cback); } else { conn.query(query, cback); @@ -77,114 +80,75 @@ function blackHole() { } -module.exports.oldinit = function () { - var query; - // Create global bans table - query = ["CREATE TABLE IF NOT EXISTS `global_bans` (", - "`ip` VARCHAR(15) NOT NULL,", - "`note` VARCHAR(255) NOT NULL,", - "PRIMARY KEY (`ip`))", - "ENGINE = MyISAM ", - "CHARACTER SET utf8;"].join(""); +module.exports.initGlobalTables = function () { + var fail = function (table) { + return function (err) { + if (err) { + Logger.errlog.log("Failed to initialize " + table); + } + }; + }; + var query = module.exports.query; + query("CREATE TABLE IF NOT EXISTS `global_bans` (" + + "`ip` VARCHAR(39) NOT NULL," + + "`reason` VARCHAR(255) NOT NULL," + + "PRIMARY KEY (`ip`, `name`)) " + + "CHARACTER SET utf8", + fail("global_bans")); + + query("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", + fail("password_reset")); - module.exports.query(query, function (err, res) { - if(err) { - Logger.errlog.log("! Failed to create global ban table"); - } - }); + query("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", + fail("user_playlists")); - // Create password reset table - query = ["CREATE TABLE IF NOT EXISTS `password_reset` (", - "`ip` VARCHAR(15) NOT NULL,", - "`name` VARCHAR(20) NOT NULL,", - "`hash` VARCHAR(64) NOT NULL,", - "`email` VARCHAR(255) NOT NULL,", - "`expire` BIGINT NOT NULL,", - "PRIMARY KEY (`name`))", - "ENGINE = MyISAM ", - "CHARACTER SET utf8;"].join(""); + query("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`))", + fail("aliases")); - module.exports.query(query, function (err, res) { - if(err) { - Logger.errlog.log("! Failed to create password reset table"); - } - }); + // TODO actionlog? or no? - // Create user playlist table - query = ["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,", - "`time` INT NOT NULL,", - "PRIMARY KEY (`user`, `name`))", - "ENGINE = MyISAM ", - "CHARACTER SET utf8;"].join(""); - - module.exports.query(query, function (err, res) { - if(err) { - Logger.errlog.log("! Failed to create user playlist table"); - } - }); - - // Create user aliases table - query = ["CREATE TABLE IF NOT EXISTS `aliases` (", - "`visit_id` INT NOT NULL AUTO_INCREMENT,", - "`ip` VARCHAR(15) NOT NULL,", - "`name` VARCHAR(20) NOT NULL,", - "`time` BIGINT NOT NULL,", - "PRIMARY KEY (`visit_id`), INDEX (`ip`))", - "ENGINE = MyISAM ", - "CHARACTER SET utf8;"].join(""); - - module.exports.query(query, function (err, res) { - if(err) { - Logger.errlog.log("! Failed to create user aliases table"); - } - }); - - // Create action log table - query = ["CREATE TABLE IF NOT EXISTS `actionlog` (", - "`ip` VARCHAR(15) NOT NULL,", - "`name` VARCHAR(20) NOT NULL,", - "`action` VARCHAR(255) NOT NULL,", - "`args` TEXT NOT NULL,", - "`time` BIGINT NOT NULL,", - "PRIMARY KEY (`ip`, `time`), INDEX (`action`))", - "ENGINE = MyISAM ", - "CHARACTER SET utf8;"].join(""); - - module.exports.query(query, function (err, res) { - if(err) { - Logger.errlog.log("! Failed to create action log table"); - } - }); - - // Create stats table - query = ["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`))", - "ENGINE = MyISAM ", - "CHARACTER SET utf8;"].join(""); - - module.exports.query(query, function (err, res) { - if(err) { - Logger.errlog.log("! Failed to create stats table"); - } - }); - - // Refresh global IP bans - module.exports.listGlobalIPBans(); + query("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", + fail("stats")); }; /* REGION global bans */ +/** + * Check if an IP address is globally banned + */ module.exports.isGlobalIPBanned = function (ip, callback) { - if(typeof callback !== "function") + if (typeof callback !== "function") { return; + } + + // TODO account for IPv6 + // Also possibly just change this to allow arbitrary + // ranges instead of only /32, /24, /16 const re = /(\d+)\.(\d+)\.(\d+)\.(\d+)/; // Account for range banning var s16 = ip.replace(re, "$1.$2"); @@ -197,45 +161,58 @@ module.exports.isGlobalIPBanned = function (ip, callback) { callback(null, banned); }; -module.exports.listGlobalIPBans = function (callback) { - if(typeof callback !== "function") +/** + * Retrieve all global bans from the database. + * Cache locally in global_bans + */ +module.exports.listGlobalBans = function (callback) { + if (typeof callback !== "function") { callback = blackHole; + } module.exports.query("SELECT * FROM global_bans WHERE 1", function (err, res) { - if(err) { + if (err) { callback(err, null); return; } global_ipbans = {}; - for(var i in res) { - global_ipbans[res[i].ip] = res[i].note; + for (var i = 0; i < res.length; i++) { + global_ipbans[res[i].ip] = res[i]; } callback(null, global_ipbans); }); }; -module.exports.setGlobalIPBan = function (ip, reason, callback) { - if(typeof callback !== "function") +/** + * Globally ban by IP + */ +module.exports.globalBanIP = function (ip, reason, callback) { + if (typeof callback !== "function") { callback = blackHole; + } - var query = "INSERT INTO global_bans VALUES (?, ?)" + - " ON DUPLICATE KEY UPDATE note=?"; + var query = "INSERT INTO global_bans (ip, reason) VALUES (?, ?)" + + " ON DUPLICATE KEY UPDATE reason=?"; module.exports.query(query, [ip, reason, reason], function (err, res) { if(err) { callback(err, null); return; } - module.exports.listGlobalIPBans(); + module.exports.listGlobalBans(); callback(null, res); }); }; -module.exports.clearGlobalIPBan = function (ip, callback) { - if(typeof callback !== "function") +/** + * Remove a global IP ban + */ +module.exports.globalUnbanIP = function (ip, callback) { + if (typeof callback !== "function") { callback = blackHole; + } var query = "DELETE FROM global_bans WHERE ip=?"; @@ -245,139 +222,16 @@ module.exports.clearGlobalIPBan = function (ip, callback) { return; } + module.exports.listGlobalBans(); callback(null, res); }); }; /* END REGION */ -/* REGION Auth */ -module.exports.getGlobalRank = function (name, callback) { - if(typeof callback !== "function") - return; - - var query = "SELECT global_rank FROM registrations WHERE uname=?"; - - module.exports.query(query, [name], function (err, res) { - if(err) { - callback(err, null); - return; - } - - if(res.length == 0) { - callback(null, 0); - return; - } - - callback(null, res[0].global_rank); - }); -}; - -module.exports.listGlobalRanks = function (names, callback) { - if(typeof callback !== "function") - return; - - if(typeof names === "string") - names = [names]; - - // Build the query template (?, ?, ?, ?, ...) - var nlist = []; - for(var i in names) - nlist.push("?"); - nlist = "(" + nlist.join(",") + ")"; - - var query = "SELECT global_rank FROM registrations WHERE uname IN " + - nlist; - module.exports.query(query, names, function (err, res) { - if(err) { - callback(err, null); - return; - } - - if(res.length == 0) { - callback(null, 0); - return; - } - - for(var i in res) - res[i] = res[i].global_rank; - - callback(null, res); - }); -}; - -/* END REGION */ - -/* REGION users */ - -module.exports.searchUser = function (name, callback) { - if(typeof callback !== "function") - return; - - // NOTE: No SELECT * here because I don't want to risk exposing - // the user's password hash - var query = "SELECT id, uname, global_rank, profile_image, " + - "profile_text, email FROM registrations WHERE " + - "uname LIKE ?"; - - module.exports.query(query, ["%" + name + "%"], callback); -}; - -/* rank */ - -module.exports.setGlobalRank = function (name, rank, callback) { - if(typeof callback !== "function") - callback = blackHole; - - var query = "UPDATE registrations SET global_rank=? WHERE uname=?"; - module.exports.query(query, [rank, name], callback); -}; - -/* email and profile */ - -module.exports.getUserProfile = function (name, callback) { - if(typeof callback !== "function") - callback = blackHole; - - var query = "SELECT profile_image, profile_text FROM registrations " + - "WHERE uname=?"; - - module.exports.query(query, [name], function (err, res) { - if(err) { - callback(err, null); - return; - } - - var def = { - profile_image: "", - profile_text: "" - }; - - callback(null, res.length > 0 ? res[0] : def); - }); -}; - -module.exports.setUserProfile = function (name, data, callback) { - if(typeof callback !== "function") - callback = blackHole; - - var query = "UPDATE registrations SET profile_image=?, profile_text=?" + - "WHERE uname=?"; - - module.exports.query(query, [data.image, data.text, name], callback); -}; - -module.exports.setUserEmail = function (name, email, callback) { - if(typeof callback !== "function") - callback = blackHole; - - var query = "UPDATE registrations SET email=? WHERE uname=?"; - - module.exports.query(query, [email, name], callback); -}; - /* password recovery */ +/* module.exports.genPasswordReset = function (ip, name, email, callback) { if(typeof callback !== "function") callback = blackHole; @@ -483,31 +337,40 @@ module.exports.resetUserPassword = function (name, callback) { }); }); }; +*/ /* user playlists */ +/** + * Retrieve all of a user's playlists + */ module.exports.listUserPlaylists = function (name, callback) { - if(typeof callback !== "function") + if (typeof callback !== "function") { return; + } - var query = "SELECT name, count, time FROM user_playlists WHERE user=?"; + var query = "SELECT name, count, duration FROM user_playlists WHERE user=?"; module.exports.query(query, [name], callback); }; +/** + * Retrieve a user playlist by (user, name) pair + */ module.exports.getUserPlaylist = function (username, plname, callback) { - if(typeof callback !== "function") + if (typeof callback !== "function") { return; + } var query = "SELECT contents FROM user_playlists WHERE " + "user=? AND name=?"; module.exports.query(query, [username, plname], function (err, res) { - if(err) { + if (err) { callback(err, null); return; } - if(res.length == 0) { + if (res.length == 0) { callback("Playlist does not exist", null); return; } @@ -523,10 +386,14 @@ module.exports.getUserPlaylist = function (username, plname, callback) { }); }; -module.exports.saveUserPlaylist = function (pl, username, plname, - callback) { - if(typeof callback !== "function") +/** + * Saves a user playlist. Overwrites if the playlist keyed by + * (user, name) already exists + */ +module.exports.saveUserPlaylist = function (pl, username, plname, callback) { + if (typeof callback !== "function") { callback = blackHole; + } var tmp = [], time = 0; for(var i in pl) { @@ -543,7 +410,7 @@ module.exports.saveUserPlaylist = function (pl, username, plname, var plText = JSON.stringify(tmp); var query = "INSERT INTO user_playlists VALUES (?, ?, ?, ?, ?) " + - "ON DUPLICATE KEY UPDATE contents=?, count=?, time=?"; + "ON DUPLICATE KEY UPDATE contents=?, count=?, duration=?"; var params = [username, plname, plText, count, time, plText, count, time]; @@ -551,30 +418,27 @@ module.exports.saveUserPlaylist = function (pl, username, plname, module.exports.query(query, params, callback); }; -module.exports.deleteUserPlaylist = function (username, plname, - callback) { - if(typeof callback !== "function") +/** + * Deletes a user playlist + */ +module.exports.deleteUserPlaylist = function (username, plname, callback) { + if (typeof callback !== "function") { callback = blackHole; + } var query = "DELETE FROM user_playlists WHERE user=? AND name=?"; module.exports.query(query, [username, plname], callback); }; -/* user channels */ - -module.exports.listUserChannels = function (username, callback) { - if(typeof callback !== "function") - return; - - var query = "SELECT * FROM channels WHERE owner=? ORDER BY id ASC"; - module.exports.query(query, [username], callback); -}; - /* aliases */ +/** + * Records a user or guest login in the aliases table + */ module.exports.recordVisit = function (ip, name, callback) { - if(typeof callback !== "function") + if (typeof callback !== "function") { callback = blackHole; + } var time = Date.now(); var query = "DELETE FROM aliases WHERE ip=? AND name=?;" + @@ -583,20 +447,28 @@ module.exports.recordVisit = function (ip, name, callback) { module.exports.query(query, [ip, name, ip, name, time], callback); }; +/** + * Deletes alias rows older than the given time + */ module.exports.cleanOldAliases = function (expiration, callback) { - if (typeof callback === "undefined") + if (typeof callback === "undefined") { callback = blackHole; + } var query = "DELETE FROM aliases WHERE time < ?"; module.exports.query(query, [Date.now() - expiration], callback); }; -module.exports.listAliases = function (ip, callback) { - if(typeof callback !== "function") +/** + * Retrieves a list of aliases for an IP address + */ +module.exports.getAliases = function (ip, callback) { + if (typeof callback !== "function") { return; + } var query = "SELECT name,time FROM aliases WHERE ip"; - // Range + // if the ip parameter is a /24 range, we want to match accordingly if(ip.match(/^\d+\.\d+\.\d+$/)) { query += " LIKE ?"; ip += ".%"; @@ -609,28 +481,26 @@ module.exports.listAliases = function (ip, callback) { module.exports.query(query, [ip], function (err, res) { var names = null; if(!err) { - names = []; - res.forEach(function (row) { - names.push(row.name); - }); + names = res.map(function (row) { return row.name; }); } callback(err, names); }); }; -module.exports.listIPsForName = function (name, callback) { - if(typeof callback !== "function") +/** + * Retrieves a list of IPs that a name as logged in from + */ +module.exports.getIPs = function (name, callback) { + if (typeof callback !== "function") { return; + } var query = "SELECT ip FROM aliases WHERE name=?"; module.exports.query(query, [name], function (err, res) { var ips = null; if(!err) { - ips = []; - res.forEach(function (row) { - ips.push(row.ip); - }); + ips = res.map(function (row) { return row.ip; }); } callback(err, ips); @@ -641,6 +511,7 @@ module.exports.listIPsForName = function (name, callback) { /* REGION action log */ +/* module.exports.recordAction = function (ip, name, action, args, callback) { if(typeof callback !== "function") @@ -724,31 +595,34 @@ module.exports.listActions = function (types, callback) { var query = "SELECT * FROM actionlog WHERE action IN " + actionlist; module.exports.query(query, types, callback); }; +*/ /* END REGION */ /* REGION stats */ -module.exports.addStatPoint = function (time, ucount, ccount, mem, - callback) { - if(typeof callback !== "function") +module.exports.addStatPoint = function (time, ucount, ccount, mem, callback) { + if (typeof callback !== "function") { callback = blackHole; + } var query = "INSERT INTO stats VALUES (?, ?, ?, ?)"; module.exports.query(query, [time, ucount, ccount, mem], callback); }; module.exports.pruneStats = function (before, callback) { - if(typeof callback !== "function") + if (typeof callback !== "function") { callback = blackHole; + } var query = "DELETE FROM stats WHERE time < ?"; module.exports.query(query, [before], callback); }; module.exports.listStats = function (callback) { - if(typeof callback !== "function") + if (typeof callback !== "function") { return; + } var query = "SELECT * FROM stats ORDER BY time ASC"; module.exports.query(query, callback); diff --git a/lib/database/accounts.js b/lib/database/accounts.js index 25d1c2ba..21b6c3c3 100644 --- a/lib/database/accounts.js +++ b/lib/database/accounts.js @@ -494,4 +494,15 @@ module.exports = { callback(new Error("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); + } };