add: database class interface

pull/245/head
xwashere 11 months ago
parent c648533469
commit 5620d0bed3
No known key found for this signature in database
GPG Key ID: 042F8BFA1B0EF93B

@ -11,8 +11,9 @@ import { epcss } from '@tycrek/express-postcss';
import { log } from './log';
import { ensureFiles, get } from './data';
import { UserConfig } from './UserConfig';
import { MySql } from './sql/mysql';
import { MySQLDatabase } from './db/mysql';
import { buildFrontendRouter } from './routers/_frontend';
import { DBManager } from './db/database';
/**
* Top-level metadata exports
@ -113,7 +114,7 @@ async function main() {
// If user config is ready, try to configure SQL
if (UserConfig.ready && UserConfig.config.sql?.mySql != null)
try { await MySql.configure(); }
try { await DBManager.use(new MySQLDatabase()); }
catch (err) { throw new Error(`Failed to configure SQL`); }
// Set up Express

@ -6,7 +6,7 @@ import { path } from '@tycrek/joint';
import { log } from './log';
import { nanoid } from './generators';
import { UserConfig } from './UserConfig';
import { MySql } from './sql/mysql';
import { DBManager } from './db/database';
/**
* Switcher type for exported functions
@ -107,7 +107,7 @@ export const setDataModeToSql = (): Promise<void> => new Promise(async (resolve,
export const put = (sector: DataSector, key: NID, data: AssFile | AssUser): Promise<void> => new Promise(async (resolve, reject) => {
try {
const useSql = MySql.ready;
const useSql = DBManager.ready;
if (sector === 'files') {
@ -134,7 +134,7 @@ export const put = (sector: DataSector, key: NID, data: AssFile | AssUser): Prom
} else {
// ? SQL
if (!(await MySql.get('assfiles', key))) await MySql.put('assfiles', key, data);
if (!(await DBManager.get('assfiles', key))) await DBManager.put('assfiles', key, data);
else return reject(new Error(`File key ${key} already exists`));
// todo: modify users SQL files property
@ -158,7 +158,7 @@ export const put = (sector: DataSector, key: NID, data: AssFile | AssUser): Prom
} else {
// ? SQL
if (!(await MySql.get('assusers', key))) await MySql.put('assusers', key, data);
if (!(await DBManager.get('assusers', key))) await DBManager.put('assusers', key, data);
else return reject(new Error(`User key ${key} already exists`));
}
}
@ -172,8 +172,8 @@ export const put = (sector: DataSector, key: NID, data: AssFile | AssUser): Prom
export const get = (sector: DataSector, key: NID): Promise<AssFile | AssUser | false> => new Promise(async (resolve, reject) => {
try {
const data: AssFile | AssUser | undefined = (MySql.ready)
? (await MySql.get(sector === 'files' ? 'assfiles' : 'assusers', key) as AssFile | AssUser | undefined)
const data: AssFile | AssUser | undefined = (DBManager.ready)
? (await DBManager.get(sector === 'files' ? 'assfiles' : 'assusers', key) as AssFile | AssUser | undefined)
: (await fs.readJson(PATHS[sector]))[sector][key];
(!data) ? resolve(false) : resolve(data);
} catch (err) {
@ -183,9 +183,9 @@ export const get = (sector: DataSector, key: NID): Promise<AssFile | AssUser | f
export const getAll = (sector: DataSector): Promise<{ [key: string]: AssFile | AssUser } | false> => new Promise(async (resolve, reject) => {
try {
const data: { [key: string]: AssFile | AssUser } | undefined = (MySql.ready)
const data: { [key: string]: AssFile | AssUser } | undefined = (DBManager.ready)
// todo: fix MySQL
? (await MySql.getAll(sector === 'files' ? 'assfiles' : 'assusers') as /* AssFile[] | AssUser[] | */ undefined)
? (await DBManager.getAll(sector === 'files' ? 'assfiles' : 'assusers') as /* AssFile[] | AssUser[] | */ [])
: (await fs.readJson(PATHS[sector]))[sector];
(!data) ? resolve(false) : resolve(data);
} catch (err) {

@ -0,0 +1,105 @@
import { AssFile, AssUser, NID, UploadToken } from "ass";
export type DatabaseValue = AssFile | AssUser | UploadToken;
export type DatabaseTable = 'assfiles' | 'assusers' | 'asstokens';
/**
* interface for database classes
*/
export interface Database {
/**
* preform database initialization tasks
*/
open(): Promise<void>;
/**
* preform database suspension tasks
*/
close(): Promise<void>;
/**
* set up database
*/
configure(): Promise<void>;
/**
* put a value in the database
*/
put(table: DatabaseTable, key: NID, data: DatabaseValue): Promise<void>;
/**
* get a value from the database
*/
get(table: DatabaseTable, key: NID): Promise<DatabaseValue | undefined>;
/**
* get all values from the database
*/
getAll(table: DatabaseTable): Promise<DatabaseValue[]>;
}
export class DBManager {
private static _db: Database;
private static _dbReady: boolean = false;
public static get ready() {
return this._dbReady;
}
static {
process.on('exit', () => {
if (DBManager._db) DBManager._db.close();
});
}
/**
* activate a database
*/
public static use(db: Database): Promise<void> {
return new Promise(async (resolve, reject) => {
if (this._db != undefined) {
await this._db.close();
this._dbReady = false;
}
this._db = db;
await this._db.open();
await this._db.configure();
this._dbReady = true;
resolve();
});
}
public static configure(): Promise<void> {
if (this._db && this._dbReady) {
return this._db.configure();
} else throw new Error("No database active");
}
/**
* put a value in the database
*/
public static put(table: DatabaseTable, key: NID, data: DatabaseValue): Promise<void> {
if (this._db && this._dbReady) {
return this._db.put(table, key, data);
} else throw new Error("No database active");
}
/**
* get a value from the database
*/
public static get(table: DatabaseTable, key: NID): Promise<DatabaseValue | undefined> {
if (this._db && this._dbReady) {
return this._db.get(table, key);
} else throw new Error("No database active");
}
/**
* get all values from the database
*/
public static getAll(table: DatabaseTable): Promise<DatabaseValue[]> {
if (this._db && this._dbReady) {
return this._db.getAll(table);
} else throw new Error("No database active");
}
}

@ -4,21 +4,20 @@ import mysql, { Pool } from 'mysql2/promise';
import { log } from '../log';
import { UserConfig } from '../UserConfig';
import { Database, DatabaseTable, DatabaseValue } from './database';
type TableNamesType = 'assfiles' | 'assusers' | 'asstokens';
export class MySQLDatabase implements Database {
private _pool: Pool;
export class MySql {
private static _pool: Pool;
private static _ready: boolean = false;
public static get ready() { return MySql._ready; }
private _ready: boolean = false;
public get ready() { return this._ready; }
/**
* Quick function for creating a simple JSON table
*/
private static _tableManager(mode: 'create' | 'drop', name: string, schema = '( NanoID varchar(255), Data JSON )'): Promise<void> {
private _tableManager(mode: 'create' | 'drop', name: string, schema = '( NanoID varchar(255), Data JSON )'): Promise<void> {
return new Promise((resolve, reject) =>
MySql._pool.query(
this._pool.query(
mode === 'create'
? `CREATE TABLE ${name} ${schema};`
: `DROP TABLE ${name};`)
@ -26,31 +25,33 @@ export class MySql {
.catch((err) => reject(err)));
}
public open() { return Promise.resolve(); }
public close() { return Promise.resolve(); }
/**
* Build the MySQL client and create the tables
*/
public static configure(): Promise<void> {
public configure(): Promise<void> {
return new Promise(async (resolve, reject) => {
try {
// Config check
if (!UserConfig.ready) throw new Error('User configuration not ready');
if (!UserConfig.config.sql?.mySql) throw new Error('MySQL configuration missing');
// Create the pool
MySql._pool = mysql.createPool(UserConfig.config.sql.mySql);
this._pool = mysql.createPool(UserConfig.config.sql.mySql);
// Check if the pool is usable
const [rowz, _fields] = await MySql._pool.query(`SHOW FULL TABLES WHERE Table_Type LIKE 'BASE TABLE';`);
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([
MySql._tableManager('create', 'assfiles'),
MySql._tableManager('create', 'assusers'),
MySql._tableManager('create', 'asstokens')
this._tableManager('create', 'assfiles'),
this._tableManager('create', 'assusers'),
this._tableManager('create', 'asstokens')
]);
log.success('MySQL', 'Tables created').callback(resolve);
} else {
@ -61,7 +62,7 @@ export class MySql {
// Check which tables ACTUALLY do exist
for (let row of rows_tableData) {
const table = row[`Tables_in_${UserConfig.config.sql!.mySql!.database}`
] as TableNamesType;
] as DatabaseTable;
if (table === 'assfiles') tablesExist.files = true;
if (table === 'assusers') tablesExist.users = true;
if (table === 'asstokens') tablesExist.tokens = true;
@ -69,9 +70,9 @@ export class MySql {
}
// Mini-function for creating a one-off table
const createOneTable = async (name: TableNamesType) => {
const createOneTable = async (name: DatabaseTable) => {
log.warn('MySQL', `Table '${name}' missing, creating`);
await MySql._tableManager('create', name);
await this._tableManager('create', name);
log.success('MySQL', `Table '${name}' created`);
}
@ -88,7 +89,7 @@ export class MySql {
// Hopefully we are ready
if (tablesExist.files && tablesExist.users)
log.info('MySQL', 'Tables exist, ready').callback(() => {
MySql._ready = true;
this._ready = true;
resolve(void 0);
});
else throw new Error('Table(s) missing!');
@ -101,26 +102,26 @@ export class MySql {
});
}
public static put(table: TableNamesType, key: NID, data: UploadToken | AssFile | AssUser): Promise<void> {
public put(table: DatabaseTable, key: NID, data: DatabaseValue): Promise<void> {
return new Promise(async (resolve, reject) => {
if (!MySql._ready) return reject(new Error('MySQL not ready'));
if (!this._ready) return reject(new Error('MySQL not ready'));
const query = `
INSERT INTO ${table} ( NanoID, Data )
VALUES ('${key}', '${JSON.stringify(data)}');
`;
return MySql._pool.query(query)
return this._pool.query(query)
.then(() => resolve(void 0))
.catch((err) => reject(err));
});
}
public static get(table: TableNamesType, key: NID): Promise<UploadToken | AssFile | AssUser | undefined> {
public get(table: DatabaseTable, key: NID): Promise<DatabaseValue | undefined> {
return new Promise(async (resolve, reject) => {
try {
// Run query
const [rowz, _fields] = await MySql._pool.query(`SELECT Data FROM ${table} WHERE NanoID = '${key}';`);
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 });
@ -133,11 +134,11 @@ VALUES ('${key}', '${JSON.stringify(data)}');
}
// todo: unknown if this works
public static getAll(table: TableNamesType): Promise<UploadToken | AssFile | AssUser | undefined> {
public getAll(table: DatabaseTable): Promise<DatabaseValue[]> {
return new Promise(async (resolve, reject) => {
try {
// Run query // ! this may not work as expected
const [rowz, _fields] = await MySql._pool.query(`SELECT Data FROM ${table}`);
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 }[]);
@ -145,7 +146,7 @@ VALUES ('${key}', '${JSON.stringify(data)}');
// console.log(rows);
// aaaaaaaaaaaa
resolve(undefined);
resolve([]);
} catch (err) {
reject(err);
}

@ -7,8 +7,8 @@ import * as data from '../data';
import { log } from '../log';
import { nanoid } from '../generators';
import { UserConfig } from '../UserConfig';
import { MySql } from '../sql/mysql';
import { rateLimiterMiddleware } from '../ratelimit';
import { DBManager } from '../db/database';
const router = Router({ caseSensitive: true });
@ -28,7 +28,7 @@ router.post('/setup', BodyParserJson(), async (req, res) => {
// Set data storage (not files) to SQL if required
if (UserConfig.config.sql?.mySql != null)
await Promise.all([MySql.configure(), data.setDataModeToSql()]);
await Promise.all([DBManager.configure(), data.setDataModeToSql()]);
log.success('Setup', 'completed');

Loading…
Cancel
Save