From 548cfb02e0dd2bde3a58406fea83b1083091e2c3 Mon Sep 17 00:00:00 2001 From: gh0sTedBuddy Date: Mon, 1 Apr 2024 22:22:37 +0100 Subject: [PATCH] enh(general) BaseRouter docs; unified Storage; implement Cookies --- src/Routing/BaseRouter.js | 65 +++++++++++++++++++++++++++++++++- src/Utils/Cookie.js | 74 +++++++++++++++++++++++++++++++++++++++ src/Utils/Storage.js | 74 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 src/Utils/Cookie.js create mode 100644 src/Utils/Storage.js diff --git a/src/Routing/BaseRouter.js b/src/Routing/BaseRouter.js index 342ab8b..0a1a2eb 100644 --- a/src/Routing/BaseRouter.js +++ b/src/Routing/BaseRouter.js @@ -1,49 +1,112 @@ import Logger from '../Utils/Logger'; +/** + * @class BaseRouter + * @description Base class for creating routers + */ export default class BaseRouter { + /** + * Add a route with the specified path, callback, and options. + * @param {string} path - The route path. + * @param {function} cb - The callback function to handle the route. + * @param {object} [options={}] - Additional options for the route. + */ add(path, cb, options = {}) { - Logger.error('add method not implemented'); + throw new Error('add method not implemented!') } + /** + * Add a route that matches any HTTP method. + * @param {string} path - The route path. + * @param {function} cb - The callback function to handle the route. + */ any(path, cb) { this.all(path, cb); } + /** + * Add a route that matches all HTTP methods. + * @param {string} path - The route path. + * @param {function} cb - The callback function to handle the route. + * @returns {BaseRouter} The current instance of BaseRouter for chaining. + */ all(path, cb) { this.add(path, cb, { ...(arguments[2] || {}), method: 'all' }); return this; } + /** + * Add a route that matches the GET HTTP method. + * @param {string} path - The route path. + * @param {function} cb - The callback function to handle the route. + * @returns {BaseRouter} The current instance of BaseRouter for chaining. + */ get(path, cb) { this.add(path, cb, { ...(arguments[2] || {}), method: 'get' }); return this; } + /** + * Add a route that matches the POST HTTP method. + * @param {string} path - The route path. + * @param {function} cb - The callback function to handle the route. + * @returns {BaseRouter} The current instance of BaseRouter for chaining. + */ post(path, cb) { this.add(path, cb, { ...(arguments[2] || {}), method: 'post' }); return this; } + /** + * Add a route that matches the PUT HTTP method. + * @param {string} path - The route path. + * @param {function} cb - The callback function to handle the route. + * @returns {BaseRouter} The current instance of BaseRouter for chaining. + */ put(path, cb) { this.add(path, cb, { ...(arguments[2] || {}), method: 'put' }); return this; } + /** + * Add a route that matches the PATCH HTTP method. + * @param {string} path - The route path. + * @param {function} cb - The callback function to handle the route. + * @returns {BaseRouter} The current instance of BaseRouter for chaining. + */ patch(path, cb) { this.add(path, cb, { ...(arguments[2] || {}), method: 'patch' }); return this; } + /** + * Add a route that matches the DELETE HTTP method. + * @param {string} path - The route path. + * @param {function} cb - The callback function to handle the route. + * @returns {BaseRouter} The current instance of BaseRouter for chaining. + */ delete(path, cb) { this.add(path, cb, { ...(arguments[2] || {}), method: 'delete' }); return this; } + /** + * Add a route that matches the HEAD HTTP method. + * @param {string} path - The route path. + * @param {function} cb - The callback function to handle the route. + * @returns {BaseRouter} The current instance of BaseRouter for chaining. + */ head(path, cb) { this.add(path, cb, { ...(arguments[2] || {}), method: 'head' }); return this; } + /** + * Add a route that matches the OPTIONS HTTP method. + * @param {string} path - The route path. + * @param {function} cb - The callback function to handle the route. + * @returns {BaseRouter} The current instance of BaseRouter for chaining. + */ options(path, cb) { this.add(path, cb, { ...(arguments[2] || {}), method: 'options' }); return this; diff --git a/src/Utils/Cookie.js b/src/Utils/Cookie.js new file mode 100644 index 0000000..47ab81c --- /dev/null +++ b/src/Utils/Cookie.js @@ -0,0 +1,74 @@ +export default class Cookie { + /** + * Reads cookies from a Request object and returns a Map of cookie values. + * @param {Request} request - The Request object to read cookies from. + * @returns {Map} - A Map containing the cookie values. + */ + static read(request, settings = {}) { + const cookie = new Map(); + if (request.headers.has('Cookie')) { + const cookiePairs = request.headers.get('Cookie').toString().split('; '); + + for (const pair of cookiePairs) { + const [name, value] = pair.split('='); + cookie.set(name, decodeURIComponent(value)); + } + } + + Object.keys(settings).forEach(k => cookie.set(k, settings[k])); + + return cookie; + } + + /** + * Writes a Map of cookie values to a Response object's headers. + * @param {Response} response - The Response object to write cookies to. + * @param {object} cookies - An object containing cookie values. + * @param {object} [settings={}] - Additional cookie settings (e.g., expires, path, domain). + */ + static write(response, data, settings = {}) { + const cookiePairs = []; + + for (const [name, value] of [...data]) { + const encodedValue = encodeURIComponent(value); + cookiePairs.push(`${name}=${encodedValue}`); + } + + const cookieString = cookiePairs.join('; '); + + let cookie = `${cookieString}`; + // Append additional cookie settings if present + for (const [settingName, settingValue] of Object.entries(settings)) { + cookie += `; ${settingName}`; + if (settingValue !== true) { + cookie += `=${settingValue}`; + } + } + + response.headers.set('Set-Cookie', cookie); + } + + /** + * Sets a cookie value with the specified name and optional settings. + * @param {Response} response - The Response object to set the cookie on. + * @param {string} name - The name of the cookie. + * @param {string} value - The value of the cookie. + * @param {object} [settings={}] - Additional cookie settings (e.g., expires, path, domain). + */ + static set(response, name, value, settings = {}) { + const cookies = CookieHandler.read(response.request); + cookies[name] = value; + CookieHandler.write(response, cookies, settings); + } + + /** + * Deletes a cookie with the specified name by setting its expiration to a past date. + * @param {Response} response - The Response object to delete the cookie from. + * @param {string} name - The name of the cookie to delete. + */ + static delete(response, name) { + const cookies = CookieHandler.read(response.request); + delete cookies[name]; + CookieHandler.write(response, cookies, { expires: new Date(0) }); + } +} diff --git a/src/Utils/Storage.js b/src/Utils/Storage.js new file mode 100644 index 0000000..5caff9d --- /dev/null +++ b/src/Utils/Storage.js @@ -0,0 +1,74 @@ +import { resolve } from 'path'; +import { mkdir, exists, readdir } from 'fs/promises'; + +/** + * Storage class for handling file operations. + */ +export default class Storage { + #options = new Map([ + ['baseDir', undefined], + ]); + + /** + * Constructor for the Storage class. + * @param {Object|Map} options - Configuration options for the Storage instance. + */ + constructor(options) { + if(typeof options == 'object' && options.constructor?.name == 'Object') { + options = new Map(Object.entries(options)); + } + + for(const k of this.#options.keys()) { + if(!options.has(k)) continue; + this.#options.set(k, options.get(k)); + } + } + + /** + * Ensures that a directory exists at the specified path. + * @param {string} path - The path of the directory to ensure. + * @returns {Promise} + */ + async ensureDir(path) { + if(!path || typeof(path) != 'string') return; + path = this.resolve(path); + if(!(await exists(path))) { + await mkdir(path, { recursive: true }); + } + } + + /** + * Resolves a relative path to an absolute path based on the configured base directory. + * @param {string} path - The relative path to resolve. + * @returns {string} The resolved absolute path. + */ + resolve(path) { + return resolve(this.#options.get('baseDir'), path); + } + + /** + * Writes data to a file at the specified path. + * @param {string} path - The path of the file to write. + * @param {string|Buffer} data - The data to write to the file. + * @returns {Promise} + */ + async write(path, data) { + path = this.resolve(path); + const file = Bun.file(path); + return await Bun.write(file, data); + } + + /** + * Reads the contents of a file at the specified path. + * @param {string} path - The path of the file to read. + * @returns {Promise} The contents of the file as a string. + * @throws {Error} If the file does not exist. + */ + async read(path) { + path = this.resolve(path); + const file = Bun.file(path); + if(!(await file.exists())) throw new Error('file does not exist'); + + return await file.text(); + } +}