Update db schema, fix broken /stats route

This commit is contained in:
Jakob Gillich 2020-08-26 18:53:55 +02:00
parent 24e264d229
commit 73c7a387ac
No known key found for this signature in database
GPG key ID: FD8BF52DB8452C91
6 changed files with 164 additions and 138 deletions

170
api/db.js
View file

@ -4,11 +4,11 @@ import bcrypt from 'bcrypt'
import assert from 'assert'
function objectFlip(obj) {
const ret = {};
Object.keys(obj).forEach(key => {
ret[obj[key]] = key;
});
return ret;
const ret = {};
Object.keys(obj).forEach(key => {
ret[obj[key]] = key;
});
return ret;
}
let pg_error_inv = objectFlip(pg_error)
@ -16,73 +16,73 @@ let pg_error_inv = objectFlip(pg_error)
export let pool;
export const FACTION_MAP = {
0 : ["Terran Republic", "TR"],
1 : ["New Conglomerate", "NC"],
2 : ["Vanu Sovereignty", "VS"],
3 : ["Neutral", "NL"],
0: ["Terran Republic", "TR"],
1: ["New Conglomerate", "NC"],
2: ["Vanu Sovereignty", "VS"],
3: ["Neutral", "NL"],
}
export const FACTION_MAP_INV = objectFlip(FACTION_MAP)
const BCRYPT_ROUNDS = 4;
export const SQL_ORDER = Object.freeze({
ASCENDING: Symbol("ASC"),
DESCENDING: Symbol("DESC"),
ASCENDING: Symbol("ASC"),
DESCENDING: Symbol("DESC"),
});
export const ACCOUNT = Object.freeze({
THIS: Symbol("accounts"),
ID: Symbol("id"),
USER: Symbol("username"),
PASSWORD: Symbol("passhash"),
CREATED: Symbol("created"),
MODIFIED: Symbol("last_modified"),
BANNED: Symbol("inactive"),
ADMIN: Symbol("gm"),
THIS: Symbol("account"),
ID: Symbol("id"),
USER: Symbol("username"),
PASSWORD: Symbol("passhash"),
CREATED: Symbol("created"),
MODIFIED: Symbol("last_modified"),
BANNED: Symbol("inactive"),
ADMIN: Symbol("gm"),
// A derived table column
LAST_LOGIN: Symbol("last_login"),
LAST_LOGIN: Symbol("last_login"),
});
export const CHARACTER = Object.freeze({
THIS: Symbol("characters"),
ID: Symbol("id"),
NAME: Symbol("name"),
ACCOUNT_ID: Symbol("account_id"),
FACTION: Symbol("faction_id"),
GENDER: Symbol("gender_id"),
HEAD: Symbol("head_id"),
VOICE: Symbol("void_id"),
CREATED: Symbol("created"),
LAST_LOGIN: Symbol("last_login"),
LAST_MODIFIED: Symbol("last_modified"),
DELETED: Symbol("deleted"),
THIS: Symbol("avatar"),
ID: Symbol("id"),
NAME: Symbol("name"),
ACCOUNT_ID: Symbol("account_id"),
FACTION: Symbol("faction_id"),
GENDER: Symbol("gender_id"),
HEAD: Symbol("head_id"),
VOICE: Symbol("void_id"),
CREATED: Symbol("created"),
LAST_LOGIN: Symbol("last_login"),
LAST_MODIFIED: Symbol("last_modified"),
DELETED: Symbol("deleted"),
});
export const LOGIN = Object.freeze({
THIS: Symbol("logins"),
ID: Symbol("id"),
ACCOUNT_ID: Symbol("account_id"),
THIS: Symbol("login"),
ID: Symbol("id"),
ACCOUNT_ID: Symbol("account_id"),
});
function to_sql(symbol) {
assert(typeof symbol == 'symbol',
`symbol expected got ${typeof symbol}`)
return String(symbol).slice(7,-1);
return String(symbol).slice(7, -1);
}
function to_sql_kv(fields, idx=1) {
function to_sql_kv(fields, idx = 1) {
let SQL = [];
let values = [];
// This will ONLY get Symbols in the field dict
if (!fields || Object.getOwnPropertySymbols(fields).length == 0) {
return { sql : [], next_idx: idx, values: [] }
return { sql: [], next_idx: idx, values: [] }
}
Object.getOwnPropertySymbols(fields).forEach(key => {
assert(typeof key == 'symbol')
SQL.push(to_sql(key)+"=$"+idx++);
SQL.push(to_sql(key) + "=$" + idx++);
values.push(fields[key]);
});
@ -93,7 +93,7 @@ function to_sql_kv(fields, idx=1) {
}
}
function build_SET(fields, idx=1) {
function build_SET(fields, idx = 1) {
const kv = to_sql_kv(fields, idx);
assert(kv.sql.length > 0, "SET MUST have at least one kv pair")
@ -103,7 +103,7 @@ function build_SET(fields, idx=1) {
return kv;
}
function build_WHERE(filter, idx=1) {
function build_WHERE(filter, idx = 1) {
const kv = to_sql_kv(filter.fields, idx);
if (kv.sql.length > 1) {
@ -136,7 +136,7 @@ function build_ORDER_BY(sort) {
return Symbol(`ORDER BY ${SQL.join(", ")}`);
}
function build_OFFSET(offset, limit, idx=1) {
function build_OFFSET(offset, limit, idx = 1) {
assert(typeof offset == 'number', "offset must be a number");
assert(typeof limit == 'number', "limit must be a number");
@ -147,13 +147,13 @@ function build_OFFSET(offset, limit, idx=1) {
};
}
async function get_row_count(table, filter=undefined) {
async function get_row_count(table, filter = undefined) {
let resp;
if (filter) {
const where = build_WHERE(filter);
resp = await pool.query(`SELECT COUNT(*) FROM ${to_sql(table)} ${to_sql(where.sql)}`,
where.values);
where.values);
} else {
resp = await pool.query(`SELECT COUNT(*) FROM ${to_sql(table)}`);
}
@ -180,7 +180,7 @@ export async function connect_to_db() {
export async function get_account_by_id(id) {
try {
const account = await pool.query('SELECT * FROM accounts WHERE id=$1', [id]);
const account = await pool.query('SELECT * FROM account WHERE id=$1', [id]);
if (account.rows.length == 0) {
return undefined;
@ -196,12 +196,12 @@ export async function get_account_by_id(id) {
}
export async function get_accounts(pagination, sort, order) {
const start_id = (pagination.page-1)*pagination.items_per_page;
const start_id = (pagination.page - 1) * pagination.items_per_page;
const values = [start_id, pagination.items_per_page];
try {
const account_count = await get_row_count(ACCOUNT.THIS);
const accounts = await pool.query(`SELECT id, username, created, last_modified, gm, inactive FROM accounts ORDER BY ${to_sql(sort)} ${to_sql(order)} OFFSET $1 LIMIT $2`, values);
const accounts = await pool.query(`SELECT id, username, created, last_modified, gm, inactive FROM account ORDER BY ${to_sql(sort)} ${to_sql(order)} OFFSET $1 LIMIT $2`, values);
pagination.item_count = account_count;
pagination.page_count = Math.ceil(pagination.item_count / pagination.items_per_page);
@ -220,7 +220,7 @@ export async function get_accounts(pagination, sort, order) {
}
export async function get_accounts_login_info(pagination, sort, filter) {
const start_id = (pagination.page-1)*pagination.items_per_page;
const start_id = (pagination.page - 1) * pagination.items_per_page;
const values = [start_id, pagination.items_per_page];
try {
@ -233,17 +233,17 @@ export async function get_accounts_login_info(pagination, sort, filter) {
// this was a really hard query to get right...
// https://www.gab.lc/articles/better_faster_subqueries_postgresql/
const accounts = await pool.query(
'SELECT accounts.*, COALESCE(l.lastLogin, TIMESTAMP \'epoch\') as last_login, l2.ip_address, l2.canonical_hostname FROM accounts' +
' LEFT OUTER JOIN (' +
' SELECT MAX(id) as loginId, account_id, MAX(login_time) as lastLogin' +
' FROM logins' +
' GROUP BY account_id' +
' ) l ON l.account_id = accounts.id' +
' LEFT OUTER JOIN logins l2' +
' ON l2.id = l.loginId' +
` ${to_sql(where.sql)}` +
` ${to_sql(order)}` +
` ${to_sql(offset.sql)}`, values);
'SELECT account.*, COALESCE(l.lastLogin, TIMESTAMP \'epoch\') as last_login, l2.ip_address, l2.canonical_hostname FROM account' +
' LEFT OUTER JOIN (' +
' SELECT MAX(id) as loginId, account_id, MAX(login_time) as lastLogin' +
' FROM login' +
' GROUP BY account_id' +
' ) l ON l.account_id = account.id' +
' LEFT OUTER JOIN login l2' +
' ON l2.id = l.loginId' +
` ${to_sql(where.sql)}` +
` ${to_sql(order)}` +
` ${to_sql(offset.sql)}`, values);
pagination.item_count = account_count;
pagination.page_count = Math.ceil(pagination.item_count / pagination.items_per_page);
@ -254,9 +254,9 @@ export async function get_accounts_login_info(pagination, sort, filter) {
if (r.ip_address !== null) {
r.last_login = {
time : r.last_login,
hostname : r.canonical_hostname,
ip : r.ip_address,
time: r.last_login,
hostname: r.canonical_hostname,
ip: r.ip_address,
}
} else {
r.last_login = {}
@ -277,12 +277,12 @@ export async function get_accounts_login_info(pagination, sort, filter) {
}
export async function get_characters(pagination, sort, order) {
const start_id = (pagination.page-1)*pagination.items_per_page;
const start_id = (pagination.page - 1) * pagination.items_per_page;
const values = [start_id, pagination.items_per_page];
try {
const char_count = await get_row_count(CHARACTER.THIS);
const chars = await pool.query(`SELECT id, account_id, name, faction_id, created, last_login FROM characters ORDER BY ${to_sql(sort)} ${to_sql(order)} OFFSET $1 LIMIT $2`, values);
const chars = await pool.query(`SELECT id, account_id, name, faction_id, created, last_login FROM avatar ORDER BY ${to_sql(sort)} ${to_sql(order)} OFFSET $1 LIMIT $2`, values);
pagination.item_count = char_count;
pagination.page_count = Math.ceil(pagination.item_count / pagination.items_per_page);
@ -297,7 +297,7 @@ export async function get_characters(pagination, sort, order) {
export async function get_characters_by_account(account_id) {
try {
const characters = await pool.query('SELECT * FROM characters WHERE account_id=$1 AND deleted=false', [account_id])
const characters = await pool.query('SELECT * FROM avatar WHERE account_id=$1 AND deleted=false', [account_id])
return characters.rows;
} catch (e) {
if (e.code)
@ -308,7 +308,7 @@ export async function get_characters_by_account(account_id) {
export async function get_account_by_name(name) {
try {
const account = await pool.query('SELECT * FROM accounts WHERE username=$1', [name]);
const account = await pool.query('SELECT * FROM account WHERE username=$1', [name]);
return account.rows[0];
} catch (e) {
if (e.code)
@ -319,7 +319,7 @@ export async function get_account_by_name(name) {
export async function get_character_by_name(name) {
try {
const account = await pool.query('SELECT id, account_id, name, faction_id, created, last_login FROM characters WHERE name=$1 AND deleted=false', [name]);
const account = await pool.query('SELECT id, account_id, name, faction_id, created, last_login FROM avatar WHERE name=$1 AND deleted=false', [name]);
return account.rows[0];
} catch (e) {
if (e.code)
@ -331,7 +331,7 @@ export async function get_character_by_name(name) {
export async function create_account(username, password) {
try {
const passhash = await bcrypt.hash(password, BCRYPT_ROUNDS);
const account_id = await pool.query('INSERT INTO accounts(username, passhash) VALUES($1, $2) RETURNING id', [username, passhash]);
const account_id = await pool.query('INSERT INTO account(username, passhash) VALUES($1, $2) RETURNING id', [username, passhash]);
return account_id.rows[0].id;
} catch (e) {
if (e.code)
@ -349,7 +349,7 @@ export async function update_account(account_id, fields) {
set.values.push(account_id)
try {
const update_result = await pool.query(`UPDATE accounts ${to_sql(set.sql)} WHERE id=$${set.next_idx}`, set.values);
const update_result = await pool.query(`UPDATE account ${to_sql(set.sql)} WHERE id=$${set.next_idx}`, set.values);
return update_result.rowCount;
} catch (e) {
if (e.code)
@ -360,7 +360,7 @@ export async function update_account(account_id, fields) {
export async function get_empire_stats() {
try {
const query = await pool.query('SELECT faction_id, COUNT(*) FROM characters GROUP BY faction_id');
const query = await pool.query('SELECT faction_id, COUNT(*) FROM avatar GROUP BY faction_id');
const empires = {};
query.rows.forEach((r) => {
@ -380,7 +380,7 @@ export async function get_stats() {
try {
const account_count = await get_row_count(ACCOUNT.THIS);
const character_count = await get_row_count(CHARACTER.THIS);
const last_character = await pool.query('SELECT id, account_id, name, faction_id, created FROM characters ORDER BY id DESC LIMIT 1');
const last_character = await pool.query('SELECT id, account_id, name, faction_id, created FROM avatar ORDER BY id DESC LIMIT 1');
const stats = {}
@ -398,16 +398,16 @@ export async function get_stats() {
}
export async function get_account_logins(account_id, pagination) {
const start_id = (pagination.page-1)*pagination.items_per_page;
const start_id = (pagination.page - 1) * pagination.items_per_page;
const values = [account_id, start_id, pagination.items_per_page];
try {
const login_count = await get_row_count(LOGIN.THIS, {
fields : {
[LOGIN.ACCOUNT_ID] : account_id,
fields: {
[LOGIN.ACCOUNT_ID]: account_id,
}
});
const logins = await pool.query('SELECT * FROM logins WHERE account_id=$1 ORDER by login_time DESC ' + ` OFFSET $2 LIMIT $3`, values);
const logins = await pool.query('SELECT * FROM login WHERE account_id=$1 ORDER by login_time DESC ' + ` OFFSET $2 LIMIT $3`, values);
pagination.item_count = login_count;
pagination.page_count = Math.ceil(pagination.item_count / pagination.items_per_page);
@ -421,7 +421,7 @@ export async function get_account_logins(account_id, pagination) {
}
export async function search(term, pagination) {
const start_id = (pagination.page-1)*pagination.items_per_page;
const start_id = (pagination.page - 1) * pagination.items_per_page;
term = term.replace(/%/g, "");
@ -429,15 +429,15 @@ export async function search(term, pagination) {
return [];
}
const values = ['%'+term.toUpperCase()+'%', start_id, pagination.items_per_page];
const values = ['%' + term.toUpperCase() + '%', start_id, pagination.items_per_page];
try {
const accounts = await pool.query('SELECT id, username, gm, inactive FROM accounts ' +
'WHERE upper(username) LIKE $1 '+
` ORDER BY username OFFSET $2 LIMIT $3`, values);
const characters = await pool.query('SELECT id, name, account_id, faction_id FROM characters ' +
'WHERE upper(name) LIKE $1 '+
` ORDER BY name OFFSET $2 LIMIT $3`, values);
const accounts = await pool.query('SELECT id, username, gm, inactive FROM account ' +
'WHERE upper(username) LIKE $1 ' +
` ORDER BY username OFFSET $2 LIMIT $3`, values);
const characters = await pool.query('SELECT id, name, account_id, faction_id FROM avatar ' +
'WHERE upper(name) LIKE $1 ' +
` ORDER BY name OFFSET $2 LIMIT $3`, values);
pagination.item_count = 100;
pagination.page_count = Math.ceil(pagination.item_count / pagination.items_per_page);
@ -460,7 +460,7 @@ export async function search(term, pagination) {
});
// sort by name
results.sort(function(a, b) {
results.sort(function (a, b) {
var nameA = a.name.toUpperCase(); // ignore upper and lowercase
var nameB = b.name.toUpperCase(); // ignore upper and lowercase
if (nameA < nameB) {
@ -484,7 +484,7 @@ export async function search(term, pagination) {
export async function validate_account(username, password) {
try {
const data = await pool.query('SELECT id, passhash FROM accounts WHERE username=$1 and gm=true', [username]);
const data = await pool.query('SELECT id, passhash FROM account WHERE username=$1', [username]);
if (data.rows.length === 0) {
return undefined;