From 2a3d3add6ca2b044d1dcce730168251d4fca1746 Mon Sep 17 00:00:00 2001 From: Aroop Roelofs Date: Thu, 3 Aug 2023 12:42:30 +0200 Subject: [PATCH] Reorganize a bunch of stuff --- {common => core}/cache.ts | 36 +++++++++++++++++------------- {common => core}/configure.ts | 0 discord/dispatchers/event.ts | 10 ++++----- discord/dispatchers/interaction.ts | 22 +++++++++--------- filesystem/file.ts | 30 +++++++++++++++++++++++++ filesystem/folder.ts | 16 +++++++++++++ logging/logger.ts | 4 ++-- tests/common/configure.test.ts | 2 +- tests/util/inflector.test.ts | 2 +- util/folder-exists.ts | 9 -------- util/is-ts.ts | 5 ----- util/tokenizer.ts | 10 --------- {util => utility}/check-source.ts | 16 +++---------- {common => utility}/cron.ts | 0 {util => utility}/inflector.ts | 10 ++++----- {util => utility}/raise.ts | 2 +- utility/text.ts | 25 +++++++++++++++++++++ {util => utility}/time-string.ts | 0 {common => utility}/time.ts | 2 +- webserver/controller/controller.ts | 8 +++---- webserver/http/response-builder.ts | 2 +- webserver/renderers/handlebars.ts | 2 +- webserver/routing/router.ts | 4 ++-- websocket/authenticator.ts | 2 +- websocket/websocket.ts | 2 +- 25 files changed, 131 insertions(+), 90 deletions(-) rename {common => core}/cache.ts (71%) rename {common => core}/configure.ts (100%) create mode 100644 filesystem/file.ts create mode 100644 filesystem/folder.ts delete mode 100644 util/folder-exists.ts delete mode 100644 util/is-ts.ts delete mode 100644 util/tokenizer.ts rename {util => utility}/check-source.ts (88%) rename {common => utility}/cron.ts (100%) rename {util => utility}/inflector.ts (97%) rename {util => utility}/raise.ts (97%) create mode 100644 utility/text.ts rename {util => utility}/time-string.ts (100%) rename {common => utility}/time.ts (96%) diff --git a/common/cache.ts b/core/cache.ts similarity index 71% rename from common/cache.ts rename to core/cache.ts index 26c63e11..151b6b15 100644 --- a/common/cache.ts +++ b/core/cache.ts @@ -1,9 +1,9 @@ -import { T as TimeString } from "../util/time-string.ts"; +import { T as TimeString } from "../utility/time-string.ts"; interface CacheItem { [key: string]: { data: unknown; - expires: Date; + expires: Date|null; } } @@ -12,65 +12,69 @@ export class Cache { /** * Add an item to the cache. - * + * * @param key * @param value - * @param expiry + * @param expiry Can be set to null for never expiring items */ - public static set(key: string, value: unknown, expiry = '+1 minute'): void { + public static set(key: string, value: unknown, expiry: string|null = '+1 minute'): void { + let expiresAt = null; + if(expiry) expiresAt = new Date(new Date().getTime() + TimeString`${expiry}`) + Cache._items[key] = { data: value, - expires: new Date(new Date().getTime() + TimeString`${expiry}`), + expires: expiresAt, }; } /** * Get an item from the cache - * + * * @param key */ public static get(key: string): unknown|null { // Return null if the item doesn't exist if(!Cache.exists(key)) return null; - + // Return null if the item expired if(Cache.expired(key)) return null; - + // Return the item's data return Cache._items[key].data; } /** * Check whether an item exists in the cache - * + * * @param key */ public static exists(key: string): boolean { return key in Cache._items; } - + /** * Check whether an item has expired - * + * * @param key */ public static expired(key: string): boolean { // If the item doesn't exist, return true if(!Cache.exists(key)) return true; - + // Check if the expiry date is before our current date - return Cache._items[key].expires < new Date(); + if(!Cache._items[key].expires) return false; + return Cache._items[key].expires! < new Date(); } /** * Remove an item from the cache - * + * * @param key */ public static remove(key: string): void { // Return if the item doesn't exist if(!Cache.exists(key)) return; - + // Delete our item delete Cache._items[key]; } diff --git a/common/configure.ts b/core/configure.ts similarity index 100% rename from common/configure.ts rename to core/configure.ts diff --git a/discord/dispatchers/event.ts b/discord/dispatchers/event.ts index 4fd19ae3..cb826a12 100644 --- a/discord/dispatchers/event.ts +++ b/discord/dispatchers/event.ts @@ -1,7 +1,7 @@ import { Logger } from "../../logging/logger.ts"; -import { folderExists } from "../../util/folder-exists.ts"; -import { isTs } from "../../util/is-ts.ts"; -import { Inflector } from "../../util/inflector.ts"; +import { Folder } from "../../filesystem/folder.ts"; +import { File } from "../../filesystem/file.ts"; +import { Inflector } from "../../utility/inflector.ts"; interface EventConfig { name: string; @@ -91,7 +91,7 @@ export class EventDispatcher { Logger.debug(`Loading events from "${dir}"...`); // Make sure the interactions directory exists - if(!folderExists(dir)) { + if(!await new Folder(dir).exists()) { Logger.warning(`"${dir}" does not exist, no events to load.`); return; } @@ -102,7 +102,7 @@ export class EventDispatcher { // Load all interactions const promiseQueue: Promise[] = []; for await(const file of files) { - if(!isTs(file.name)) { + if(new File(`${dir}/${file.name}`).ext() === 'ts') { Logger.debug(`File "${file.name}" is not a TS file, skipping...`); continue; } diff --git a/discord/dispatchers/interaction.ts b/discord/dispatchers/interaction.ts index 75e8abcc..eba8b3be 100644 --- a/discord/dispatchers/interaction.ts +++ b/discord/dispatchers/interaction.ts @@ -1,9 +1,9 @@ import {ApplicationCommandOption, ApplicationCommandTypes} from "https://deno.land/x/discordeno@13.0.0/mod.ts"; import { Discord } from "../discord.ts"; import { Logger } from "../../logging/logger.ts"; -import { isTs } from "../../util/is-ts.ts"; -import { folderExists } from "../../util/folder-exists.ts"; -import { Inflector } from "../../util/inflector.ts"; +import { File } from "../../filesystem/file.ts"; +import { Folder } from "../../filesystem/folder.ts"; +import { Inflector } from "../../utility/inflector.ts"; export interface InteractionConfig { name: string; @@ -14,6 +14,7 @@ export interface InteractionConfig { } export class InteractionDispatcher { + private static _interactionsDir = `${Deno.cwd()}/src/interactions`; private static list: InteractionConfig[] = []; private static handlers: any = {}; @@ -57,7 +58,7 @@ export class InteractionDispatcher { public static async add(interaction: InteractionConfig): Promise { try { // Import the interaction handler - InteractionDispatcher.handlers[interaction.handler] = await import(`file://${Deno.cwd()}/src/interactions/${interaction.handler}.ts`) + InteractionDispatcher.handlers[interaction.handler] = await import(`file://${InteractionDispatcher._interactionsDir}/${interaction.handler}.ts`) } catch(e) { Logger.error(`Could not register interaction handler for "${interaction}": ${e.message}`); return; @@ -98,29 +99,28 @@ export class InteractionDispatcher { */ public static async load(): Promise { // Create our directory string - const dir = `${Deno.cwd()}/src/interactions`; - Logger.debug(`Loading interactions from "${dir}"...`); + Logger.debug(`Loading interactions from "${InteractionDispatcher._interactionsDir}"...`); // Make sure the interactions directory exists - if(!folderExists(dir)) { - Logger.warning(`"${dir}" does not exist, no interactions to load`); + if(!await new Folder(InteractionDispatcher._interactionsDir).exists()) { + Logger.warning(`"${InteractionDispatcher._interactionsDir}" does not exist, no interactions to load`); return; } // Get a list of all files - const files = await Deno.readDir(dir); + const files = await Deno.readDir(InteractionDispatcher._interactionsDir); // Load all interactions const promiseQueue: Promise[] = []; for await(const file of files) { - if(!isTs(file.name)) { + if(new File(`${InteractionDispatcher._interactionsDir}/${file.name}`).ext() === 'ts') { Logger.debug(`File "${file.name}" is not a TS file, skipping...`); continue; } // Import each file as a module Logger.debug(`Loading "${file.name}"...`); - const module = await import(`file:///${dir}/${file.name}`); + const module = await import(`file:///${InteractionDispatcher._interactionsDir}/${file.name}`); // Make sure module has a "config" exposed if(!('config' in module)) { diff --git a/filesystem/file.ts b/filesystem/file.ts new file mode 100644 index 00000000..1e927c01 --- /dev/null +++ b/filesystem/file.ts @@ -0,0 +1,30 @@ +export class File { + public constructor( + private readonly path: string + ) { + } + + public async create(): Promise { + await Deno.create(this.path); + } + + public async exists(): Promise { + try { + const target = await Deno.stat(this.path); + return target.isFile; + } catch(e) { + if(e instanceof Deno.errors.NotFound) return false; + throw e; + } + } + + public async delete(): Promise { + await Deno.remove(this.path); + } + + public ext(): string { + const pos = this.path.lastIndexOf("."); + if(pos < 1) return ''; + return this.path.slice(pos + 1); + } +} diff --git a/filesystem/folder.ts b/filesystem/folder.ts new file mode 100644 index 00000000..c8698d01 --- /dev/null +++ b/filesystem/folder.ts @@ -0,0 +1,16 @@ +export class Folder { + public constructor( + private readonly path: string + ) { + } + + public async exists(): Promise { + try { + const target = await Deno.stat(this.path); + return target.isDirectory; + } catch(e) { + if(e instanceof Deno.errors.NotFound) return false; + throw e; + } + } +} diff --git a/logging/logger.ts b/logging/logger.ts index 74279b16..86d44a89 100644 --- a/logging/logger.ts +++ b/logging/logger.ts @@ -1,5 +1,5 @@ -import { Time } from "../common/time.ts"; -import { Configure } from "../common/configure.ts"; +import { Time } from "../utility/time.ts"; +import { Configure } from "../core/configure.ts"; import { cyan, yellow, red, magenta, bold } from "https://deno.land/std@0.117.0/fmt/colors.ts"; export class Logger { diff --git a/tests/common/configure.test.ts b/tests/common/configure.test.ts index 62372443..20549d4a 100644 --- a/tests/common/configure.test.ts +++ b/tests/common/configure.test.ts @@ -1,5 +1,5 @@ import { assertEquals } from "https://deno.land/std@0.152.0/testing/asserts.ts"; -import { Configure } from "../../common/configure.ts"; +import { Configure } from "../../core/configure.ts"; Deno.test("Configure Test", async (t) => { // Add a test variable and test against it diff --git a/tests/util/inflector.test.ts b/tests/util/inflector.test.ts index d018b459..414f6446 100644 --- a/tests/util/inflector.test.ts +++ b/tests/util/inflector.test.ts @@ -1,4 +1,4 @@ -import { Inflector } from "../../util/inflector.ts"; +import { Inflector } from "../../utility/inflector.ts"; import { assertEquals } from "https://deno.land/std@0.152.0/testing/asserts.ts"; Deno.test("Inflector Test", async (t) => { diff --git a/util/folder-exists.ts b/util/folder-exists.ts deleted file mode 100644 index fe79b389..00000000 --- a/util/folder-exists.ts +++ /dev/null @@ -1,9 +0,0 @@ -export async function folderExists(path: string): Promise { - try { - const target = await Deno.stat(path); - return target.isDirectory; - } catch(e) { - if(e instanceof Deno.errors.NotFound) return false; - throw e; - } -} diff --git a/util/is-ts.ts b/util/is-ts.ts deleted file mode 100644 index eade59b9..00000000 --- a/util/is-ts.ts +++ /dev/null @@ -1,5 +0,0 @@ -export function isTs(name: string): boolean { - const pos = name.lastIndexOf("."); - if(pos < 1) return false; - return name.slice(pos + 1) === 'ts'; -} diff --git a/util/tokenizer.ts b/util/tokenizer.ts deleted file mode 100644 index e709eed0..00000000 --- a/util/tokenizer.ts +++ /dev/null @@ -1,10 +0,0 @@ -export function tokenizer(input: string, limit = 3) { - const tokens = input.split(" "); - if(tokens.length > limit) { - let ret = tokens.splice(0, limit); - ret.push(tokens.join(" ")); - return ret; - } - - return tokens; -} diff --git a/util/check-source.ts b/utility/check-source.ts similarity index 88% rename from util/check-source.ts rename to utility/check-source.ts index 076ffc1e..652459fa 100644 --- a/util/check-source.ts +++ b/utility/check-source.ts @@ -1,4 +1,5 @@ import { Logger } from "../logging/logger.ts"; +import { File } from "../filesystem/file.ts"; export interface ExclusionConfig { directories?: string[]; @@ -18,7 +19,7 @@ export class CheckSource { // Get list of files await this.getFiles(this.path); - // Checkk all files found + // Check all files found Logger.info(`Checking "${this.files.length}" files...`); await this.checkFiles(); @@ -53,7 +54,7 @@ export class CheckSource { Logger.debug(`Skipping excluded file "${path}/${entry.name}"...`); continue; } - if(!this.isTs(entry.name)) { + if(new File(`${path}/${entry.name}`).ext() !== 'ts') { Logger.debug(`Skipping non-ts file...`); continue; } @@ -86,15 +87,4 @@ export class CheckSource { } } } - - /** - * Checks whether the file is a ".ts" file - * - * @returns boolean - */ - private isTs(name: string): boolean { - const pos = name.lastIndexOf("."); - if(pos < 1) return false; - return name.slice(pos + 1) === 'ts'; - } } diff --git a/common/cron.ts b/utility/cron.ts similarity index 100% rename from common/cron.ts rename to utility/cron.ts diff --git a/util/inflector.ts b/utility/inflector.ts similarity index 97% rename from util/inflector.ts rename to utility/inflector.ts index 7419db06..4bf088ba 100644 --- a/util/inflector.ts +++ b/utility/inflector.ts @@ -14,7 +14,7 @@ export class Inflector { /** * Return input string with first character lowercased. - * + * * @param input */ public static lcfirst(input: string): string { @@ -23,7 +23,7 @@ export class Inflector { /** * Turn a string into PascalCase. - * + * * @param input */ public static pascalize(input: string, delimiter: string = '_'): string { @@ -35,7 +35,7 @@ export class Inflector { /** * Return the input lower_case_delimited_string as "A Human Readable String". * (Underscores are replaced by spaces and capitalized following words.) - * + * * @param input * @param delimiter */ @@ -43,12 +43,12 @@ export class Inflector { // Split our string into tokens const tokens: string[] = input .split(delimiter); - + // Uppercase each of the tokens for(let i = 0; i < tokens.length; i++) { tokens[i] = Inflector.ucfirst(tokens[i]); } - + // Join tokens into a string and return return tokens.join(' '); } diff --git a/util/raise.ts b/utility/raise.ts similarity index 97% rename from util/raise.ts rename to utility/raise.ts index e36f3a30..3bb59659 100644 --- a/util/raise.ts +++ b/utility/raise.ts @@ -1,6 +1,6 @@ /** * Utility function that literally just throws an error - * + * * @param err */ export function raise(err: string): never { diff --git a/utility/text.ts b/utility/text.ts new file mode 100644 index 00000000..3000238c --- /dev/null +++ b/utility/text.ts @@ -0,0 +1,25 @@ +export class Text { + /** + * Generate unique identifiers as per RFC-4122. + */ + public static uuid(): string { + return crypto.randomUUID(); + } + + /** + * Tokenize a string into an array of strings. + * + * @param input + * @param limit + */ + public static tokenize(input: string, limit = 3): string[] { + const tokens = input.split(" "); + if(tokens.length > limit) { + let ret = tokens.splice(0, limit); + ret.push(tokens.join(" ")); + return ret; + } + + return tokens; + } +} diff --git a/util/time-string.ts b/utility/time-string.ts similarity index 100% rename from util/time-string.ts rename to utility/time-string.ts diff --git a/common/time.ts b/utility/time.ts similarity index 96% rename from common/time.ts rename to utility/time.ts index a1376bee..0bdc1eaa 100644 --- a/common/time.ts +++ b/utility/time.ts @@ -1,6 +1,6 @@ import { time as timets } from "https://denopkg.com/burhanahmeed/time.ts@v2.0.1/mod.ts"; import { format as formatter } from "https://cdn.deno.land/std/versions/0.77.0/raw/datetime/mod.ts"; -import { T } from "../util/time-string.ts"; +import { T } from "./time-string.ts"; export class Time { private readonly time; diff --git a/webserver/controller/controller.ts b/webserver/controller/controller.ts index de34721c..a3c3cdc4 100644 --- a/webserver/controller/controller.ts +++ b/webserver/controller/controller.ts @@ -1,9 +1,9 @@ import { Logger } from "../../logging/logger.ts"; -import { Inflector } from "../../util/inflector.ts"; +import { Inflector } from "../../utility/inflector.ts"; import { Handlebars } from "../renderers/handlebars.ts"; import { ResponseBuilder } from "../http/response-builder.ts"; import { Request } from "../http/request.ts"; -import { raise } from "../../util/raise.ts"; +import { raise } from "../../utility/raise.ts"; import { Component } from "./component.ts"; import { Registry } from "../registry/registry.ts"; import { compress as compressBrotli } from "https://deno.land/x/brotli@v0.1.4/mod.ts"; @@ -106,7 +106,7 @@ export class Controller { * @returns Promise */ public async render(): Promise { - let body = ''; + let body: string|Uint8Array = ''; let canCompress = true; switch(this.getResponse().getHeaderLine('Content-Type').toLowerCase()) { case 'application/json': @@ -118,7 +118,7 @@ export class Controller { case 'text/html': const controller = Inflector.lcfirst(this.getRequest().getRoute().getController()); const action = this.getRequest().getRoute().getAction(); - body = await Handlebars.render(`${Controller._templateDir}/${controller}/${action}.hbs`, this._vars); + body = await Handlebars.render(`${Controller._templateDir}/${controller}/${action}.hbs`, this._vars) ?? raise('Could not render handlebars'); } // Check if we can compress with Brotli diff --git a/webserver/http/response-builder.ts b/webserver/http/response-builder.ts index 7cb1cc3c..62ade900 100644 --- a/webserver/http/response-builder.ts +++ b/webserver/http/response-builder.ts @@ -1,5 +1,5 @@ import { StatusCodes } from "./status-codes.ts"; -import { T as TimeString } from "../../util/time-string.ts"; +import { T as TimeString } from "../../utility/time-string.ts"; interface ResponseHeader { [key: string]: string; diff --git a/webserver/renderers/handlebars.ts b/webserver/renderers/handlebars.ts index 1a54f693..c27c8eb0 100644 --- a/webserver/renderers/handlebars.ts +++ b/webserver/renderers/handlebars.ts @@ -1,6 +1,6 @@ import { default as hbs } from "https://jspm.dev/handlebars@4.7.6"; import { Logger } from "../../logging/logger.ts"; -import { raise } from "../../util/raise.ts"; +import { raise } from "../../utility/raise.ts"; import { ViewVariable } from "../controller/controller.ts"; interface CacheItem { diff --git a/webserver/routing/router.ts b/webserver/routing/router.ts index 0c7fa044..405e78f6 100644 --- a/webserver/routing/router.ts +++ b/webserver/routing/router.ts @@ -1,13 +1,13 @@ import { readerFromStreamReader } from "https://deno.land/std@0.126.0/io/mod.ts"; import { pathToRegexp } from "../pathToRegexp.ts"; -import { Inflector } from "../../util/inflector.ts"; +import { Inflector } from "../../utility/inflector.ts"; import { Logger } from "../../logging/logger.ts"; import { Request as ChompRequest, RequestParameters } from "../http/request.ts"; import { StatusCodes } from "../http/status-codes.ts"; import { Route as ChompRoute } from "./route.ts"; import { Controller } from "../controller/controller.ts"; import { Registry } from "../registry/registry.ts"; -import { raise } from "../../util/raise.ts"; +import { raise } from "../../utility/raise.ts"; interface Route { path: string; diff --git a/websocket/authenticator.ts b/websocket/authenticator.ts index 80321461..e92fbc1f 100644 --- a/websocket/authenticator.ts +++ b/websocket/authenticator.ts @@ -1,5 +1,5 @@ import { Logger } from "../logging/logger.ts"; -import { Configure } from "../common/configure.ts"; +import { Configure } from "../core/configure.ts"; export class Authenticator { /** diff --git a/websocket/websocket.ts b/websocket/websocket.ts index 2464e9c7..98a3ce53 100644 --- a/websocket/websocket.ts +++ b/websocket/websocket.ts @@ -2,7 +2,7 @@ import { WebSocketServer, WebSocketAcceptedClient } from "https://deno.land/x/we import { Logger } from "../logging/logger.ts"; import { Events } from "./events.ts"; import { Authenticator } from "./authenticator.ts"; -import { Configure } from "../common/configure.ts"; +import { Configure } from "../core/configure.ts"; export class Websocket { private readonly port: number = 80;