diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b42aef9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,27 @@ +# see: http://EditorConfig.org + +# end_of_line is unspecified so Windows users can use the repo in crlf mode + +root = true + +[*] +charset = utf-8 +indent_style = tab +indent_size = 3 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{json,yml,webapp}] +# npm style +indent_style = space +indent_size = 2 + +[*.md] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = false + +[*.tsv] +indent_style = tab +indent_size = 8 +trim_trailing_whitespace = false diff --git a/.mocharc.json b/.mocharc.json new file mode 100644 index 0000000..b8a4c78 --- /dev/null +++ b/.mocharc.json @@ -0,0 +1,6 @@ +{ + "spec": [".dist/src/test/main.js", ".dist/src/test/**.test.js"], + "reporter": "dot", + "ui": "bdd", + "exit": true +} diff --git a/package.json b/package.json index 2f93b77..c78685b 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "google-auth-library": "^3.1.2", "mysql2": "^2.3.3", "pm2": "^5.1.2", - "sql-template-strings": "^2.2.2" + "sql-template-strings": "^2.2.2", + "testcontainers": "^9.1.1" }, "devDependencies": { "@types/bcrypt": "^5.0.0", diff --git a/src/schemas/ntbb-ladder.sql b/src/schemas/ntbb-ladder.sql new file mode 100644 index 0000000..f05dcb5 --- /dev/null +++ b/src/schemas/ntbb-ladder.sql @@ -0,0 +1,46 @@ +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; + +-- +-- Database: `showdown` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `ntbb_ladder` +-- + +CREATE TABLE IF NOT EXISTS `ntbb_ladder` ( + `entryid` int(11) NOT NULL AUTO_INCREMENT, + `formatid` varbinary(63) NOT NULL, + `userid` varbinary(63) NOT NULL, + `username` varbinary(63) NOT NULL, + `w` int(11) NOT NULL DEFAULT '0', + `l` int(11) NOT NULL DEFAULT '0', + `t` int(11) NOT NULL DEFAULT '0', + `gxe` double NOT NULL DEFAULT '0', + `r` double NOT NULL DEFAULT '1500', + `rd` double NOT NULL DEFAULT '350', + `sigma` double NOT NULL DEFAULT '0.06', + `rptime` bigint(11) NOT NULL, + `rpr` double NOT NULL DEFAULT '1500', + `rprd` double NOT NULL DEFAULT '350', + `rpsigma` double NOT NULL DEFAULT '0.06', + `rpdata` mediumblob NOT NULL, + `elo` double NOT NULL DEFAULT '1000', + `col1` double NOT NULL, + PRIMARY KEY (`entryid`), + UNIQUE KEY `userformats` (`userid`,`formatid`), + KEY `formattoplist` (`formatid`,`elo`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin AUTO_INCREMENT=1; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/src/schemas/ntbb-loginthrottle.sql b/src/schemas/ntbb-loginthrottle.sql new file mode 100644 index 0000000..6579251 --- /dev/null +++ b/src/schemas/ntbb-loginthrottle.sql @@ -0,0 +1,9 @@ +CREATE TABLE `ntbb_loginthrottle` ( + `ip` varchar(63) COLLATE utf8mb4_bin NOT NULL, + `count` int(11) NOT NULL, + `lastuserid` varchar(63) COLLATE utf8mb4_bin NOT NULL, + `time` int(11) NOT NULL, + PRIMARY KEY (`ip`), + KEY `count` (`count`), + KEY `time` (`time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin; diff --git a/src/schemas/ntbb-usermodlog.sql b/src/schemas/ntbb-usermodlog.sql new file mode 100644 index 0000000..b51aa94 --- /dev/null +++ b/src/schemas/ntbb-usermodlog.sql @@ -0,0 +1,16 @@ +-- +-- Table structure for table `ntbb_usermodlog` +-- + +CREATE TABLE `ntbb_usermodlog` ( + `entryid` int(11) NOT NULL AUTO_INCREMENT, + `userid` varchar(63) CHARACTER SET latin1 NOT NULL, + `actorid` varchar(63) CHARACTER SET latin1 NOT NULL, + `date` int(11) NOT NULL, + `ip` varchar(63) CHARACTER SET latin1 NOT NULL, + `entry` varchar(1023) CHARACTER SET latin1 NOT NULL, + PRIMARY KEY (`entryid`), + KEY `userid` (`userid`), + KEY `actorid` (`actorid`), + KEY `ip` (`ip`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/src/schemas/ntbb-users.sql b/src/schemas/ntbb-users.sql new file mode 100644 index 0000000..856e46d --- /dev/null +++ b/src/schemas/ntbb-users.sql @@ -0,0 +1,51 @@ +-- Database: showdown + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `ntbb_users` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `ntbb_users` ( + `userid` varbinary(255) NOT NULL, + `usernum` int(11) NOT NULL AUTO_INCREMENT, + `username` varbinary(255) NOT NULL, + `password` varbinary(255) DEFAULT NULL, + `nonce` varbinary(255) DEFAULT NULL, + `passwordhash` varbinary(255) DEFAULT NULL, + `email` varbinary(255) DEFAULT NULL, + `registertime` bigint(20) NOT NULL, + `group` int(11) NOT NULL DEFAULT '1', + `banstate` int(11) NOT NULL DEFAULT '0', + `ip` varchar(255) NOT NULL DEFAULT '', + `avatar` int(11) NOT NULL DEFAULT '0', + `account` varbinary(255) DEFAULT NULL, + `logintime` bigint(20) NOT NULL DEFAULT '0', + `loginip` varbinary(255) DEFAULT NULL, + PRIMARY KEY (`userid`), + UNIQUE KEY `usernum` (`usernum`), + KEY `ip` (`ip`), + KEY `loginip` (`loginip`), + KEY `account` (`account`) +) ENGINE=InnoDB AUTO_INCREMENT=7379773 DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; diff --git a/src/schemas/ntbb-userstats.sql b/src/schemas/ntbb-userstats.sql new file mode 100644 index 0000000..945c3ff --- /dev/null +++ b/src/schemas/ntbb-userstats.sql @@ -0,0 +1,14 @@ +-- +-- Table structure for table `ntbb_userstats` +-- + +DROP TABLE IF EXISTS `ntbb_userstats`; +CREATE TABLE `ntbb_userstats` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `serverid` varchar(255) NOT NULL, + `date` bigint(20) NOT NULL, + `usercount` int(11) NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `serverid` (`serverid`), + KEY `date` (`date`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/src/schemas/ntbb-userstatshistory.sql b/src/schemas/ntbb-userstatshistory.sql new file mode 100644 index 0000000..ed9bb0f --- /dev/null +++ b/src/schemas/ntbb-userstatshistory.sql @@ -0,0 +1,15 @@ +-- +-- Table structure for table `ntbb_userstatshistory` +-- + +CREATE TABLE `ntbb_userstatshistory` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `date` bigint(20) NOT NULL, + `usercount` int(11) NOT NULL, + `programid` enum('showdown','po') NOT NULL DEFAULT 'showdown', + PRIMARY KEY (`id`), + KEY `date` (`date`), + KEY `usercount` (`usercount`), + KEY `programid` (`programid`), + KEY `maxusers` (`programid`,`usercount`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/src/schemas/ntbb_sessions.sql b/src/schemas/ntbb_sessions.sql new file mode 100644 index 0000000..6ddf106 --- /dev/null +++ b/src/schemas/ntbb_sessions.sql @@ -0,0 +1,41 @@ +-- Database: showdown + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `ntbb_sessions` +-- + +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE IF NOT EXISTS `ntbb_sessions` ( + `session` bigint(20) NOT NULL AUTO_INCREMENT, + `sid` varchar(255) NOT NULL, + `userid` varchar(255) NOT NULL, + `time` int(11) NOT NULL, + `timeout` int(11) NOT NULL, + `ip` varchar(255) NOT NULL, + PRIMARY KEY (`session`), + KEY `userid` (`userid`), + KEY `sid` (`sid`), + KEY `timeout` (`timeout`) +) ENGINE=InnoDB AUTO_INCREMENT=1700523 DEFAULT CHARSET=utf8; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; diff --git a/src/schemas/ps_prepreplays.sql b/src/schemas/ps_prepreplays.sql new file mode 100644 index 0000000..54a1918 --- /dev/null +++ b/src/schemas/ps_prepreplays.sql @@ -0,0 +1,26 @@ +-- Database: ps_replays +-- Generation Time: 2019-06-21 16:24:15.4930 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +CREATE TABLE `ps_prepreplays` ( + `id` varbinary(255) NOT NULL DEFAULT '', + `p1` varchar(45) NOT NULL DEFAULT '', + `p2` varchar(45) NOT NULL, + `format` varchar(45) NOT NULL, + `private` tinyint(1) NOT NULL, + `loghash` varbinary(255) NOT NULL, + `inputlog` mediumtext, + `rating` int(11) NOT NULL DEFAULT '0', + `uploadtime` bigint(20) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=TokuDB DEFAULT CHARSET=utf8mb4; diff --git a/src/schemas/ps_replays.sql b/src/schemas/ps_replays.sql new file mode 100644 index 0000000..e34e34e --- /dev/null +++ b/src/schemas/ps_replays.sql @@ -0,0 +1,26 @@ +-- +-- Table structure for table `ps_replays` +-- + +CREATE TABLE `ps_replays` ( + `id` varbinary(255) NOT NULL DEFAULT '', + `p1` varchar(45) NOT NULL, + `p2` varchar(45) NOT NULL, + `format` varchar(45) NOT NULL, + `log` mediumtext NOT NULL, + `inputlog` mediumtext, + `uploadtime` bigint(20) NOT NULL, + `views` int(11) NOT NULL DEFAULT '0', + `p1id` varbinary(45) NOT NULL, + `p2id` varbinary(45) NOT NULL, + `formatid` varbinary(45) NOT NULL, + `rating` int(11) NOT NULL DEFAULT '0', + `private` tinyint(1) NOT NULL, + `password` varchar(31) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `p1` (`private`,`p1id`,`uploadtime`), + KEY `p2` (`private`,`p2id`,`uploadtime`), + KEY `top` (`private`,`formatid`,`rating`), + KEY `format` (`private`,`formatid`,`uploadtime`), + KEY `recent` (`private`,`uploadtime`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/src/test/main.ts b/src/test/main.ts new file mode 100644 index 0000000..c5f370b --- /dev/null +++ b/src/test/main.ts @@ -0,0 +1,15 @@ +/** + * Test setup. + */ +import {start} from './mysql'; +import * as fs from 'fs'; +import * as path from 'path'; + +before(async () => { + const files = fs.readdirSync(path.resolve(__dirname, '../')).map( + f => path.resolve(__dirname, '../schemas', f) + ); + await start({ + startupFiles: files, + }); +}); diff --git a/src/test/mysql.ts b/src/test/mysql.ts new file mode 100644 index 0000000..41407fc --- /dev/null +++ b/src/test/mysql.ts @@ -0,0 +1,82 @@ +// Borrowed from chaos, all credit to him + +import * as tc from 'testcontainers'; +import * as path from 'path'; +import {Config} from '../config-loader'; + +/* HACK +Similar hack to postgresql. + */ +class MySQLReadyHack extends RegExp { + seenInit: boolean; + + constructor() { + super(""); + this.seenInit = false; + } + + test(line: string) { + if (line.includes("MySQL init process done. Ready for start up.")) { + this.seenInit = true; + return false; + } else if (this.seenInit && line.includes("mysqld: ready for connections.")) { + return true; + } else { + return false; + } + } +} + +export class StartedMysqlContainer { + constructor( + private container: tc.StartedTestContainer, + public connectionInfo = { + address: { + host: container.getHost(), + port: container.getMappedPort(3306), + }, + user: 'test', + database: 'test', + } + ) { + (Config.mysql as any) = connectionInfo; + } + + stop() { + return this.container.stop(); + } +} + +export type StartupFile = {src: string; dst: string} | string; + +export interface StartOptions { + version?: number; + startupFiles?: StartupFile[]; +} + +function toTcCopyFile(file: StartupFile) { + let src, dst; + if (typeof file === 'string') { + src = file; + dst = path.basename(file); + } else { + src = file.src; + dst = file.dst; + } + return { + source: src, + target: path.join('/docker-entrypoint-initdb.d/', dst), + }; +} + +export async function start(options: StartOptions = {}) { + const version = options.version ?? "5.7"; + const startupFiles = options.startupFiles ?? []; + const container = await new tc.GenericContainer(`mysql:${version}`) + .withExposedPorts(3306) + .withEnvironment({MYSQL_ALLOW_EMPTY_PASSWORD: "yes", MYSQL_DATABASE: "xenforo"}) + .withWaitStrategy(tc.Wait.forLogMessage(new MySQLReadyHack)) + .withCopyFilesToContainer(startupFiles.map(toTcCopyFile)) + .start(); + return new StartedMysqlContainer(container); +} diff --git a/src/test/test-utils.ts b/src/test/test-utils.ts index 4ced91c..1eb8243 100644 --- a/src/test/test-utils.ts +++ b/src/test/test-utils.ts @@ -67,4 +67,4 @@ export async function randomBytes(size = 128) { return new Promise((resolve, reject) => { crypto.randomBytes(size, (err, buffer) => err ? reject(err) : resolve(buffer.toString('hex'))); }); -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index c48150b..6ee6858 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,8 @@ }, "types": ["node"], "include": [ - "./src/**/*", "scripts/run.js", + "./src/**/*", + "./config/**/*", ], "exclude": ["./.dist/**/*", "./scripts/**/*"], }