Skip to content
This repository has been archived by the owner on Dec 19, 2024. It is now read-only.

Commit

Permalink
Merge pull request #245 from XWasHere/dev/0.15.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Josh Moore authored Nov 2, 2023
2 parents 1ffcc02 + 41d683b commit 347b80e
Show file tree
Hide file tree
Showing 13 changed files with 859 additions and 238 deletions.
29 changes: 21 additions & 8 deletions backend/UserConfig.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UserConfiguration, UserConfigTypeChecker } from 'ass';
import { UserConfiguration, UserConfigTypeChecker, PostgresConfiguration } from 'ass';

import fs from 'fs-extra';
import { path } from '@tycrek/joint';
Expand Down Expand Up @@ -56,7 +56,10 @@ const Checkers: UserConfigTypeChecker = {
host: basicStringChecker,
user: basicStringChecker,
password: basicStringChecker,
database: basicStringChecker
database: basicStringChecker,
},
postgres: {
port: (val) => numChecker(val) && val >= 1 && val <= 65535
}
},

Expand Down Expand Up @@ -102,12 +105,22 @@ export class UserConfig {
if (!Checkers.s3.credentials.secretKey(config.s3.credentials.secretKey)) throw new Error('Invalid S3 Secret key');
}

// * Optional SQL config(s) (Currently only checks MySQL)
if (config.sql?.mySql != null) {
if (!Checkers.sql.mySql.host(config.sql.mySql.host)) throw new Error('Invalid MySql Host');
if (!Checkers.sql.mySql.user(config.sql.mySql.user)) throw new Error('Invalid MySql User');
if (!Checkers.sql.mySql.password(config.sql.mySql.password)) throw new Error('Invalid MySql Password');
if (!Checkers.sql.mySql.database(config.sql.mySql.database)) throw new Error('Invalid MySql Database');
// * Optional database config(s)
if (config.database != null) {
// these both have the same schema so we can just check both
if (config.database.kind == 'mysql' || config.database.kind == 'postgres') {
if (config.database.options != undefined) {
if (!Checkers.sql.mySql.host(config.database.options.host)) throw new Error('Invalid database host');
if (!Checkers.sql.mySql.user(config.database.options.user)) throw new Error('Invalid databse user');
if (!Checkers.sql.mySql.password(config.database.options.password)) throw new Error('Invalid database password');
if (!Checkers.sql.mySql.database(config.database.options.database)) throw new Error('Invalid database');
if (config.database.kind == 'postgres') {
if (!Checkers.sql.postgres.port((config.database.options as PostgresConfiguration).port)) {
throw new Error("Invalid database port");
}
}
} else throw new Error('Database options missing');
}
}

// * optional rate limit config
Expand Down
33 changes: 26 additions & 7 deletions backend/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ import { path, isProd } from '@tycrek/joint';
import { epcss } from '@tycrek/express-postcss';

import { log } from './log';
import { ensureFiles, get } from './data';
import { get } from './data';
import { UserConfig } from './UserConfig';
import { MySql } from './sql/mysql';
import { DBManager } from './sql/database';
import { JSONDatabase } from './sql/json';
import { MySQLDatabase } from './sql/mysql';
import { PostgreSQLDatabase } from './sql/postgres';
import { buildFrontendRouter } from './routers/_frontend';

/**
Expand Down Expand Up @@ -76,8 +79,9 @@ async function main() {

App.pkgVersion = pkg.version;

// Ensure data files exist
await ensureFiles();
// Ensure data directory exists
log.debug('Checking data dir')
await fs.ensureDir(path.join('.ass-data'));

// Set default server configuration
const serverConfig: ServerConfiguration = {
Expand Down Expand Up @@ -112,9 +116,24 @@ async function main() {
.catch((err) => (err.code && err.code === 'ENOENT' ? {} : console.error(err), resolve(void 0))));

// If user config is ready, try to configure SQL
if (UserConfig.ready && UserConfig.config.sql?.mySql != null)
try { await MySql.configure(); }
catch (err) { throw new Error(`Failed to configure SQL`); }
if (UserConfig.ready && UserConfig.config.database != null) {
try {
switch (UserConfig.config.database?.kind) {
case 'json':
await DBManager.use(new JSONDatabase());
break;
case 'mysql':
await DBManager.use(new MySQLDatabase());
break;
case 'postgres':
await DBManager.use(new PostgreSQLDatabase());
break;
}
} catch (err) { throw new Error(`Failed to configure SQL`); }
} else { // default to json database
log.debug('DB not set! Defaulting to JSON');
await DBManager.use(new JSONDatabase());
}

// Set up Express
const app = express();
Expand Down
169 changes: 15 additions & 154 deletions backend/data.ts
Original file line number Diff line number Diff line change
@@ -1,169 +1,34 @@
import { AssFile, AssUser, NID, FilesSchema, UsersSchema } from 'ass';

import fs from 'fs-extra';
import { path } from '@tycrek/joint';
import { AssFile, AssUser, NID } from 'ass';

import { log } from './log';
import { nanoid } from './generators';
import { UserConfig } from './UserConfig';
import { MySql } from './sql/mysql';
import { DBManager } from './sql/database';

/**
* Switcher type for exported functions
*/
type DataSector = 'files' | 'users';

/**
* Absolute filepaths for JSON data files
* database kind -> name mapping
*/
const PATHS = {
files: path.join('.ass-data/files.json'),
users: path.join('.ass-data/users.json')
};

const bothWriter = async (files: FilesSchema, users: UsersSchema) => {
await fs.writeJson(PATHS.files, files, { spaces: '\t' });
await fs.writeJson(PATHS.users, users, { spaces: '\t' });
const DBNAMES = {
'mysql': 'MySQL',
'postgres': 'PostgreSQL',
'json': 'JSON'
};

/**
* Creates a JSON file with a given empty data template
*/
const createEmptyJson = (filepath: string, emptyData: any): Promise<void> => new Promise(async (resolve, reject) => {
try {
if (!(await fs.pathExists(filepath))) {
await fs.ensureFile(filepath);
await fs.writeJson(filepath, emptyData, { spaces: '\t' });
}
resolve(void 0);
} catch (err) {
reject(err);
}
});

/**
* Ensures the data files exist and creates them if required
*/
export const ensureFiles = (): Promise<void> => new Promise(async (resolve, reject) => {
log.debug('Checking data files');

try {
// Create data directory
await fs.ensureDir(path.join('.ass-data'));

// * Default files.json
await createEmptyJson(PATHS.files, {
files: {},
useSql: false,
meta: {}
} as FilesSchema);

// * Default users.json
await createEmptyJson(PATHS.users, {
tokens: [],
users: {},
cliKey: nanoid(32),
useSql: false,
meta: {}
} as UsersSchema);

log.debug('Data files exist');
resolve();
} catch (err) {
log.error('Failed to verify existence of data files');
reject(err);
}
});

export const setDataModeToSql = (): Promise<void> => new Promise(async (resolve, reject) => {
log.debug('Setting data mode to SQL');

// Main config check
if (!UserConfig.ready || !UserConfig.config.sql?.mySql) return reject(new Error('MySQL not configured'));
const mySqlConf = UserConfig.config.sql.mySql;

// Read data files
const [files, users]: [FilesSchema, UsersSchema] = await Promise.all([fs.readJson(PATHS.files), fs.readJson(PATHS.users)]);

// 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;

// Set the vars
files.useSql = issue == null;
users.useSql = issue == null;

// Write data & return
await bothWriter(files, users);
(issue) ? reject(new Error(issue)) : resolve(void 0);
});

export const put = (sector: DataSector, key: NID, data: AssFile | AssUser): Promise<void> => new Promise(async (resolve, reject) => {
try {
const useSql = MySql.ready;

if (sector === 'files') {

// * 1: Save as files (image, video, etc)
data = data as AssFile;
if (!useSql) {

// ? Local JSON
const filesJson = await fs.readJson(PATHS.files) as FilesSchema;

// Check if key already exists
if (filesJson.files[key] != null) return reject(new Error(`File key ${key} already exists`));

// Otherwise add the data
filesJson.files[key] = data;

// Also save the key to the users file
const usersJson = await fs.readJson(PATHS.users) as UsersSchema;
// todo: uncomment this once users are implemented
// usersJson.users[data.uploader].files.push(key);

// Save the files
await bothWriter(filesJson, usersJson);
} else {

// ? SQL
if (!(await MySql.get('assfiles', key))) await MySql.put('assfiles', key, data);
else return reject(new Error(`File key ${key} already exists`));

// todo: modify users SQL files property
}
await DBManager.put('assfiles', key, data as AssFile);
} else {

// * 2: Save as users
data = data as AssUser;
if (!useSql) {

// ? Local JSON
const usersJson = await fs.readJson(PATHS.users) as UsersSchema;

// Check if key already exists
if (usersJson.users[key] != null) return reject(new Error(`User key ${key} already exists`));

// Otherwise add the data
usersJson.users[key] = data;

await fs.writeJson(PATHS.users, usersJson, { spaces: '\t' });
} else {

// ? SQL
if (!(await MySql.get('assusers', key))) await MySql.put('assusers', key, data);
else return reject(new Error(`User key ${key} already exists`));
}
await DBManager.put('assusers', key, data as AssUser);
}

log.info(`PUT ${sector} data`, `using ${useSql ? 'SQL' : 'local JSON'}`, key);
log.info(`PUT ${sector} data`, `using ${DBNAMES[UserConfig.config.database?.kind ?? 'json']}`, key);
resolve(void 0);
} catch (err) {
reject(err);
Expand All @@ -172,22 +37,18 @@ 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)
: (await fs.readJson(PATHS[sector]))[sector][key];
const data: AssFile | AssUser | undefined = await DBManager.get(sector === 'files' ? 'assfiles' : 'assusers', key) as AssFile | AssUser | undefined
(!data) ? resolve(false) : resolve(data);
} catch (err) {
reject(err);
}
});

export const getAll = (sector: DataSector): Promise<{ [key: string]: AssFile | AssUser } | false> => new Promise(async (resolve, reject) => {
export const getAll = (sector: DataSector): Promise<{ [key: string]: AssFile | AssUser }> => new Promise(async (resolve, reject) => {
try {
const data: { [key: string]: AssFile | AssUser } | undefined = (MySql.ready)
// todo: fix MySQL
? (await MySql.getAll(sector === 'files' ? 'assfiles' : 'assusers') as /* AssFile[] | AssUser[] | */ undefined)
: (await fs.readJson(PATHS[sector]))[sector];
(!data) ? resolve(false) : resolve(data);
// todo: fix MySQL
const data: { [key: string]: AssFile | AssUser } = await DBManager.getAll(sector === 'files' ? 'assfiles' : 'assusers') as /* AssFile[] | AssUser[] | */ {}
resolve(data);
} catch (err) {
reject(err);
}
Expand Down
28 changes: 21 additions & 7 deletions backend/routers/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import * as data from '../data';
import { log } from '../log';
import { nanoid } from '../generators';
import { UserConfig } from '../UserConfig';
import { MySql } from '../sql/mysql';
import { rateLimiterMiddleware, setRateLimiter } from '../ratelimit';
import { DBManager } from '../sql/database';
import { JSONDatabase } from '../sql/json';
import { MySQLDatabase } from '../sql/mysql';
import { PostgreSQLDatabase } from '../sql/postgres';

const router = Router({ caseSensitive: true });

Expand All @@ -26,14 +29,25 @@ router.post('/setup', BodyParserJson(), async (req, res) => {
// Save config
await UserConfig.saveConfigFile();

// Set data storage (not files) to SQL if required
if (UserConfig.config.sql?.mySql != null)
await Promise.all([MySql.configure(), data.setDataModeToSql()]);
// set up new databases
if (UserConfig.config.database) {
switch (UserConfig.config.database.kind) {
case 'json':
await DBManager.use(new JSONDatabase());
break;
case 'mysql':
await DBManager.use(new MySQLDatabase());
break;
case 'postgres':
await DBManager.use(new PostgreSQLDatabase());
break;
}
}

// set rate limits
if (UserConfig.config.rateLimit?.api) setRateLimiter('api', UserConfig.config.rateLimit.api);
if (UserConfig.config.rateLimit?.login) setRateLimiter('login', UserConfig.config.rateLimit.login);
if (UserConfig.config.rateLimit?.upload) setRateLimiter('upload', UserConfig.config.rateLimit.upload);
if (UserConfig.config.rateLimit?.api) setRateLimiter('api', UserConfig.config.rateLimit.api);
if (UserConfig.config.rateLimit?.login) setRateLimiter('login', UserConfig.config.rateLimit.login);
if (UserConfig.config.rateLimit?.upload) setRateLimiter('upload', UserConfig.config.rateLimit.upload);;

log.success('Setup', 'completed');

Expand Down
Loading

0 comments on commit 347b80e

Please sign in to comment.