CyTube/src/database.js

408 lines
11 KiB
JavaScript
Raw Normal View History

2013-08-12 14:34:57 +00:00
var mysql = require("mysql");
var bcrypt = require("bcrypt");
2014-01-22 23:11:26 +00:00
var Config = require("./config");
var tables = require("./database/tables");
2014-05-21 02:30:14 +00:00
var net = require("net");
var util = require("./utilities");
import * as Metrics from './metrics/metrics';
2017-05-29 05:39:27 +00:00
import knex from 'knex';
2017-06-06 05:53:35 +00:00
import { GlobalBanDB } from './db/globalban';
2017-04-05 06:02:31 +00:00
2017-07-09 03:11:54 +00:00
const LOGGER = require('@calzoneman/jsli')('database');
2013-06-03 22:37:30 +00:00
2017-05-29 05:39:27 +00:00
let db = null;
2017-06-06 05:53:35 +00:00
let globalBanDB = null;
2013-12-12 22:28:30 +00:00
2017-05-29 05:39:27 +00:00
class Database {
constructor(knexConfig = null) {
if (knexConfig === null) {
knexConfig = {
client: 'mysql',
connection: {
host: Config.get('mysql.server'),
port: Config.get('mysql.port'),
user: Config.get('mysql.user'),
password: Config.get('mysql.password'),
database: Config.get('mysql.database'),
multipleStatements: true, // Legacy thing
charset: 'UTF8MB4_GENERAL_CI'
},
pool: {
min: Config.get('mysql.pool-size'),
max: Config.get('mysql.pool-size')
},
debug: !!process.env.KNEX_DEBUG
};
}
2013-06-03 22:37:30 +00:00
this.knex = knex(knexConfig);
}
runTransaction(fn) {
const timer = Metrics.startTimer('db:queryTime');
return this.knex.transaction(fn).finally(() => {
Metrics.stopTimer(timer);
});
2017-05-29 05:39:27 +00:00
}
}
2013-08-12 14:58:21 +00:00
module.exports.Database = Database;
2017-06-06 05:45:14 +00:00
module.exports.init = function (newDB) {
if (newDB) {
db = newDB;
} else {
db = new Database();
}
2017-05-29 05:39:27 +00:00
db.knex.raw('select 1 from dual')
.catch(error => {
LOGGER.error('Initial database connection failed: %s', error.stack);
process.exit(1);
}).then(() => {
process.nextTick(legacySetup);
});
2013-12-12 23:09:49 +00:00
module.exports.users = require("./database/accounts");
2013-12-14 00:52:13 +00:00
module.exports.channels = require("./database/channels");
2013-08-12 14:34:57 +00:00
};
2017-05-29 05:39:27 +00:00
module.exports.getDB = function getDB() {
return db;
};
2017-06-06 05:53:35 +00:00
module.exports.getGlobalBanDB = function getGlobalBanDB() {
if (globalBanDB === null) {
globalBanDB = new GlobalBanDB(db);
}
return globalBanDB;
};
2017-05-29 05:39:27 +00:00
function legacySetup() {
tables.init(module.exports.query, function (err) {
if (err) {
return;
}
require("./database/update").checkVersion();
module.exports.loadAnnouncement();
});
}
2013-12-14 02:39:21 +00:00
/**
* Execute a database query
*/
2013-12-12 22:28:30 +00:00
module.exports.query = function (query, sub, callback) {
2016-04-28 04:55:25 +00:00
const timer = Metrics.startTimer('db:queryTime');
2013-08-12 14:34:57 +00:00
// 2nd argument is optional
2013-12-14 02:39:21 +00:00
if (typeof sub === "function") {
2013-08-12 14:34:57 +00:00
callback = sub;
2017-05-29 05:39:27 +00:00
sub = undefined;
2013-08-12 14:34:57 +00:00
}
2013-12-14 02:39:21 +00:00
if (typeof callback !== "function") {
callback = blackHole;
2013-12-14 02:39:21 +00:00
}
2017-05-29 05:39:27 +00:00
if (process.env.SHOW_SQL) {
console.log(query);
}
db.knex.raw(query, sub)
.then(res => {
process.nextTick(callback, null, res[0]);
}).catch(error => {
LOGGER.error('Legacy DB query failed. Query: %s, Substitutions: %j, Error: %s', query, sub, error);
process.nextTick(callback, 'Database failure', null);
}).finally(() => {
Metrics.stopTimer(timer);
});
};
2013-08-21 00:56:03 +00:00
2013-12-27 03:15:54 +00:00
/**
* Dummy function to be used as a callback when none is provided
*/
2013-08-21 00:56:03 +00:00
function blackHole() {
}
/* password recovery */
2014-02-01 19:03:08 +00:00
/**
* Deletes recovery rows older than the given time
*/
module.exports.cleanOldPasswordResets = function (callback) {
if (typeof callback === "undefined") {
callback = blackHole;
}
var query = "DELETE FROM password_reset WHERE expire < ?";
2014-02-01 19:03:08 +00:00
module.exports.query(query, [Date.now() - 24*60*60*1000], callback);
};
module.exports.addPasswordReset = function (data, cb) {
if (typeof cb !== "function") {
cb = blackHole;
}
var ip = data.ip || "";
var name = data.name;
var email = data.email;
var hash = data.hash;
var expire = data.expire;
if (!name || !hash) {
cb("Internal error: Must provide name and hash to insert a new password reset", null);
return;
}
module.exports.query("INSERT INTO `password_reset` (`ip`, `name`, `email`, `hash`, `expire`) " +
"VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE ip=?, hash=?, email=?, expire=?",
[ip, name, email, hash, expire, ip, hash, email, expire], cb);
};
2014-02-01 19:03:08 +00:00
module.exports.lookupPasswordReset = function (hash, cb) {
if (typeof cb !== "function") {
return;
}
module.exports.query("SELECT * FROM `password_reset` WHERE hash=?", [hash],
function (err, rows) {
if (err) {
cb(err, null);
} else if (rows.length === 0) {
cb("Invalid password reset link", null);
} else {
cb(null, rows[0]);
}
});
};
module.exports.deletePasswordReset = function (hash) {
module.exports.query("DELETE FROM `password_reset` WHERE hash=?", [hash]);
};
/* user playlists */
2013-12-14 02:39:21 +00:00
/**
* Retrieve all of a user's playlists
*/
2013-12-12 22:28:30 +00:00
module.exports.listUserPlaylists = function (name, callback) {
2013-12-14 02:39:21 +00:00
if (typeof callback !== "function") {
return;
2013-12-14 02:39:21 +00:00
}
2013-12-14 02:39:21 +00:00
var query = "SELECT name, count, duration FROM user_playlists WHERE user=?";
2013-12-12 22:28:30 +00:00
module.exports.query(query, [name], callback);
};
2013-12-14 02:39:21 +00:00
/**
* Retrieve a user playlist by (user, name) pair
*/
2013-12-12 22:28:30 +00:00
module.exports.getUserPlaylist = function (username, plname, callback) {
2013-12-14 02:39:21 +00:00
if (typeof callback !== "function") {
return;
2013-12-14 02:39:21 +00:00
}
var query = "SELECT contents FROM user_playlists WHERE " +
"user=? AND name=?";
2013-12-12 22:28:30 +00:00
module.exports.query(query, [username, plname], function (err, res) {
2013-12-14 02:39:21 +00:00
if (err) {
callback(err, null);
return;
}
2013-12-14 02:39:21 +00:00
if (res.length == 0) {
callback("Playlist does not exist", null);
return;
}
var pl = null;
try {
pl = JSON.parse(res[0].contents);
} catch(e) {
callback("Malformed playlist JSON", null);
return;
}
callback(null, pl);
});
};
2013-12-14 02:39:21 +00:00
/**
* 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;
2013-12-14 02:39:21 +00:00
}
var tmp = [], time = 0;
for(var i in pl) {
var e = {
id: pl[i].media.id,
title: pl[i].media.title,
seconds: pl[i].media.seconds || 0,
type: pl[i].media.type,
meta: {
codec: pl[i].media.meta.codec,
bitrate: pl[i].media.meta.bitrate,
scuri: pl[i].media.meta.scuri,
embed: pl[i].media.meta.embed
}
};
time += pl[i].media.seconds || 0;
tmp.push(e);
}
var count = tmp.length;
var plText = JSON.stringify(tmp);
var query = "INSERT INTO user_playlists VALUES (?, ?, ?, ?, ?) " +
2013-12-14 02:39:21 +00:00
"ON DUPLICATE KEY UPDATE contents=?, count=?, duration=?";
var params = [username, plname, plText, count, time,
plText, count, time];
2013-12-12 22:28:30 +00:00
module.exports.query(query, params, callback);
};
2013-12-14 02:39:21 +00:00
/**
* Deletes a user playlist
*/
module.exports.deleteUserPlaylist = function (username, plname, callback) {
if (typeof callback !== "function") {
callback = blackHole;
2013-12-14 02:39:21 +00:00
}
var query = "DELETE FROM user_playlists WHERE user=? AND name=?";
2013-12-12 22:28:30 +00:00
module.exports.query(query, [username, plname], callback);
};
/* aliases */
2013-12-14 02:39:21 +00:00
/**
* Records a user or guest login in the aliases table
*/
2013-12-12 22:28:30 +00:00
module.exports.recordVisit = function (ip, name, callback) {
2013-12-14 02:39:21 +00:00
if (typeof callback !== "function") {
callback = blackHole;
2013-12-14 02:39:21 +00:00
}
var time = Date.now();
var query = "DELETE FROM aliases WHERE ip=? AND name=?;" +
"INSERT INTO aliases VALUES (NULL, ?, ?, ?)";
2013-12-12 22:28:30 +00:00
module.exports.query(query, [ip, name, ip, name, time], callback);
};
2013-12-14 02:39:21 +00:00
/**
* Deletes alias rows older than the given time
*/
2013-12-12 22:28:30 +00:00
module.exports.cleanOldAliases = function (expiration, callback) {
2013-12-14 02:39:21 +00:00
if (typeof callback === "undefined") {
callback = blackHole;
2013-12-14 02:39:21 +00:00
}
var query = "DELETE FROM aliases WHERE time < ?";
2013-12-12 22:28:30 +00:00
module.exports.query(query, [Date.now() - expiration], callback);
};
2013-12-14 02:39:21 +00:00
/**
* Retrieves a list of aliases for an IP address
*/
module.exports.getAliases = function (ip, callback) {
if (typeof callback !== "function") {
return;
2013-12-14 02:39:21 +00:00
}
var query = "SELECT name,time FROM aliases WHERE ip";
2013-12-14 02:39:21 +00:00
// if the ip parameter is a /24 range, we want to match accordingly
2014-05-21 02:30:14 +00:00
if (ip.match(/^\d+\.\d+\.\d+$/) || ip.match(/^\d+\.\d+$/)) {
2013-08-18 22:58:16 +00:00
query += " LIKE ?";
ip += ".%";
2014-05-21 02:30:14 +00:00
} else if (ip.match(/^(?:[0-9a-f]{4}:){3}[0-9a-f]{4}$/) ||
ip.match(/^(?:[0-9a-f]{4}:){2}[0-9a-f]{4}$/)) {
query += " LIKE ?";
ip += ":%";
2013-08-18 22:58:16 +00:00
} else {
query += "=?";
}
query += " ORDER BY time DESC LIMIT 5";
2013-12-12 22:28:30 +00:00
module.exports.query(query, [ip], function (err, res) {
var names = null;
if(!err) {
2013-12-14 02:39:21 +00:00
names = res.map(function (row) { return row.name; });
}
callback(err, names);
});
};
2013-12-14 02:39:21 +00:00
/**
* Retrieves a list of IPs that a name as logged in from
*/
module.exports.getIPs = function (name, callback) {
if (typeof callback !== "function") {
return;
2013-12-14 02:39:21 +00:00
}
var query = "SELECT ip FROM aliases WHERE name=?";
2013-12-12 22:28:30 +00:00
module.exports.query(query, [name], function (err, res) {
var ips = null;
if(!err) {
2013-12-14 02:39:21 +00:00
ips = res.map(function (row) { return row.ip; });
}
callback(err, ips);
});
};
/* END REGION */
2013-08-15 22:44:22 +00:00
2014-02-01 18:41:06 +00:00
/* Misc */
module.exports.loadAnnouncement = function () {
var query = "SELECT * FROM `meta` WHERE `key`='announcement'";
module.exports.query(query, function (err, rows) {
if (err) {
return;
}
if (rows.length === 0) {
return;
}
var announcement = rows[0].value;
try {
announcement = JSON.parse(announcement);
} catch (e) {
2017-04-05 06:02:31 +00:00
LOGGER.error("Invalid announcement data in database: " +
2014-02-01 18:41:06 +00:00
announcement.value);
module.exports.clearAnnouncement();
return;
}
2015-10-08 05:19:39 +00:00
var Server = require("./server");
if (!Server.getServer || !Server.getServer()) {
return;
}
2014-02-01 18:41:06 +00:00
var sv = Server.getServer();
sv.announcement = announcement;
for (var id in sv.ioServers) {
2016-01-07 05:42:48 +00:00
sv.ioServers[id].emit("announcement", announcement);
2014-02-01 18:41:06 +00:00
}
});
};
module.exports.setAnnouncement = function (data) {
var query = "INSERT INTO `meta` (`key`, `value`) VALUES ('announcement', ?) " +
"ON DUPLICATE KEY UPDATE `value`=?";
var repl = JSON.stringify(data);
module.exports.query(query, [repl, repl]);
};
module.exports.clearAnnouncement = function () {
module.exports.query("DELETE FROM `meta` WHERE `key`='announcement'");
};