feat(domains) implement domain routing; Logger
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
import { LayServer } from '../src';
|
import { LayServer } from '../src';
|
||||||
import { google } from 'googleapis';
|
import {resolve} from 'path';
|
||||||
|
import Html from '../src/Views/Html';
|
||||||
|
|
||||||
|
const APP_PORT = Bun.env.APP_PORT ?? Bun.env.PORT ?? 3000;
|
||||||
|
|
||||||
class InfoController {
|
class InfoController {
|
||||||
static namespace = 'info';
|
static namespace = 'info';
|
||||||
@@ -10,7 +13,10 @@ class InfoController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
index(req, res) {
|
index(req, res) {
|
||||||
res.text('I think you are right here');
|
return res.json({
|
||||||
|
headers: req.headers
|
||||||
|
})
|
||||||
|
res.text('I think you are right here <a href="/country/spain">now go here</a>');
|
||||||
}
|
}
|
||||||
|
|
||||||
countryInfo(req, res) {
|
countryInfo(req, res) {
|
||||||
@@ -28,54 +34,16 @@ class InfoController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class oAuthController {
|
|
||||||
static namespace = 'oauth';
|
|
||||||
constructor(server) {
|
|
||||||
server.get('/:provider', this.auth.bind(this));
|
|
||||||
server.get('/callback/:provider', this.callback.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
async auth(req, res) {
|
|
||||||
if (req.params.get('provider') != 'google') {
|
|
||||||
return {
|
|
||||||
messages: 'invalid provider',
|
|
||||||
data: [...req.params.entries()],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const scopes = 'https://www.googleapis.com/auth/youtube';
|
|
||||||
const url = oauth2Client.generateAuthUrl({
|
|
||||||
// 'online' (default) or 'offline' (gets refresh_token)
|
|
||||||
access_type: 'offline',
|
|
||||||
|
|
||||||
// If you only need one scope you can pass it as a string
|
|
||||||
scope: scopes,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(url);
|
|
||||||
|
|
||||||
return Response.redirect(url, 301);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(req, res) {
|
|
||||||
if (!req.query.get('code')) {
|
|
||||||
return new Response('/', { status: 302 });
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response(`/${req.query.get('code')}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const server = new LayServer({
|
const server = new LayServer({
|
||||||
|
viewsPath: resolve(__dirname, 'views'),
|
||||||
|
loaders: new Map([[
|
||||||
|
'html', new Html()
|
||||||
|
]]),
|
||||||
controllers: [
|
controllers: [
|
||||||
function Main(server) {
|
function Main(server) {
|
||||||
server.get('/', async (req, res) => {
|
server.get('/', async (req, res) => {
|
||||||
return {
|
return res.json({message: 'hello world'})
|
||||||
message: 'Hi mom, i am on the internet!',
|
}, { domain: 'api.tipedi.local' });
|
||||||
batman: req.query.get('batman'),
|
|
||||||
body: req.query,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
server.all('/data', (req, res) => {
|
server.all('/data', (req, res) => {
|
||||||
let respBody = `your ${req.method} request was well recieved`;
|
let respBody = `your ${req.method} request was well recieved`;
|
||||||
switch (req.method) {
|
switch (req.method) {
|
||||||
@@ -110,9 +78,14 @@ const server = new LayServer({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
oAuthController,
|
|
||||||
InfoController,
|
InfoController,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen(Bun.env.APP_PORT ?? Bun.env.PORT ?? 42169);
|
server.get('/', async (req, res) => {
|
||||||
|
return res.render('index');
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(APP_PORT, () => {
|
||||||
|
console.log(`server listen on ${APP_PORT}`);
|
||||||
|
});
|
||||||
|
|||||||
29
examples/views/index.html
Normal file
29
examples/views/index.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang='en'>
|
||||||
|
<head>
|
||||||
|
<meta charset='UTF-8'>
|
||||||
|
<meta name='viewport' content='width=device-width,initial-scale=1.0'>
|
||||||
|
<link rel='stylesheet' href='app.css'>
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div class="logo">
|
||||||
|
<a href="/">layc</a>
|
||||||
|
</div>
|
||||||
|
<nav class="nav">
|
||||||
|
<a href="/features">features</a>
|
||||||
|
<a href="/blog">Blog</a>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
{{ content }}
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<nav class="nav">
|
||||||
|
<a href="/imprint">Imprint</a>
|
||||||
|
<a href="/tos">Terms</a>
|
||||||
|
</nav>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import MimeTypeMap from './MimeTypeMap';
|
import MimeTypeMap from './MimeTypeMap';
|
||||||
|
|
||||||
export default class LayResponse {
|
export default class LayResponse {
|
||||||
|
routing = undefined;
|
||||||
body;
|
body;
|
||||||
statusCode;
|
statusCode;
|
||||||
headerList = new Map();
|
headerList = new Map();
|
||||||
@@ -9,8 +10,8 @@ export default class LayResponse {
|
|||||||
disableFileCheck: false,
|
disableFileCheck: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(router) {
|
constructor(routing) {
|
||||||
this.router = router;
|
this.routing = routing;
|
||||||
this.body = 'Not Implemented';
|
this.body = 'Not Implemented';
|
||||||
this.statusCode = 502;
|
this.statusCode = 502;
|
||||||
this.headerList = new Map();
|
this.headerList = new Map();
|
||||||
@@ -101,7 +102,7 @@ export default class LayResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async render(path, data = {}) {
|
async render(path, data = {}) {
|
||||||
const loader = await this.router.getServer().load(path);
|
const loader = await this.routing.getServer().load(path);
|
||||||
const content = await loader.parse(data, {
|
const content = await loader.parse(data, {
|
||||||
status: this.status(),
|
status: this.status(),
|
||||||
headers: this.headers(),
|
headers: this.headers(),
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
|
import { readdir } from 'fs/promises';
|
||||||
import { resolve as resolvePath } from 'path';
|
import { resolve as resolvePath } from 'path';
|
||||||
import Router from './Routing/Router';
|
|
||||||
import LayRouting from './Routing/LayRouting';
|
import LayRouting from './Routing/LayRouting';
|
||||||
import HtmlEngine from './Views/Html';
|
import HtmlEngine from './Views/Html';
|
||||||
import DefaultLoader from './Views/DefaultLoader';
|
import DefaultLoader from './Views/DefaultLoader';
|
||||||
import { readdir } from 'fs/promises';
|
import BaseRouter from './Routing/BaseRouter';
|
||||||
|
|
||||||
|
import Logger from './Utils/Logger';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a LayServer instance that handles routing and server configuration.
|
* Represents a LayServer instance that handles routing and server configuration.
|
||||||
* @todo add multi domain support
|
* @todo add multi domain support
|
||||||
* @class
|
* @class
|
||||||
*/
|
*/
|
||||||
export default class LayServer {
|
export default class LayServer extends BaseRouter {
|
||||||
controllers = new Map();
|
controllers = new Map();
|
||||||
loaders = new Map();
|
loaders = new Map();
|
||||||
proc = null;
|
proc = null;
|
||||||
router;
|
|
||||||
publicFiles;
|
publicFiles;
|
||||||
|
routing = null;
|
||||||
options = new Map([
|
options = new Map([
|
||||||
['loaders', undefined],
|
['loaders', undefined],
|
||||||
['publicDir', undefined],
|
['publicDir', undefined],
|
||||||
@@ -24,6 +27,7 @@ export default class LayServer {
|
|||||||
['discoverPublicFiles', false],
|
['discoverPublicFiles', false],
|
||||||
['enforceTrailingSlash', false],
|
['enforceTrailingSlash', false],
|
||||||
['laycLoading', false],
|
['laycLoading', false],
|
||||||
|
['allowRouteOverwrite', true],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,6 +35,7 @@ export default class LayServer {
|
|||||||
* @param {Object} options - The options for configuring the LayServer.
|
* @param {Object} options - The options for configuring the LayServer.
|
||||||
*/
|
*/
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
|
super();
|
||||||
if (typeof options == 'object') {
|
if (typeof options == 'object') {
|
||||||
const optionKeys = Object.getOwnPropertyNames(options);
|
const optionKeys = Object.getOwnPropertyNames(options);
|
||||||
|
|
||||||
@@ -53,7 +58,11 @@ export default class LayServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initRouting(options.router);
|
this.routing = new LayRouting(this);
|
||||||
|
|
||||||
|
if(options.router) {
|
||||||
|
this.routing.addRouter(options.router);
|
||||||
|
}
|
||||||
|
|
||||||
if (options.hasOwnProperty('controllers')) {
|
if (options.hasOwnProperty('controllers')) {
|
||||||
this.registerControllers(options.controllers);
|
this.registerControllers(options.controllers);
|
||||||
@@ -110,19 +119,10 @@ export default class LayServer {
|
|||||||
* @param {string} publicDir - The directory where the public files are stored.
|
* @param {string} publicDir - The directory where the public files are stored.
|
||||||
*/
|
*/
|
||||||
indexPublicFiles(publicDir) {
|
indexPublicFiles(publicDir) {
|
||||||
|
Logger.error('@todo implement method `indexPublicFiles`')
|
||||||
// this method is meant to try to iterate over all public files which are stored as static routes
|
// this method is meant to try to iterate over all public files which are stored as static routes
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the routing for the LayServer.
|
|
||||||
* @param {Function} [router=null] - The router class to be used for routing.
|
|
||||||
*/
|
|
||||||
initRouting(router = null) {
|
|
||||||
if (router) this.router = new router(this);
|
|
||||||
if (typeof this.router == 'undefined') this.router = new Router(this);
|
|
||||||
this.routeControl = new LayRouting(this.router);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers the given controllers.
|
* Registers the given controllers.
|
||||||
*
|
*
|
||||||
@@ -133,13 +133,13 @@ export default class LayServer {
|
|||||||
|
|
||||||
for (let controller of controllers) {
|
for (let controller of controllers) {
|
||||||
if (typeof controller != 'function') {
|
if (typeof controller != 'function') {
|
||||||
console.log(`ignored ${controller} for not being a function`);
|
Logger(`ignored ${controller} for not being a function`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the given controller is just an anonymous function defining the routes itself
|
// if the given controller is just an anonymous function defining the routes itself
|
||||||
if (!controller?.prototype?.constructor?.name) {
|
if (!controller?.prototype?.constructor?.name) {
|
||||||
controller(this.routeControl);
|
controller(this.routing);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,9 +156,9 @@ export default class LayServer {
|
|||||||
* @returns {Object} - The instantiated controller.
|
* @returns {Object} - The instantiated controller.
|
||||||
*/
|
*/
|
||||||
instantiateController(controller) {
|
instantiateController(controller) {
|
||||||
this.routeControl.setNamespace(controller.namespace);
|
this.routing.setNamespace(controller.namespace);
|
||||||
const _temp = new controller(this.routeControl);
|
const _temp = new controller(this.routing);
|
||||||
if (controller.namespace) this.routeControl.setNamespace(null);
|
if (controller.namespace) this.routing.setNamespace(null);
|
||||||
return _temp;
|
return _temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ export default class LayServer {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err, import.meta.dir, 'public', path);
|
Logger(err, import.meta.dir, 'public', path);
|
||||||
return new Response('Internal Server Error', {
|
return new Response('Internal Server Error', {
|
||||||
status: 500,
|
status: 500,
|
||||||
});
|
});
|
||||||
@@ -211,11 +211,9 @@ export default class LayServer {
|
|||||||
return Response.redirect(`${url.toString()}/`, 302);
|
return Response.redirect(`${url.toString()}/`, 302);
|
||||||
}
|
}
|
||||||
|
|
||||||
let resp = await this.router.handle(req);
|
let resp = await this.routing.handle(req);
|
||||||
|
if (resp instanceof Response) return resp;
|
||||||
if (!resp) return new Response('Not Found', { status: 404 });
|
return new Response('Not Found', { status: 404 });
|
||||||
|
|
||||||
return resp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -229,7 +227,7 @@ export default class LayServer {
|
|||||||
development: Bun.env.BUN_ENV != 'production',
|
development: Bun.env.BUN_ENV != 'production',
|
||||||
fetch: this.handleRequest.bind(this),
|
fetch: this.handleRequest.bind(this),
|
||||||
error(err) {
|
error(err) {
|
||||||
console.log('where', err.stack);
|
Logger('where', err.stack);
|
||||||
return new Response(`<pre>${err}\n${err.stack}</pre>`, {
|
return new Response(`<pre>${err}\n${err.stack}</pre>`, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'text/html',
|
'Content-Type': 'text/html',
|
||||||
@@ -246,8 +244,7 @@ export default class LayServer {
|
|||||||
cb(this.proc);
|
cb(this.proc);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('where', err.stack);
|
Logger.error('where', err.stack);
|
||||||
console.error(err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,7 +264,7 @@ export default class LayServer {
|
|||||||
|
|
||||||
const files = await readdir(path);
|
const files = await readdir(path);
|
||||||
for (let file of files) {
|
for (let file of files) {
|
||||||
console.log(
|
Logger(
|
||||||
file,
|
file,
|
||||||
file.substring(0, file.indexOf('.')),
|
file.substring(0, file.indexOf('.')),
|
||||||
filename.substring(0, filename.indexOf('.')) || filename
|
filename.substring(0, filename.indexOf('.')) || filename
|
||||||
@@ -304,4 +301,8 @@ export default class LayServer {
|
|||||||
|
|
||||||
return new DefaultLoader(filepath);
|
return new DefaultLoader(filepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async add(path, cb) {
|
||||||
|
this.routing.add(...arguments)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
308
src/LayServer.js~
Normal file
308
src/LayServer.js~
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
import { readdir } from 'fs/promises';
|
||||||
|
import { resolve as resolvePath } from 'path';
|
||||||
|
|
||||||
|
import LayRouting from './Routing/LayRouting';
|
||||||
|
import HtmlEngine from './Views/Html';
|
||||||
|
import DefaultLoader from './Views/DefaultLoader';
|
||||||
|
import BaseRouter from './Routing/BaseRouter';
|
||||||
|
|
||||||
|
import Logger from './Utils/Logger';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a LayServer instance that handles routing and server configuration.
|
||||||
|
* @todo add multi domain support
|
||||||
|
* @class
|
||||||
|
*/
|
||||||
|
export default class LayServer extends BaseRouter {
|
||||||
|
controllers = new Map();
|
||||||
|
loaders = new Map();
|
||||||
|
proc = null;
|
||||||
|
publicFiles;
|
||||||
|
routing = null;
|
||||||
|
options = new Map([
|
||||||
|
['loaders', undefined],
|
||||||
|
['publicDir', undefined],
|
||||||
|
['viewsPath', undefined],
|
||||||
|
['preferIndexFiles', false],
|
||||||
|
['discoverPublicFiles', false],
|
||||||
|
['enforceTrailingSlash', false],
|
||||||
|
['laycLoading', false],
|
||||||
|
['allowRouteOverwrite', true],
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of LayServer.
|
||||||
|
* @param {Object} options - The options for configuring the LayServer.
|
||||||
|
*/
|
||||||
|
constructor(options = {}) {
|
||||||
|
super();
|
||||||
|
if (typeof options == 'object') {
|
||||||
|
const optionKeys = Object.getOwnPropertyNames(options);
|
||||||
|
|
||||||
|
for (const key of optionKeys) {
|
||||||
|
if (!this.options.has(key)) continue;
|
||||||
|
if (key == 'loaders') continue;
|
||||||
|
const valueType = typeof this.options.get(key);
|
||||||
|
if (
|
||||||
|
typeof options[key] !== 'undefined' &&
|
||||||
|
(typeof options[key] == valueType ||
|
||||||
|
valueType == 'undefined')
|
||||||
|
)
|
||||||
|
this.options.set(key, options[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.hasOwnProperty('loaders')) {
|
||||||
|
if (options.loaders instanceof Map) {
|
||||||
|
this.loaders = options.loaders;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.routing = new LayRouting(this);
|
||||||
|
|
||||||
|
if(options.router) {
|
||||||
|
this.routing.addRouter(options.router);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.hasOwnProperty('controllers')) {
|
||||||
|
this.registerControllers(options.controllers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.publicFiles) this.addPublicFileRoutes(options.publicFiles);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!options.publicFiles &&
|
||||||
|
this.options.discoverPublicFiles &&
|
||||||
|
this.options.publicDir
|
||||||
|
)
|
||||||
|
this.indexPublicFiles(this.options.publicDir);
|
||||||
|
|
||||||
|
if (!this.options.viewsEngine && this.options.viewsPath) {
|
||||||
|
this.options.viewsEngine = new HtmlEngine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves or sets the value of an option.
|
||||||
|
* @param {string} key - The key of the option.
|
||||||
|
* @param {*} [value] - The value to set for the option. If not provided, the current value of the option is returned.
|
||||||
|
* @returns {*} - The current value of the option, or undefined if the option does not exist.
|
||||||
|
*/
|
||||||
|
option(key, value = undefined) {
|
||||||
|
if (!this.options.has(key)) return undefined;
|
||||||
|
if (value !== undefined) this.option.set(key, value);
|
||||||
|
return this.options.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds public file routes to the LayServer instance.
|
||||||
|
* @param {Array<string>} filePaths - An array of file paths to be added as public file routes.
|
||||||
|
* @throws {Error} Throws an error if filePaths is not an array or undefined.
|
||||||
|
*/
|
||||||
|
addPublicFileRoutes(filePaths) {
|
||||||
|
if (!filePaths) return;
|
||||||
|
if (!Array.isArray(filePaths))
|
||||||
|
throw new Error(
|
||||||
|
'publicFiles option has to be an array or undefined'
|
||||||
|
);
|
||||||
|
if (!this.publicFiles || !Array.isArray(this.publicFiles))
|
||||||
|
this.publicFiles = [];
|
||||||
|
for (let path of filePaths) {
|
||||||
|
if (typeof path != 'string') continue;
|
||||||
|
this.publicFiles.push(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indexes all public files stored as static routes.
|
||||||
|
* @todo Implement this method.
|
||||||
|
* @param {string} publicDir - The directory where the public files are stored.
|
||||||
|
*/
|
||||||
|
indexPublicFiles(publicDir) {
|
||||||
|
Logger.error('@todo implement method `indexPublicFiles`')
|
||||||
|
// this method is meant to try to iterate over all public files which are stored as static routes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the given controllers.
|
||||||
|
*
|
||||||
|
* @param {Function|Function[]} controllers - The controllers to register.
|
||||||
|
*/
|
||||||
|
registerControllers(controllers) {
|
||||||
|
if (!Array.isArray(controllers)) controllers = [controllers];
|
||||||
|
|
||||||
|
for (let controller of controllers) {
|
||||||
|
if (typeof controller != 'function') {
|
||||||
|
Logger(`ignored ${controller} for not being a function`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the given controller is just an anonymous function defining the routes itself
|
||||||
|
if (!controller?.prototype?.constructor?.name) {
|
||||||
|
controller(this.routing);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.controllers.set(
|
||||||
|
controller?.name,
|
||||||
|
this.instantiateController(controller)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a controller and sets its namespace in the route control.
|
||||||
|
* @param {Object} controller - The controller to be instantiated.
|
||||||
|
* @returns {Object} - The instantiated controller.
|
||||||
|
*/
|
||||||
|
instantiateController(controller) {
|
||||||
|
this.routing.setNamespace(controller.namespace);
|
||||||
|
const _temp = new controller(this.routing);
|
||||||
|
if (controller.namespace) this.routing.setNamespace(null);
|
||||||
|
return _temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the server is running.
|
||||||
|
* @returns {boolean} True if the server is running, false otherwise.
|
||||||
|
*/
|
||||||
|
isRunning() {
|
||||||
|
proc != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles an incoming request.
|
||||||
|
*
|
||||||
|
* @param {Request} req - The incoming request object.
|
||||||
|
* @returns {Promise<Response>} - A promise that resolves to a response object.
|
||||||
|
*/
|
||||||
|
async handleRequest(req) {
|
||||||
|
const url = new URL(req.url);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!url.pathname.endsWith('/') &&
|
||||||
|
Array.isArray(this.publicFiles) &&
|
||||||
|
this.publicFiles.length > 0
|
||||||
|
) {
|
||||||
|
let path = this.publicFiles.find(
|
||||||
|
(v) => url.pathname.substring(1) === v
|
||||||
|
);
|
||||||
|
if (path) {
|
||||||
|
try {
|
||||||
|
return new Response(
|
||||||
|
Bun.file(
|
||||||
|
resolvePath(
|
||||||
|
this.options.get('publicDir') ??
|
||||||
|
`${import.meta.dir}/public`,
|
||||||
|
path
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
Logger(err, import.meta.dir, 'public', path);
|
||||||
|
return new Response('Internal Server Error', {
|
||||||
|
status: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.enforceTrailingSlash && !url.pathname.endsWith('/')) {
|
||||||
|
return Response.redirect(`${url.toString()}/`, 302);
|
||||||
|
}
|
||||||
|
|
||||||
|
let resp = await this.routing.handle(req);
|
||||||
|
if (resp instanceof Response) return resp;
|
||||||
|
return new Response('Not Found', { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the server and listens for incoming requests on the specified port.
|
||||||
|
* @param {number} [port=8080] - The port number to listen on.
|
||||||
|
*/
|
||||||
|
async listen(port = 8080, cb = () => {}) {
|
||||||
|
try {
|
||||||
|
this.proc = await Bun.serve({
|
||||||
|
port,
|
||||||
|
development: Bun.env.BUN_ENV != 'production',
|
||||||
|
fetch: this.handleRequest.bind(this),
|
||||||
|
error(err) {
|
||||||
|
Logger('where', err.stack);
|
||||||
|
return new Response(`<pre>${err}\n${err.stack}</pre>`, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/html',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!this.proc) {
|
||||||
|
throw new Error('something went wrong');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!cb && cb.constructor?.name.endsWith('Function')) {
|
||||||
|
cb(this.proc);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
Logger.error('where', err.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the path of a view file based on the given filepath.
|
||||||
|
* @param {string} filepath - The filepath of the view file.
|
||||||
|
* @returns {Promise<string|null>} - The path of the view file, or null if not found.
|
||||||
|
*/
|
||||||
|
async getViewPath(filepath) {
|
||||||
|
let path = this.options.get('viewsPath');
|
||||||
|
if (!path.endsWith('/')) path += '/';
|
||||||
|
|
||||||
|
const filename = filepath.split('/').pop().toString();
|
||||||
|
const basedir = filepath.substring(0, filepath.indexOf(filename));
|
||||||
|
path += basedir;
|
||||||
|
if (!path.endsWith('/')) path += '/';
|
||||||
|
|
||||||
|
const files = await readdir(path);
|
||||||
|
for (let file of files) {
|
||||||
|
console.log(
|
||||||
|
file,
|
||||||
|
file.substring(0, file.indexOf('.')),
|
||||||
|
filename.substring(0, filename.indexOf('.')) || filename
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
(file.substring(0, file.indexOf('.')) || file) !=
|
||||||
|
(filename.substring(0, filename.indexOf('.')) || filename)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
return `${path}${file}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a file from the specified filepath.
|
||||||
|
*
|
||||||
|
* @param {string} filepath - The path of the file to load.
|
||||||
|
* @returns {DefaultLoader} - The loader for the file.
|
||||||
|
*/
|
||||||
|
async load(filepath) {
|
||||||
|
const fullFilePath = await this.getViewPath(filepath);
|
||||||
|
if (!fullFilePath) return new DefaultLoader(filepath);
|
||||||
|
const filename = fullFilePath.split('/').pop().toString();
|
||||||
|
const ext = filename.substring(filename.indexOf('.') + 1);
|
||||||
|
|
||||||
|
if (this.loaders.size > 0) {
|
||||||
|
for (const keys of this.loaders.keys()) {
|
||||||
|
if (!keys.includes(ext)) continue;
|
||||||
|
return this.loaders.get(keys).handle(fullFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DefaultLoader(filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
async add(path, cb) {
|
||||||
|
this.routing.add(...arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/Routing/BaseRouter.js
Normal file
51
src/Routing/BaseRouter.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import Logger from '../Utils/Logger';
|
||||||
|
|
||||||
|
export default class BaseRouter {
|
||||||
|
add(path, cb, options = {}) {
|
||||||
|
Logger.error('add method not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
any(path, cb) {
|
||||||
|
this.all(path, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
all(path, cb) {
|
||||||
|
this.add(path, cb, { ...(arguments[2] || {}), method: 'all' });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(path, cb) {
|
||||||
|
this.add(path, cb, { ...(arguments[2] || {}), method: 'get' });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
post(path, cb) {
|
||||||
|
this.add(path, cb, { ...(arguments[2] || {}), method: 'post' });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
put(path, cb) {
|
||||||
|
this.add(path, cb, { ...(arguments[2] || {}), method: 'put' });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
patch(path, cb) {
|
||||||
|
this.add(path, cb, { ...(arguments[2] || {}), method: 'patch' });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(path, cb) {
|
||||||
|
this.add(path, cb, { ...(arguments[2] || {}), method: 'delete' });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
head(path, cb) {
|
||||||
|
this.add(path, cb, { ...(arguments[2] || {}), method: 'head' });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
options(path, cb) {
|
||||||
|
this.add(path, cb, { ...(arguments[2] || {}), method: 'options' });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/Routing/BaseRouter.js~
Normal file
50
src/Routing/BaseRouter.js~
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import Logger from '../Utils/Logger';
|
||||||
|
export default class BaseRouter {
|
||||||
|
add(path, cb, options = {}) {
|
||||||
|
Logger.error('add method not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
any(path, cb) {
|
||||||
|
this.all(path, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
all(path, cb) {
|
||||||
|
this.add(path, cb, { ...(arguments[2] || {}), method: 'all' });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(path, cb) {
|
||||||
|
this.add(path, cb, { ...(arguments[2] || {}), method: 'get' });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
post(path, cb) {
|
||||||
|
this.add(path, cb, { ...(arguments[2] || {}), method: 'post' });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
put(path, cb) {
|
||||||
|
this.add(path, cb, { ...(arguments[2] || {}), method: 'put' });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
patch(path, cb) {
|
||||||
|
this.add(path, cb, { ...(arguments[2] || {}), method: 'patch' });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(path, cb) {
|
||||||
|
this.add(path, cb, { ...(arguments[2] || {}), method: 'delete' });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
head(path, cb) {
|
||||||
|
this.add(path, cb, { ...(arguments[2] || {}), method: 'head' });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
options(path, cb) {
|
||||||
|
this.add(path, cb, { ...(arguments[2] || {}), method: 'options' });
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,23 @@
|
|||||||
export default class LayRouting {
|
import Router from './Router';
|
||||||
router;
|
import BaseRouter from './BaseRouter';
|
||||||
namespace;
|
import LayRequest from '../IO/LayRequest';
|
||||||
constructor(router, namespace = null) {
|
import LayResponse from '../IO/LayResponse';
|
||||||
this.router = router;
|
import Logger from '../Utils/Logger';
|
||||||
|
|
||||||
if (namespace !== null) this.namespace = namespace;
|
export default class LayRouting extends BaseRouter {
|
||||||
|
routers;
|
||||||
|
namespace = null;
|
||||||
|
server;
|
||||||
|
constructor(server, namespace = null) {
|
||||||
|
super();
|
||||||
|
this.server = server;
|
||||||
|
this.routers = new Map([
|
||||||
|
['$default', new Router(this.server)]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getServer() {
|
||||||
|
return this.server;
|
||||||
}
|
}
|
||||||
|
|
||||||
setNamespace(ns) {
|
setNamespace(ns) {
|
||||||
@@ -13,16 +26,18 @@ export default class LayRouting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
routes() {
|
routes() {
|
||||||
return this.router.routes;
|
return this.routers.get('$default').routes;
|
||||||
}
|
}
|
||||||
|
|
||||||
add(path, cb, method) {
|
addRouter(router) {
|
||||||
if (this.namespace) {
|
let routerInstance;
|
||||||
if (!path.startsWith('/')) path = '/' + path;
|
if(typeof router == 'undefined' || router === null) {
|
||||||
path = this.namespace + path;
|
routerInstance = new Router(this.server);
|
||||||
|
} else {
|
||||||
|
routerInstance = new router(this.server);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.router.add(path, cb, method);
|
this.routers.set(router.domain || '$default', routerInstance);
|
||||||
}
|
}
|
||||||
|
|
||||||
route(config = {}) {
|
route(config = {}) {
|
||||||
@@ -38,47 +53,48 @@ export default class LayRouting {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
any(path, cb) {
|
async handle(req) {
|
||||||
this.all(path, cb);
|
const url = new URL(req.url);
|
||||||
|
const reqContext = {
|
||||||
|
request: null,
|
||||||
|
response: null,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
reqContext.request = new LayRequest(req);
|
||||||
|
reqContext.response = new LayResponse(this);
|
||||||
|
} catch (err) {
|
||||||
|
Logger.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
all(path, cb) {
|
let domain = url.hostname;
|
||||||
this.add(path, cb, 'all');
|
if([...this.routers.keys()].indexOf(domain) < 0) domain = '$default';
|
||||||
return this;
|
return await this.routers.get(domain).handle(reqContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
get(path, cb) {
|
add(path, cb) {
|
||||||
this.add(path, cb, 'get');
|
let options = arguments[2] ?? { method: 'get', domain: '$default' };
|
||||||
return this;
|
if(typeof options.domain != 'string') options.domain = '$default';
|
||||||
|
if (this.namespace) {
|
||||||
|
if (!path.startsWith('/')) path = '/' + path;
|
||||||
|
path = this.namespace + path;
|
||||||
}
|
}
|
||||||
|
|
||||||
post(path, cb) {
|
Logger(path, options)
|
||||||
this.add(path, cb, 'post');
|
|
||||||
return this;
|
if(options.domain === '$default') {
|
||||||
|
let domains = this.routers.keys();
|
||||||
|
for(let domain of [...domains]) {
|
||||||
|
this.routers.get(domain).add(path, cb, options);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(!this.routers.has(options.domain)) {
|
||||||
|
const domainRouter = Router;
|
||||||
|
domainRouter.domain = options.domain;
|
||||||
|
this.addRouter(domainRouter);
|
||||||
|
Logger('added new router for domain', options.domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
put(path, cb) {
|
this.routers.get(options.domain).add (path, cb, options);
|
||||||
this.add(path, cb, 'put');
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
patch(path, cb) {
|
|
||||||
this.add(path, cb, 'patch');
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(path, cb) {
|
|
||||||
this.add(path, cb, 'delete');
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
head(path, cb) {
|
|
||||||
this.add(path, cb, 'head');
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
options(path, cb) {
|
|
||||||
this.add(path, cb, 'options');
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
100
src/Routing/LayRouting.js~
Normal file
100
src/Routing/LayRouting.js~
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import Router from './Router';
|
||||||
|
import BaseRouter from './BaseRouter';
|
||||||
|
import LayRequest from '../IO/LayRequest';
|
||||||
|
import LayResponse from '../IO/LayResponse';
|
||||||
|
import Logger from '../Utils/Logger';
|
||||||
|
|
||||||
|
export default class LayRouting extends BaseRouter {
|
||||||
|
routers;
|
||||||
|
namespace = null;
|
||||||
|
server;
|
||||||
|
constructor(server, namespace = null) {
|
||||||
|
super();
|
||||||
|
this.server = server;
|
||||||
|
this.routers = new Map([
|
||||||
|
['$default', new Router(this.server)]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getServer() {
|
||||||
|
return this.server;
|
||||||
|
}
|
||||||
|
|
||||||
|
setNamespace(ns) {
|
||||||
|
if (!ns || typeof ns != 'string') this.namespace = null;
|
||||||
|
this.namespace = ns;
|
||||||
|
}
|
||||||
|
|
||||||
|
routes() {
|
||||||
|
return this.routers.get('$default').routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
addRouter(router) {
|
||||||
|
let routerInstance;
|
||||||
|
if(typeof router == 'undefined' || router === null) {
|
||||||
|
routerInstance = new Router(this.server);
|
||||||
|
} else {
|
||||||
|
routerInstance = new router(this.server);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.routers.set(router.domain || '$default', routerInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
route(config = {}) {
|
||||||
|
if (typeof config != 'object')
|
||||||
|
throw new Error('route config is not an object');
|
||||||
|
|
||||||
|
const { path, handle, method = 'get' } = config;
|
||||||
|
if (typeof path != 'string')
|
||||||
|
throw new Error('route path is not a string');
|
||||||
|
if (typeof handle != 'function')
|
||||||
|
throw new Error('route handle is not a function');
|
||||||
|
this.add(path, handle, method);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle(req) {
|
||||||
|
const url = new URL(req.url);
|
||||||
|
const reqContext = {
|
||||||
|
request: null,
|
||||||
|
response: null,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
reqContext.request = new LayRequest(req);
|
||||||
|
reqContext.response = new LayResponse(this);
|
||||||
|
} catch (err) {
|
||||||
|
Logger.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let domain = url.hostname;
|
||||||
|
if([...this.routers.keys()].indexOf(domain) < 0) domain = '$default';
|
||||||
|
return await this.routers.get(domain).handle(reqContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
add(path, cb) {
|
||||||
|
let options = arguments[2] ?? { method: 'get', domain: '$default' };
|
||||||
|
if(typeof options.domain != 'string') options.domain = '$default';
|
||||||
|
if (this.namespace) {
|
||||||
|
if (!path.startsWith('/')) path = '/' + path;
|
||||||
|
path = this.namespace + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger(path, options)
|
||||||
|
|
||||||
|
if(options.domain === '$default') {
|
||||||
|
let domains = this.routers.keys();
|
||||||
|
for(let domain of [...domains]) {
|
||||||
|
this.routers.get(domain).add(path, cb, options);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(!this.routers.has(options.domain)) {
|
||||||
|
const domainRouter = Router;
|
||||||
|
domainRouter.domain = options.domain;
|
||||||
|
this.addRouter(domainRouter);
|
||||||
|
Logger('added new router for domain', options.domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.routers.get(options.domain).add (path, cb, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import LayRequest from '../IO/LayRequest';
|
|
||||||
import LayResponse from '../IO/LayResponse';
|
|
||||||
import RequestParser from '../Utils/RequestParser';
|
import RequestParser from '../Utils/RequestParser';
|
||||||
|
import BaseRouter from './BaseRouter';
|
||||||
|
import Logger from '../Utils/Logger';
|
||||||
|
|
||||||
export default class Router {
|
export default class Router extends BaseRouter {
|
||||||
|
domain = undefined;
|
||||||
server = undefined;
|
server = undefined;
|
||||||
routes = new Map();
|
routes = new Map();
|
||||||
static allMethodKey = '$all';
|
static allMethodKey = '$all';
|
||||||
@@ -10,6 +11,7 @@ export default class Router {
|
|||||||
headerList = new Map();
|
headerList = new Map();
|
||||||
|
|
||||||
constructor(server) {
|
constructor(server) {
|
||||||
|
super();
|
||||||
this.server = server;
|
this.server = server;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,15 +19,11 @@ export default class Router {
|
|||||||
return this.server;
|
return this.server;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handle(req) {
|
async handle(reqContext) {
|
||||||
if (!(req instanceof Request)) {
|
let method = (reqContext.request.method || '').toString().toLowerCase();
|
||||||
throw new Error('`req` is not a proper Request');
|
|
||||||
}
|
|
||||||
|
|
||||||
let method = (req.method || '').toString().toLowerCase();
|
|
||||||
const methodKey = `$${method.toLowerCase()}`;
|
const methodKey = `$${method.toLowerCase()}`;
|
||||||
let path;
|
let path;
|
||||||
let url = req.url;
|
let url = reqContext.request.url;
|
||||||
|
|
||||||
if (!(url instanceof URL) && typeof url == 'string') {
|
if (!(url instanceof URL) && typeof url == 'string') {
|
||||||
url = new URL(url);
|
url = new URL(url);
|
||||||
@@ -78,16 +76,6 @@ export default class Router {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const reqContext = {
|
|
||||||
request: null,
|
|
||||||
response: null,
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
reqContext.request = new LayRequest(req);
|
|
||||||
reqContext.response = new LayResponse(this);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
let handle =
|
let handle =
|
||||||
currentNode.get(methodKey) || currentNode.get(Router.allMethodKey);
|
currentNode.get(methodKey) || currentNode.get(Router.allMethodKey);
|
||||||
|
|
||||||
@@ -124,7 +112,7 @@ export default class Router {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
Logger(err);
|
||||||
return new Response('Error', { status: 500 });
|
return new Response('Error', { status: 500 });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,21 +129,23 @@ export default class Router {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
add(route, callback, method = 'get', middleware = null) {
|
add(route, callback) {
|
||||||
|
let options = arguments[2];
|
||||||
|
let middleware = arguments[3];
|
||||||
if (typeof callback != 'function')
|
if (typeof callback != 'function')
|
||||||
throw new Error('`callback` is not a function');
|
throw new Error('`callback` is not a function');
|
||||||
if (typeof route != 'string')
|
if (typeof route != 'string')
|
||||||
throw new Error('`route` is not a string');
|
throw new Error('`route` is not a string');
|
||||||
if (Router.methods.indexOf(method) < 0)
|
if (Router.methods.indexOf(options.method) < 0)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`invalid \`method\` (${method}) given`,
|
`invalid \`method\` (${options.method}) given`,
|
||||||
Router.methods
|
Router.methods
|
||||||
);
|
);
|
||||||
if (!Array.isArray(middleware)) {
|
if (!Array.isArray(middleware)) {
|
||||||
middleware = typeof middleware == 'function' ? [middleware] : null;
|
middleware = typeof middleware == 'function' ? [middleware] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const methodKey = `$${method.toLowerCase()}`;
|
const methodKey = `$${options.method.toLowerCase()}`;
|
||||||
if (route.startsWith('/')) route = route.substring(1);
|
if (route.startsWith('/')) route = route.substring(1);
|
||||||
if (route.endsWith('/')) route = route.substring(0, route.length - 1);
|
if (route.endsWith('/')) route = route.substring(0, route.length - 1);
|
||||||
|
|
||||||
@@ -165,14 +155,14 @@ export default class Router {
|
|||||||
|
|
||||||
if (route == '') {
|
if (route == '') {
|
||||||
if (currentNode.get(methodKey)) {
|
if (currentNode.get(methodKey)) {
|
||||||
if (!this.server.option('allowOverwrite')) {
|
if (!this.server.option('allowRouteOverwrite')) {
|
||||||
console.log(
|
Logger(
|
||||||
`overwriting of routes is prohibited: [${method}] \`${route}\``
|
`overwriting of routes is prohibited: [${options.method}] \`${route}\``
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`overwriting route for \`${route}\``);
|
Logger(`overwriting route for \`${route}\``);
|
||||||
}
|
}
|
||||||
currentNode.set(methodKey, { callback, middleware, params: [] });
|
currentNode.set(methodKey, { callback, middleware, params: [] });
|
||||||
return;
|
return;
|
||||||
@@ -202,14 +192,14 @@ export default class Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (currentNode.has(methodKey)) {
|
if (currentNode.has(methodKey)) {
|
||||||
if (!this.server.option('allowOverwrite')) {
|
if (!this.server.option('allowRouteOverwrite')) {
|
||||||
console.log(
|
Logger(
|
||||||
`overwriting of routes is prohibited: [${method}] \`${route}\``
|
`overwriting of routes is prohibited: [${method}] \`${route}\``
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`overwriting route for \`${route}\``);
|
Logger(`overwriting route for \`${route}\``);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentNode.set(methodKey, { callback, middleware, params });
|
currentNode.set(methodKey, { callback, middleware, params });
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
export class Logger {
|
const LOGGING_TYPES = ['log', 'debug', 'error', 'warn', 'info'];
|
||||||
static info() {
|
function Logger (data, type = 'debug') {
|
||||||
const [message, data] = arguments;
|
if(!process.env.DEBUG) return;
|
||||||
|
if(LOGGING_TYPES.indexOf(type) < 0) type = 'log';
|
||||||
|
|
||||||
|
console[type](...(Array.isArray(data) ? data : [data]));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const k of LOGGING_TYPES) {
|
||||||
|
Logger.prototype[k] = function() {
|
||||||
|
Logger(arguments, k);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Logger;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import Logger from './Logger';
|
||||||
|
|
||||||
export default class Placekeeper {
|
export default class Placekeeper {
|
||||||
static KEYWORDS = ['if', 'for', 'while', 'else', 'elseif', 'end'];
|
|
||||||
static init(content) {
|
static init(content) {
|
||||||
const sections = Array();
|
const sections = Array();
|
||||||
const variables = new Map();
|
const variables = new Map();
|
||||||
@@ -43,13 +44,46 @@ export default class Placekeeper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(sections);
|
|
||||||
|
Logger('content to array', Placekeeper.contentToArray(content));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
variables,
|
variables,
|
||||||
sections,
|
sections,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static contentToArray(content) {
|
||||||
|
const parts = content.split(/\{\{([^}]+)\}\}|\{(#[a-zA-Z]+)\s*([^}]+)\}/);
|
||||||
|
const result = [];
|
||||||
|
let stack = [];
|
||||||
|
function processStack() {
|
||||||
|
const block = stack.pop();
|
||||||
|
result.push(new Function('data', 'with(data){return `'+ block.content.join('') + '`;}'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each part of the split content
|
||||||
|
for (let i = 0; i < parts.length; i++) {
|
||||||
|
if (parts[i] === '#' && ['for', 'if', 'else', 'elseif', 'while'].includes(parts[i + 1])) {
|
||||||
|
// Start of control structure
|
||||||
|
stack.push({ type: parts[i + 1], content: [] });
|
||||||
|
i += 2; // Skip the control structure identifier
|
||||||
|
} else if (parts[i] === '/#' && ['for', 'if', 'while'].includes(stack[stack.length - 1].type)) {
|
||||||
|
// End of control structure
|
||||||
|
i++;
|
||||||
|
processStack();
|
||||||
|
} else if (stack.length > 0) {
|
||||||
|
// Inside a control structure
|
||||||
|
stack[stack.length - 1].content.push(parts[i]);
|
||||||
|
} else if (parts[i] !== undefined && parts[i] !== '#end') {
|
||||||
|
// Directly executable expression or static string
|
||||||
|
result.push(parts[i].startsWith('\n') ? '\n' + parts[i].trimStart() : parts[i].trimEnd());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static variableRegex = new RegExp(/[\w\d]/, 'i');
|
static variableRegex = new RegExp(/[\w\d]/, 'i');
|
||||||
|
|
||||||
static parseVariables(content) {
|
static parseVariables(content) {
|
||||||
@@ -87,7 +121,7 @@ export default class Placekeeper {
|
|||||||
let parts = [];
|
let parts = [];
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
for (const section of sections) {
|
for (const section of sections) {
|
||||||
if (!section instanceof Map) {
|
if (!(section instanceof Map)) {
|
||||||
if (!Array.isArray(section)) continue;
|
if (!Array.isArray(section)) continue;
|
||||||
if (section.length < 3) continue;
|
if (section.length < 3) continue;
|
||||||
}
|
}
|
||||||
|
|||||||
135
src/Utils/Placekeeper.js~
Normal file
135
src/Utils/Placekeeper.js~
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import Logger from './Logger';
|
||||||
|
|
||||||
|
export default class Placekeeper {
|
||||||
|
static init(content) {
|
||||||
|
const sections = Array();
|
||||||
|
const variables = new Map();
|
||||||
|
const currentSection = new Map();
|
||||||
|
|
||||||
|
for (let idx = 0; idx < content.length; idx++) {
|
||||||
|
const char = content[idx];
|
||||||
|
if (char == '{') {
|
||||||
|
if (content[idx + 1] == '{') {
|
||||||
|
if (currentSection.has('start')) currentSection.clear(); // everything before this was not a section then
|
||||||
|
// begins a new section meant to be for a variable but still can be an operational section
|
||||||
|
// @example: {{#if variableName}} {{#for variableName}} {{#while variableName}}
|
||||||
|
currentSection.set('start', idx);
|
||||||
|
}
|
||||||
|
} else if (currentSection.has('start')) {
|
||||||
|
if (char == '}') {
|
||||||
|
if (content[idx + 1] == '}') {
|
||||||
|
// ends a section
|
||||||
|
currentSection.set('end', idx + 2);
|
||||||
|
currentSection.set(
|
||||||
|
'content',
|
||||||
|
content
|
||||||
|
.substring(
|
||||||
|
currentSection.get('start'),
|
||||||
|
currentSection.get('end')
|
||||||
|
)
|
||||||
|
.toString()
|
||||||
|
.trim()
|
||||||
|
);
|
||||||
|
|
||||||
|
currentSection.set(
|
||||||
|
'variables',
|
||||||
|
Placekeeper.parseVariables(
|
||||||
|
currentSection.get('content')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
sections.push(new Map(currentSection));
|
||||||
|
currentSection.clear();
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger('content to array', Placekeeper.contentToArray(content));
|
||||||
|
|
||||||
|
return {
|
||||||
|
variables,
|
||||||
|
sections,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static contentToArray(content) {
|
||||||
|
const parts = content.split(/\{\{([^}]+)\}\}|\{(#[a-zA-Z]+)\s*([^}]+)\}/);
|
||||||
|
const result = [];
|
||||||
|
let stack = [];
|
||||||
|
function processStack() {
|
||||||
|
const block = stack.pop();
|
||||||
|
result.push(new Function('data', 'with(data){return `'+ block.content.join('') + '`;}'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each part of the split content
|
||||||
|
for (let i = 0; i < parts.length; i++) {
|
||||||
|
if (parts[i] === '#' && ['for', 'if', 'else', 'elseif', 'while'].includes(parts[i + 1])) {
|
||||||
|
// Start of control structure
|
||||||
|
stack.push({ type: parts[i + 1], content: [] });
|
||||||
|
i += 2; // Skip the control structure identifier
|
||||||
|
} else if (parts[i] === '/#' && ['for', 'if', 'while'].includes(stack[stack.length - 1].type)) {
|
||||||
|
// End of control structure
|
||||||
|
i++;
|
||||||
|
processStack();
|
||||||
|
} else if (stack.length > 0) {
|
||||||
|
// Inside a control structure
|
||||||
|
stack[stack.length - 1].content.push(parts[i]);
|
||||||
|
} else if (parts[i] !== undefined && parts[i] !== '#end') {
|
||||||
|
// Directly executable expression or static string
|
||||||
|
result.push(parts[i].startsWith('\n') ? '\n' + parts[i].trimStart() : parts[i].trimEnd());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static variableRegex = new RegExp(/[\w\d]/, 'i');
|
||||||
|
|
||||||
|
static parseVariables(content) {
|
||||||
|
// iterates over the content character by character to extract variables and returns a list of them
|
||||||
|
const variables = new Map();
|
||||||
|
let currentVar;
|
||||||
|
|
||||||
|
for (let idx = 0; idx < content.length; idx++) {
|
||||||
|
const char = content[idx];
|
||||||
|
if (Placekeeper.variableRegex.test(char)) {
|
||||||
|
if (!(currentVar instanceof Map))
|
||||||
|
currentVar = new Map([
|
||||||
|
['name', ''],
|
||||||
|
['start', idx],
|
||||||
|
['end', -1],
|
||||||
|
]);
|
||||||
|
currentVar.set('name', currentVar.get('name') + char);
|
||||||
|
} else if (currentVar instanceof Map) {
|
||||||
|
currentVar.set('end', idx);
|
||||||
|
if (currentVar.get('name').length > 0) {
|
||||||
|
variables.set(currentVar.get('name'), currentVar);
|
||||||
|
}
|
||||||
|
currentVar = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static parse(content, sections) {
|
||||||
|
// replace all sections with an empty string
|
||||||
|
if (!Array.isArray(sections)) {
|
||||||
|
if (!sections instanceof Map) return content;
|
||||||
|
sections = [...sections.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
let parts = [];
|
||||||
|
let offset = 0;
|
||||||
|
for (const section of sections) {
|
||||||
|
if (!(section instanceof Map)) {
|
||||||
|
if (!Array.isArray(section)) continue;
|
||||||
|
if (section.length < 3) continue;
|
||||||
|
}
|
||||||
|
parts.push(content.substring(offset, section.get('start')));
|
||||||
|
offset = section.get('end');
|
||||||
|
}
|
||||||
|
parts.push(content.substring(offset));
|
||||||
|
|
||||||
|
return parts.join('');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,12 @@
|
|||||||
|
import Logger from './Logger';
|
||||||
export async function parseFormData(req, multipart = false) {
|
export async function parseFormData(req, multipart = false) {
|
||||||
const data = await req.formData();
|
const data = await req.formData();
|
||||||
|
|
||||||
// console.log(tempFile, new Response(tempFile).headers.get('Content-Type'));
|
|
||||||
// console.log(upload.type, upload.type == tempFile.type);
|
|
||||||
|
|
||||||
for (const key of data.keys()) {
|
for (const key of data.keys()) {
|
||||||
const value = data.get(key);
|
const value = data.get(key);
|
||||||
if (value.constructor.name == 'Blob' && multipart) {
|
if (value.constructor.name == 'Blob' && multipart) {
|
||||||
// const ext = value.name.toString().split('.').pop().toLowerCase();
|
let tempFile = Bun.file(`/tmp/${Date.now().toString(36)}`, { type: value.type });
|
||||||
const tempFilePath = `/tmp/${Date.now().toString(36)}`;
|
data.set(key, await Bun.write(tempFile, value));
|
||||||
await Bun.write(tempFilePath, value);
|
|
||||||
let tempFile = Bun.file(tempFilePath, { type: value.type });
|
|
||||||
console.log(
|
|
||||||
tempFile,
|
|
||||||
new Response(tempFile).headers.get('Content-Type')
|
|
||||||
);
|
|
||||||
console.log(value.type, value.type == tempFile.type);
|
|
||||||
// data.set(key, await Bun.write())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,13 +17,9 @@ export default async function RequestParser(req) {
|
|||||||
try {
|
try {
|
||||||
const contentType = req.headers.get('Content-Type')?.toLowerCase();
|
const contentType = req.headers.get('Content-Type')?.toLowerCase();
|
||||||
|
|
||||||
let val = await req.text();
|
|
||||||
console.log(val);
|
|
||||||
return {};
|
|
||||||
|
|
||||||
if (!contentType) return await req.text();
|
if (!contentType) return await req.text();
|
||||||
|
|
||||||
if (contentType.startsWith('multipart/form-data'))
|
if (contentType.indexOf('multipart') >= 0)
|
||||||
return await parseFormData(req, true);
|
return await parseFormData(req, true);
|
||||||
if (contentType == 'application/x-www-form-urlencoded')
|
if (contentType == 'application/x-www-form-urlencoded')
|
||||||
return await parseFormData(req);
|
return await parseFormData(req);
|
||||||
@@ -41,6 +27,6 @@ export default async function RequestParser(req) {
|
|||||||
|
|
||||||
return await req.text();
|
return await req.text();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err, typeof req, req.constructor.name);
|
Logger(err, typeof req, req.constructor.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/Utils/RequestParser.js~
Normal file
36
src/Utils/RequestParser.js~
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import Logger from './Logger';
|
||||||
|
export async function parseFormData(req, multipart = false) {
|
||||||
|
const data = await req.formData();
|
||||||
|
|
||||||
|
for (const key of data.keys()) {
|
||||||
|
const value = data.get(key);
|
||||||
|
if (value.constructor.name == 'Blob' && multipart) {
|
||||||
|
let tempFile = Bun.file(`/tmp/${Date.now().toString(36)}`, { type: value.type });
|
||||||
|
data.set(key, await Bun.write(tempFile, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function RequestParser(req) {
|
||||||
|
try {
|
||||||
|
const contentType = req.headers.get('Content-Type')?.toLowerCase();
|
||||||
|
|
||||||
|
let val = await req.text();
|
||||||
|
console.log(val);
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (!contentType) return await req.text();
|
||||||
|
|
||||||
|
if (contentType.indexOf('multipart') >= 0)
|
||||||
|
return await parseFormData(req, true);
|
||||||
|
if (contentType == 'application/x-www-form-urlencoded')
|
||||||
|
return await parseFormData(req);
|
||||||
|
if (contentType == 'application/json') return await req.json();
|
||||||
|
|
||||||
|
return await req.text();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err, typeof req, req.constructor.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user