Kickstart the big db rewrite

This commit is contained in:
Holly Stubbs 2023-10-04 15:10:38 +01:00
parent 686e6001b2
commit 4b90031294
15 changed files with 140 additions and 47 deletions

View file

@ -12,8 +12,8 @@ import SpectatorManager from "./SpectatorManager";
import osu from "../osuTyping";
const shared:Shared = new Shared();
shared.database.query("UPDATE mp_matches SET close_time = UNIX_TIMESTAMP() WHERE close_time IS NULL");
shared.database.query("UPDATE osu_info SET value = 0 WHERE name = 'online_now'");
shared.database.execute("UPDATE mp_matches SET close_time = UNIX_TIMESTAMP() WHERE close_time IS NULL");
shared.database.execute("UPDATE osu_info SET value = 0 WHERE name = 'online_now'");
// Server Setup
const spectatorManager:SpectatorManager = new SpectatorManager(shared);
@ -90,7 +90,7 @@ export default async function HandleRequest(req:IncomingMessage, res:ServerRespo
// Client doesn't have a token yet, let's auth them!
await LoginProcess(req, res, packet, shared);
shared.database.query("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [shared.users.getLength() - 1]);
shared.database.execute("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [shared.users.getLength() - 1]);
} else {
let responseData = Buffer.allocUnsafe(0);
@ -240,11 +240,11 @@ export default async function HandleRequest(req:IncomingMessage, res:ServerRespo
break;
case Packets.Client_FriendAdd:
AddFriend(user, packet.data);
await AddFriend(user, packet.data);
break;
case Packets.Client_FriendRemove:
RemoveFriend(user, packet.data);
await RemoveFriend(user, packet.data);
break;
case Packets.Client_UserStatsRequest:

View file

@ -37,7 +37,7 @@ enum LoginResult {
function TestLogin(loginInfo:LoginInfo, shared:Shared) {
return new Promise<LoginResult>(async (resolve, reject) => {
const userDBData:UserInfo = await shared.database.query("SELECT * FROM users_info WHERE username = ? LIMIT 1", [loginInfo.username]);
const userDBData = await shared.userInfoRepository.getByUsername(loginInfo.username);
// Make sure a user was found in the database
if (userDBData == null) return resolve(LoginResult.INCORRECT);
@ -123,7 +123,10 @@ export default async function LoginProcess(req:IncomingMessage, res:ServerRespon
}
// Get information about the user from the database
const userId:number = (await shared.database.query("SELECT id FROM users_info WHERE username = ? LIMIT 1", [loginInfo.username])).id;
const userInfo = await shared.userInfoRepository.getByUsername(loginInfo.username);
if (userInfo == null) {
return;
}
// Create a token for the client
const newClientToken:string = await generateSession();
@ -136,7 +139,7 @@ export default async function LoginProcess(req:IncomingMessage, res:ServerRespon
}
// Retreive the newly created user
newUser = shared.users.add(newClientToken, new User(userId, loginInfo.username, newClientToken, shared));
newUser = shared.users.add(newClientToken, new User(userInfo.id, loginInfo.username, newClientToken, shared));
// Set tourney client flag
newUser.isTourneyUser = isTourneyClient;
newUser.location = userLocation;

View file

@ -1,4 +1,5 @@
export enum Permissions {
None = 0,
BAT = 2,
Supporter = 4,
Friend = 8,

View file

@ -1,5 +1,6 @@
import { ConsoleHelper } from "../../ConsoleHelper";
import { createPool, Pool } from "mysql2";
import { createPool, Pool, RowDataPacket } from "mysql2";
import { DBInDataType } from "../types/DBTypes";
export default class Database {
private connectionPool:Pool;
@ -20,50 +21,70 @@ export default class Database {
ConsoleHelper.printInfo(`Connected DB connection pool. MAX_CONNECTIONS = ${Database.CONNECTION_LIMIT}`);
}
public query(query = "", data?:Array<any>) {
const limited = query.includes("LIMIT 1");
return new Promise<any>((resolve, reject) => {
public execute(query:string, data?:Array<DBInDataType>) {
return new Promise<boolean>((resolve, reject) => {
this.connectionPool.getConnection((err, connection) => {
if (err) {
reject(err);
try {
connection.release();
} catch (e) {
ConsoleHelper.printError("Failed to release mysql connection\n" + err);
return reject(err);
}
if (data == null) {
connection.execute(query, data, (err, result) => {
if (err) {
connection.release();
return reject(err);
}
resolve(result !== undefined);
});
} else {
}
});
});
}
public query(query:string, data?:Array<DBInDataType>) {
return new Promise<RowDataPacket[]>((resolve, reject) => {
this.connectionPool.getConnection((err, connection) => {
if (err) {
return reject(err);
} else {
// Use old query
if (data == null) {
connection.query(query, (err, data) => {
connection.query<RowDataPacket[]>(query, (err, rows) => {
connection.release();
if (err) {
reject(err);
connection.release();
} else {
dataReceived(resolve, data, limited);
connection.release();
return reject(err);
}
resolve(rows);
connection.release();
});
}
// Use new prepared statements w/ placeholders
else {
connection.execute(query, data, (err, data) => {
connection.execute<RowDataPacket[]>(query, data, (err, rows) => {
connection.release();
if (err) {
reject(err);
connection.release();
} else {
dataReceived(resolve, data, limited);
connection.release();
}
});
}
}
});
});
}
return reject(err);
}
function dataReceived(resolveCallback:(value:unknown) => void, data:any, limited:boolean = false) : void {
if (limited) resolveCallback(data[0]);
else resolveCallback(data);
resolve(rows);
connection.release();
});
}
}
});
});
}
public async querySingle(query:string, data?:Array<DBInDataType>) {
const dbData = await this.query(query, data);
if (dbData != null && dbData.length > 0) {
return dbData[0];
}
return null;
}
}

View file

@ -29,7 +29,7 @@ export default class FunkyArray<T> {
}
public regenerateIterableArray() : void {
this.iterableArray = new Array();
this.iterableArray = new Array<T>();
for (const itemKey of this.itemKeys) {
this.iterableArray.push(this.items[itemKey]);
}
@ -64,7 +64,7 @@ export default class FunkyArray<T> {
return this.itemKeys;
}
public getItems() : any {
public getItems() : { [id: string]: T } {
return this.items;
}

View file

@ -242,7 +242,7 @@ export default class Match {
}
queryData.push(this.matchId);
await this.shared.database.query(
await this.shared.database.execute(
`UPDATE mp_matches SET ${gameNameChanged ? `name = ?${gameSeedChanged ? ", " : ""}` : ""}${gameSeedChanged ? `seed = ?` : ""} WHERE id = ?`,
queryData
);
@ -632,7 +632,7 @@ export default class Match {
slot.status = SlotStatus.NotReady;
}
await this.shared.database.query("INSERT INTO mp_match_rounds (id, match_id, round_id, round_mode, match_type, round_scoring_type, round_team_type, round_mods, beatmap_md5, freemod, player0, player1, player2, player3, player4, player5, player6, player7, player8, player9, player10, player11, player12, player13, player14, player15) VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", queryData);
await this.shared.database.execute("INSERT INTO mp_match_rounds (id, match_id, round_id, round_mode, match_type, round_scoring_type, round_team_type, round_mods, beatmap_md5, freemod, player0, player1, player2, player3, player4, player5, player6, player7, player8, player9, player10, player11, player12, player13, player14, player15) VALUES (NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", queryData);
osuPacketWriter.MatchComplete();

View file

@ -10,6 +10,7 @@ import User from "./User";
import LatLng from "./LatLng";
import Bot from "../Bot";
import { ConsoleHelper } from "../../ConsoleHelper";
import UserInfoRepository from "../repos/UserInfoRepository";
export default class Shared {
public readonly chatManager:ChatManager;
@ -21,6 +22,8 @@ export default class Shared {
public readonly users:UserArray;
public readonly bot:Bot;
public readonly userInfoRepository:UserInfoRepository;
public constructor() {
if (!existsSync("./config.json")) {
ConsoleHelper.printError("Config file missing!");
@ -46,5 +49,8 @@ export default class Shared {
this.multiplayerManager = new MultiplayerManager(this);
this.privateChatManager = new PrivateChatManager(this);
// DB Repos
this.userInfoRepository = new UserInfoRepository(this);
}
}

View file

@ -98,7 +98,7 @@ export default class User {
// Gets the user's score information from the database and caches it
async updateUserInfo(forceUpdate:boolean = false) {
const userScoreDB = await this.shared.database.query("SELECT * FROM users_modes_info WHERE user_id = ? AND mode_id = ? LIMIT 1", [this.id, this.playMode]);
const userScoreDB = await this.shared.database.querySingle("SELECT * FROM users_modes_info WHERE user_id = ? AND mode_id = ? LIMIT 1", [this.id, this.playMode]);
const mappedRankingMode = rankingModes[this.rankingMode];
const userRankDB = await this.shared.database.query(`SELECT user_id, ${mappedRankingMode} FROM users_modes_info WHERE mode_id = ? ORDER BY ${mappedRankingMode} DESC`, [this.playMode]);

View file

@ -1,3 +1,5 @@
import { Permissions } from "../../enums/Permissions";
export default class UserInfo {
id:number = Number.MIN_VALUE;
username:string = "";
@ -10,7 +12,7 @@ export default class UserInfo {
last_login_date:Date = new Date(0);
last_played_mode:number = Number.MIN_VALUE;
online_now:boolean = false;
tags:number = Number.MIN_VALUE;
tags:Permissions = Permissions.None;
supporter:boolean = false;
web_session:string = "";
verification_needed:boolean = false;

View file

@ -1,7 +1,7 @@
import User from "../objects/User";
export default function AddFriend(user:User, friendId:number) {
user.shared.database.query("INSERT INTO friends (user, friendsWith) VALUES (?, ?)", [
export default async function AddFriend(user:User, friendId:number) {
await user.shared.database.execute("INSERT INTO friends (user, friendsWith) VALUES (?, ?)", [
user.id, friendId
]);
}

View file

@ -13,7 +13,7 @@ export default async function Logout(user:User) {
// Remove user from user list
user.shared.users.remove(user.uuid);
await user.shared.database.query("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [user.shared.users.getLength() - 1]);
await user.shared.database.execute("UPDATE osu_info SET value = ? WHERE name = 'online_now'", [user.shared.users.getLength() - 1]);
ConsoleHelper.printBancho(`User logged out, took ${Date.now() - logoutStartTime}ms. [User: ${user.username}]`);
}

View file

@ -1,7 +1,7 @@
import User from "../objects/User";
export default function RemoveFriend(user:User, friendId:number) {
user.shared.database.query("DELETE FROM friends WHERE user = ? AND friendsWith = ? LIMIT 1", [
export default async function RemoveFriend(user:User, friendId:number) {
await user.shared.database.execute("DELETE FROM friends WHERE user = ? AND friendsWith = ? LIMIT 1", [
user.id, friendId
]);
}

View file

@ -0,0 +1,59 @@
import { RowDataPacket } from "mysql2";
import Database from "../objects/Database";
import Shared from "../objects/Shared";
import UserInfo from "../objects/database/UserInfo";
export default class UserInfoRepository {
private database:Database;
public constructor(shared:Shared) {
this.database = shared.database;
}
public async getById(id:number) {
const query = await this.database.query("SELECT * FROM users_info WHERE id = ? AND is_deleted = 0 LIMIT 1", [id]);
if (query != null) {
const userInfo = new UserInfo();
populateUserInfoFromRowDataPacket(userInfo, query[0]);
return userInfo;
}
return null;
}
public async getByUsername(username:string) {
const query = await this.database.query("SELECT * FROM users_info WHERE username = ? AND is_deleted = 0 LIMIT 1", [username]);
if (query != null) {
const userInfo = new UserInfo();
populateUserInfoFromRowDataPacket(userInfo, query[0]);
return userInfo;
}
return null;
}
}
function populateUserInfoFromRowDataPacket(userInfo:UserInfo, rowDataPacket:RowDataPacket) {
userInfo.id = rowDataPacket["id"];
userInfo.username = rowDataPacket["username"];
userInfo.username_safe = rowDataPacket["username_safe"];
userInfo.password_hash = rowDataPacket["password_hash"];
userInfo.password_salt = rowDataPacket["password_salt"];
userInfo.email = rowDataPacket["email"];
userInfo.country = rowDataPacket["country"];
userInfo.reg_date = rowDataPacket["reg_date"];
userInfo.last_login_date = rowDataPacket["last_login_date"];
userInfo.last_played_mode = rowDataPacket["last_played_mode"];
userInfo.online_now = rowDataPacket["online_now"];
userInfo.tags = rowDataPacket["tags"];
userInfo.supporter = rowDataPacket["supporter"];
userInfo.web_session = rowDataPacket["web_session"];
userInfo.verification_needed = rowDataPacket["verification_needed"];
userInfo.password_change_required = rowDataPacket["password_change_required"];
userInfo.has_old_password = rowDataPacket["has_old_password"];
userInfo.password_reset_key = rowDataPacket["password_reset_key"];
userInfo.away_message = rowDataPacket["away_message"];
userInfo.last_modified_time = rowDataPacket["last_modified_time"];
userInfo.is_deleted = rowDataPacket["is_deleted"];
}

1
server/types/DBTypes.ts Normal file
View file

@ -0,0 +1 @@
export type DBInDataType = string | number | null | undefined;

View file

@ -53,7 +53,7 @@ import { Registry, collectDefaultMetrics } from "prom-client";
import { RedisClientType, createClient } from "redis";
import { readFileSync, existsSync } from "fs";
import { randomBytes, pbkdf2 } from "crypto";
import { createPool, Pool } from "mysql2";
import { createPool, Pool, RowDataPacket } from "mysql2";
import * as dyetty from "dyetty";
import fetch from "node-fetch";
import http from "http";`);