import { AssFile , AssUser , NID , UploadToken } from 'ass' ;
import mysql , { Pool } from 'mysql2/promise' ;
import { log } from '../log' ;
import { UserConfig } from '../UserConfig' ;
import { Database , DatabaseTable , DatabaseValue } from './database' ;
export class MySQLDatabase implements Database {
private _pool : Pool ;
private _ready : boolean = false ;
public get ready() { return this . _ready ; }
/ * *
* Quick function for creating a simple JSON table
* /
private _tableManager ( mode : 'create' | 'drop' , name : string , schema = '( NanoID varchar(255), Data JSON )' ) : Promise < void > {
return new Promise ( ( resolve , reject ) = >
this . _pool . query (
mode === 'create'
? ` CREATE TABLE ${ name } ${ schema } ; `
: ` DROP TABLE ${ name } ; ` )
. then ( ( ) = > resolve ( ) )
. catch ( ( err ) = > reject ( err ) ) ) ;
}
/ * *
* validate the mysql config
* /
private _validateConfig ( ) : string | undefined {
// make sure the configuration exists
if ( ! UserConfig . ready ) return 'User configuration not ready' ;
if ( typeof UserConfig . config . database != 'object' ) return 'MySQL configuration missing' ;
if ( UserConfig . config . database . kind != "mysql" ) return 'Database not set to MySQL, but MySQL is in use, something has gone terribly wrong' ;
if ( typeof UserConfig . config . database . options != 'object' ) return 'MySQL configuration missing' ;
let mySqlConf = UserConfig . config . database . options ;
// Check the MySQL configuration
const checker = ( val : string ) = > val != null && val !== '' ;
const issue =
! checker ( mySqlConf . host ) ? 'Missing MySQL Host'
: ! checker ( mySqlConf . user ) ? 'Missing MySQL User'
: ! checker ( mySqlConf . password ) ? 'Missing MySQL Password'
: ! checker ( mySqlConf . database ) ? 'Missing MySQL Database'
// ! Blame VS Code for this weird indentation
: undefined ;
return issue ;
}
public open ( ) { return Promise . resolve ( ) ; }
public close() { return Promise . resolve ( ) ; }
/ * *
* Build the MySQL client and create the tables
* /
public configure ( ) : Promise < void > {
return new Promise ( async ( resolve , reject ) = > {
try {
// Config check
let configError = this . _validateConfig ( ) ;
if ( configError ) throw new Error ( configError ) ;
// Create the pool
this . _pool = mysql . createPool ( UserConfig . config . database ! . options ! ) ;
// Check if the pool is usable
const [ rowz , _fields ] = await this . _pool . query ( ` SHOW FULL TABLES WHERE Table_Type LIKE 'BASE TABLE'; ` ) ;
const rows_tableData = rowz as unknown as { [ key : string ] : string } [ ] ;
// Create tables if needed
if ( rows_tableData . length === 0 ) {
log . warn ( 'MySQL' , 'Tables do not exist, creating' ) ;
await Promise . all ( [
this . _tableManager ( 'create' , 'assfiles' ) ,
this . _tableManager ( 'create' , 'assusers' ) ,
this . _tableManager ( 'create' , 'asstokens' )
] ) ;
log . success ( 'MySQL' , 'Tables created' ) . callback ( resolve ) ;
} else {
// There's at least one row, do further checks
const tablesExist = { files : false , users : false , tokens : false } ;
// Check which tables ACTUALLY do exist
for ( let row of rows_tableData ) {
const table = row [ ` Tables_in_ ${ UserConfig . config . database ! . options ! . database } `
] as DatabaseTable ;
if ( table === 'assfiles' ) tablesExist . files = true ;
if ( table === 'assusers' ) tablesExist . users = true ;
if ( table === 'asstokens' ) tablesExist . tokens = true ;
// ! Don't use `= table === ''` because this is a loop
}
// Mini-function for creating a one-off table
const createOneTable = async ( name : DatabaseTable ) = > {
log . warn ( 'MySQL' , ` Table ' ${ name } ' missing, creating ` ) ;
await this . _tableManager ( 'create' , name ) ;
log . success ( 'MySQL' , ` Table ' ${ name } ' created ` ) ;
}
// Check & create tables
if ( ! tablesExist . files ) await createOneTable ( 'assfiles' ) ;
if ( ! tablesExist . users ) await createOneTable ( 'assusers' ) ;
if ( ! tablesExist . users ) await createOneTable ( 'asstokens' ) ;
// ! temp: drop tables for testing
/ * a w a i t M y S q l . _ t a b l e M a n a g e r ( ' d r o p ' , ' a s s f i l e s ' ) ;
await MySql . _tableManager ( 'drop' , 'assusers' ) ;
log . debug ( 'Table dropped' ) ; * /
// Hopefully we are ready
if ( tablesExist . files && tablesExist . users )
log . info ( 'MySQL' , 'Tables exist, ready' ) . callback ( ( ) = > {
this . _ready = true ;
resolve ( void 0 ) ;
} ) ;
else throw new Error ( 'Table(s) missing!' ) ;
}
} catch ( err ) {
log . error ( 'MySQL' , 'failed to initialize' ) ;
console . error ( err ) ;
reject ( err ) ;
}
} ) ;
}
public put ( table : DatabaseTable , key : NID , data : DatabaseValue ) : Promise < void > {
return new Promise ( async ( resolve , reject ) = > {
if ( ! this . _ready ) return reject ( new Error ( 'MySQL not ready' ) ) ;
if ( await this . get ( table , key ) ) reject ( new Error ( ` ${ table == 'assfiles' ? 'File' : table == 'assusers' ? 'User' : 'Token' } key ${ key } already exists ` ) ) ;
const query = `
INSERT INTO $ { table } ( NanoID , Data )
VALUES ( '${key}' , '${JSON.stringify(data)}' ) ;
` ;
return this . _pool . query ( query )
. then ( ( ) = > resolve ( void 0 ) )
. catch ( ( err ) = > reject ( err ) ) ;
} ) ;
}
public get ( table : DatabaseTable , key : NID ) : Promise < DatabaseValue | undefined > {
return new Promise ( async ( resolve , reject ) = > {
try {
// Run query
const [ rowz , _fields ] = await this . _pool . query ( ` SELECT Data FROM ${ table } WHERE NanoID = ' ${ key } '; ` ) ;
// Disgustingly interpret the query results
const rows_tableData = ( rowz as unknown as { [ key : string ] : string } [ ] ) [ 0 ] as unknown as ( { Data : UploadToken | AssFile | AssUser | undefined } ) ;
resolve ( rows_tableData ? . Data ? ? undefined ) ;
} catch ( err ) {
reject ( err ) ;
}
} ) ;
}
// todo: unknown if this works
public getAll ( table : DatabaseTable ) : Promise < { [ index : string ] : DatabaseValue } > {
return new Promise ( async ( resolve , reject ) = > {
try {
// Run query // ! this may not work as expected
const [ rowz , _fields ] = await this . _pool . query ( ` SELECT Data FROM ${ table } ` ) ;
// Interpret results this is pain
const rows = ( rowz as unknown as { [ key : string ] : string } [ ] ) ;
// console.log(rows);
// aaaaaaaaaaaa
resolve ( { } ) ;
} catch ( err ) {
reject ( err ) ;
}
} ) ;
}
}