@webtypen/webframez-core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ name: Node.js Package
2
+
3
+ on:
4
+ release:
5
+ types: [created]
6
+
7
+ jobs:
8
+ publish-gpr:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v3
12
+ - uses: actions/setup-node@v3
13
+ with:
14
+ node-version: 18
15
+ registry-url: https://npm.pkg.github.com/
16
+ - run: npm install
17
+ - run: npm publish
18
+ env:
19
+ NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
20
+ publish-npm:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v3
24
+ - uses: actions/setup-node@v3
25
+ with:
26
+ node-version: 18
27
+ registry-url: https://registry.npmjs.org/
28
+ - run: npm install
29
+ - run: npm publish --access public
30
+ env:
31
+ NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # webtypen webframez Docs
2
+
3
+ ## Controller
4
+
5
+ ### Register a controller
6
+
7
+ A new controller is registered via the `controller` attribute in `app/Kernel.ts`. Just add a new entry for the controller to the object. The key of the object is the unique name of the controller, this is used again when connecting routes.
8
+
9
+ The controllers are initialized once when the web service is started. When calling a route, a single controller instance is created per request and thrown away after the request finishes.
10
+
11
+ ## Routing
12
+
13
+ ### Register a route
14
+
15
+ Routes are managed via the app/routes.ts. Routes can be registered and configured with the Route Facade. Routes can be associated strictly with a request method (GET, POST, PUT, DELETE) or all request methods (ANY):
16
+
17
+ ```ts
18
+ Route.get("/", "TestController@test");
19
+ Route.post("/test2", "TestController@test");
20
+ Route.put("/test2", "TestController@test");
21
+ Route.delete("/test2", "TestController@test");
22
+ Route.any("/test2", "TestController@test");
23
+ ```
24
+
25
+ The URL path is specified in the first parameter. The second parameter connects the route to a controller method. A previously registered controller can be connected using the syntax `[CONTROLLER]@[METHODE]`.
26
+
27
+ Alternatively, a function can be specified directly or a previously imported one can be used:
28
+
29
+ ```ts
30
+ // Directly
31
+ Route.get("/", (req: Request, res: Response) =>
32
+ res.send({ status: "success" })
33
+ );
34
+
35
+ // Imported
36
+ import { myFunction } from "./myfunction";
37
+ Route.get("/", myFunction);
38
+ ```
39
+
40
+ ### Register a middleware
41
+
42
+ Middleware functions are also registered in `app/Kernel.ts.` A middleware function can be associated with one or more routes. When calling these routes, the middleware function is called before executing the route logic. In this function, the request can be continued or aborted, for example, authorization rules can be implemented and easily reused for multiple routes.
43
+
44
+ Middleware functions are usually created under `app/Middleware`. A middleware function is called with the parameters (next, abort, req and res):
45
+
46
+ - `next`: Function that signals when called that the request may be continued
47
+ - `abort`: Function that aborts the request when called. An HTTP status code can be specified in the first parameter and a request body (usually text or JSON) in the second.
48
+ - `req`: The Request-Object
49
+ - `res`: The Response-Object
50
+
51
+ Alternatively, middleware functions can also be defined directly in Kernel.ts:
52
+
53
+ ```ts
54
+ static middleware: { [key: string]: any } = {
55
+ auth: async (next: any, abort: any, req: Request, res: Response) => {
56
+ console.log("auth-middleware");
57
+ next();
58
+ },
59
+ };
60
+ ```
61
+
62
+ A route can be connected to several middleware functions, which are called one after the other but before executing the route logic.
63
+
64
+ ### Connect a route with a middleware
65
+
66
+ When registering a route, a configuration object can be passed in the third parameter. This can contain an array with middleware keys in the `middleware` parameter. The middlewares specified here are executed prior to executing the route logic when invoking this route. If a specified middleware aborts the request, it will not be executed:
67
+
68
+ ```ts
69
+ Route.get("/auth-data", "AuthController@authData", { middleware: ["auth"] });
70
+ ```
71
+
72
+ ### Group routes
73
+
74
+ With the grouping of routes, the code of the application can be significantly reduced and kept clear. Here's how the following configurations can be used with a multiple route definition:
75
+
76
+ - `prefix`: A route path prefix used for all routes in this group:
77
+
78
+ ```ts
79
+ Route.group({ prefix: "/admin", () => {
80
+ Route.get("/dashboard", "AdminDashboardController@data");
81
+ // Path for this route would be: /admin/dashboard
82
+ }});
83
+ ```
84
+
85
+ - `middleware`: An array of middleware-keys used for all routes in this group:
86
+
87
+ ```ts
88
+ Route.group({ middleware: ["auth"], () => {
89
+ // Route with middleware auth
90
+ Route.get("/auth-data", "AuthController@authData");
91
+
92
+ // Route with middleware auth and admin
93
+ Route.get("/dashboard", "AdminDashboardController@data", { middleware: ["admin"] });
94
+ }});
95
+ ```
96
+
97
+ The configuration parameters of a route group can of course be combined with each other.
98
+
99
+ Nesting of the route groups is also possible:
100
+
101
+ ```ts
102
+ Route.group({ prefix: "/test" }, () => {
103
+ Route.get("/", "TestController@test"); // Path: /test/
104
+ Route.get("/test2", "TestController@test"); // Path: /test2/
105
+
106
+ Route.group({ middleware: ["auth"], prefix: "/auth" }, () => {
107
+ Route.get("/data", "AuthController@data"); // Path: /test/auth/data; Middleware: auth
108
+ });
109
+ });
110
+ ```
111
+
112
+ ## Database
113
+
114
+ ### Connect
115
+
116
+ The application can handle multiple database connections, but one is defined as the default connection and is automatically used by the system if models and database requests are not explicitly executed over another connection.
117
+
118
+ The connections are configured in the `config/database.ts` file. Each connection is given a unique key in the `connections` object and uses a driver. The following drivers are currently available:
119
+
120
+ - MongoDB Driver
121
+
122
+ Depending on the driver, the database connection must be configured with different parameters.
123
+
124
+ **MongoDB Connection example:**
125
+
126
+ ```ts
127
+ export default {
128
+ defaultConnection: "default",
129
+ connections: {
130
+ ["default"]: {
131
+ driver: "mongodb",
132
+ url: "mongodb://...",
133
+ },
134
+ },
135
+ };
136
+ ```
137
+
138
+ ### Model
139
+
140
+ Models are usually placed under `app/Models`. Each model class inherits from `Model` and must store the table / collection name, in which the models data sets are stored, in the `__table` attribute.
141
+
142
+ ```ts
143
+ import { Model } from "@webtypen/webframez-core";
144
+
145
+ export class Test extends Model {
146
+ __table = "test_table";
147
+ }
148
+ ```
package/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export * from "./src/BaseKernel";
2
+ export * from "./src/Controller/Controller";
3
+ export * from "./src/WebApplication";
4
+ export * from "./src/Router/Route";
5
+ export * from "./src/Router/Request";
6
+ export * from "./src/Router/Response";
7
+ export * from "./src/Database/Model";
8
+ export * from "./src/Database/QueryBuilder";
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@webtypen/webframez-core",
3
+ "version": "0.0.1",
4
+ "description": "The core package of the typescript based webframez (web-framework) by webtypen.de",
5
+ "homepage": "https://webtypen.de/",
6
+ "license": "MIT",
7
+ "main": "index.js",
8
+ "author": {
9
+ "name": "Benedict Romp",
10
+ "email": "b.romp@webtypen.de",
11
+ "url": "https://webtypen.de/"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/webtypen/webframez-core/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/webtypen/webframez-core.git"
19
+ },
20
+ "dependencies": {
21
+ "dotenv": "^8.2.0",
22
+ "lodash": "^4.17.20"
23
+ }
24
+ }
@@ -0,0 +1,7 @@
1
+ import { Controller } from "./Controller/Controller";
2
+
3
+ export class BaseKernel {
4
+ static commands = [];
5
+ static controller: { [key: string]: Controller } = {};
6
+ static middleware: { [key: string]: any };
7
+ }
@@ -0,0 +1 @@
1
+ export class Controller {}
@@ -0,0 +1,22 @@
1
+ import { Model } from "./Model";
2
+ import { QueryBuilder } from "./QueryBuilder";
3
+
4
+ export class BaseDBDriver {
5
+ config?: object;
6
+
7
+ setConfig(config: object) {
8
+ this.config = config;
9
+ }
10
+
11
+ async connect() {}
12
+
13
+ async close(client: any) {}
14
+
15
+ async handleQueryBuilder(client: any, queryBuilder: QueryBuilder) {}
16
+
17
+ async execute(client: any, data: any) {}
18
+
19
+ async onModelSave(model: Model, saveStatus: any | null | undefined) {
20
+ return model;
21
+ }
22
+ }
@@ -0,0 +1,103 @@
1
+ import { DBDrivers } from "./DBDriver";
2
+ import dbconfig from "../../../../config/database";
3
+ import { QueryBuilder } from "./QueryBuilder";
4
+
5
+ class DBConnectionFacade {
6
+ connections: { [key: string]: any } = {};
7
+
8
+ getConnectionConfig(connection: string) {
9
+ if (!connection) {
10
+ connection = dbconfig.defaultConnection;
11
+ }
12
+
13
+ if (
14
+ !connection ||
15
+ !dbconfig ||
16
+ !dbconfig.connections ||
17
+ !dbconfig.connections[connection as keyof {}]
18
+ ) {
19
+ throw new Error("No connection given ...");
20
+ }
21
+
22
+ return dbconfig.connections[connection as keyof {}];
23
+ }
24
+
25
+ getConnectionDriver(connection: string) {
26
+ const config = this.getConnectionConfig(connection);
27
+ if (!config || !config["driver"]) {
28
+ return null;
29
+ }
30
+
31
+ const driverClass = DBDrivers.get(config["driver"]);
32
+ const driver = new driverClass();
33
+ driver.setConfig(config);
34
+ return driver;
35
+ }
36
+
37
+ async getConnection(connectionName?: string) {
38
+ if (!connectionName) {
39
+ connectionName = dbconfig.defaultConnection;
40
+ }
41
+
42
+ if (
43
+ this.connections[connectionName] &&
44
+ this.connections[connectionName].driver
45
+ ) {
46
+ // Use cached connection
47
+ return this.connections[connectionName];
48
+ }
49
+
50
+ // Create new connection and cache it
51
+ const driver = this.getConnectionDriver(connectionName);
52
+ const client = await driver.connect();
53
+
54
+ this.connections[connectionName] = {
55
+ driver: driver,
56
+ client: client,
57
+ };
58
+
59
+ return this.connections[connectionName];
60
+ }
61
+
62
+ async runQuery(query: QueryBuilder, options?: { [name: string]: any }) {
63
+ const connection = await this.getConnection();
64
+ const data = await connection.driver.handleQueryBuilder(
65
+ connection.client,
66
+ query
67
+ );
68
+
69
+ if (options && options.raw) {
70
+ return data;
71
+ }
72
+
73
+ if (!data) {
74
+ return null;
75
+ }
76
+
77
+ // @ToDo: Model-Mapping
78
+ return data;
79
+ }
80
+
81
+ async execute(data: any, connectionName?: string) {
82
+ const connection = await this.getConnection(connectionName);
83
+
84
+ return await connection.driver.execute(connection.client, data);
85
+ }
86
+
87
+ async getDriver(connectionName?: string) {
88
+ const connection = await this.getConnection(connectionName);
89
+ return connection.driver;
90
+ }
91
+
92
+ mapDataToModel(model: any, data: any) {
93
+ const obj = new model();
94
+ if (data) {
95
+ for (let i in data) {
96
+ obj[i] = data[i];
97
+ }
98
+ }
99
+ return obj;
100
+ }
101
+ }
102
+
103
+ export const DBConnection = new DBConnectionFacade();
@@ -0,0 +1,13 @@
1
+ export class DBDriversFacade {
2
+ drivers: { [key: string]: any } = {};
3
+
4
+ register(name: string, component: any) {
5
+ this.drivers[name] = component;
6
+ }
7
+
8
+ get(name: string) {
9
+ return this.drivers[name];
10
+ }
11
+ }
12
+
13
+ export const DBDrivers = new DBDriversFacade();
@@ -0,0 +1,135 @@
1
+ import { DBConnection } from "./DBConnection";
2
+ import { QueryBuilder } from "./QueryBuilder";
3
+
4
+ export class Model {
5
+ [key: string]: any | undefined; // allow-custom-properties
6
+ __primaryKey = "_id";
7
+ __table: string = "";
8
+ __connection: string | undefined = undefined;
9
+ __hidden: string[] = [];
10
+ __unmapped: string[] = [];
11
+ __unmappedSystem: string[] = [
12
+ "__primaryKey",
13
+ "__table",
14
+ "__connection",
15
+ "__unmapped",
16
+ "__unmappedSystem",
17
+ "__is_deleted",
18
+ "__hidden",
19
+ ];
20
+
21
+ /**
22
+ * Creates a new query-builder object and adds a where-clause
23
+ * @param column
24
+ * @param operator
25
+ * @param value
26
+ * @returns QueryBulder
27
+ */
28
+ static where(column: any, operator: any, value: any): QueryBuilder {
29
+ const model = new this();
30
+ const queryBuilder = new QueryBuilder();
31
+ queryBuilder.setModelMapping(this);
32
+ queryBuilder.table(model.__table);
33
+ queryBuilder.where(column, operator, value);
34
+
35
+ return queryBuilder;
36
+ }
37
+
38
+ /**
39
+ * Executes a database-aggregation (mostly used by no-sql connections)
40
+ * @param aggregation
41
+ * @returns any
42
+ */
43
+ static async aggregate(aggregation: any) {
44
+ const model = new this();
45
+ return await DBConnection.execute(
46
+ {
47
+ type: "aggregation",
48
+ table: model.__table,
49
+ aggregation: aggregation,
50
+ },
51
+ model.__connection
52
+ );
53
+ }
54
+
55
+ /**
56
+ * Returns the model-data without system- and unmapped-fields (new js-object)
57
+ * @returns object
58
+ */
59
+ getModelData(): object {
60
+ const out: { [key: string]: any } = {};
61
+ for (let i in this) {
62
+ if (!this.__unmapped.includes(i) && !this.__unmappedSystem.includes(i)) {
63
+ out[i] = this[i];
64
+ }
65
+ }
66
+ return out;
67
+ }
68
+
69
+ /**
70
+ * Returns the model-data without system-, unmapped- and hidden-fields (new js-object)
71
+ * @returns object
72
+ */
73
+ toArray(): object {
74
+ const out: { [key: string]: any } = {};
75
+ for (let i in this) {
76
+ if (
77
+ !this.__unmapped.includes(i) &&
78
+ !this.__unmappedSystem.includes(i) &&
79
+ !this.__hidden.includes(i)
80
+ ) {
81
+ out[i] = this[i];
82
+ }
83
+ }
84
+ return out;
85
+ }
86
+
87
+ /**
88
+ * Saves the object in the database
89
+ * @returns Model
90
+ */
91
+ async save() {
92
+ let status = null;
93
+ if (this[this.__primaryKey] !== undefined && this[this.__primaryKey]) {
94
+ // Update
95
+ status = await DBConnection.execute({
96
+ type: "updateOne",
97
+ table: this.__table,
98
+ primaryKey: this.__primaryKey,
99
+ filter: { [this.__primaryKey]: this[this.__primaryKey] },
100
+ data: this.getModelData(),
101
+ });
102
+ } else {
103
+ // Insert
104
+ status = await DBConnection.execute({
105
+ type: "insertOne",
106
+ table: this.__table,
107
+ data: this.getModelData(),
108
+ });
109
+ }
110
+
111
+ // onModelSave
112
+ const driver = await DBConnection.getDriver(this.__connection);
113
+ return driver.onModelSave(this, status);
114
+ }
115
+
116
+ /**
117
+ * Deletes the object from the database
118
+ * @returns boolean
119
+ */
120
+ async delete() {
121
+ if (!this[this.__primaryKey]) {
122
+ return false;
123
+ }
124
+
125
+ const queryBuilder = new QueryBuilder();
126
+ queryBuilder.setModelMapping(this);
127
+ queryBuilder.table(this.__table);
128
+ queryBuilder.where(this.__primaryKey, "=", this[this.__primaryKey]);
129
+ await queryBuilder.delete({ mode: "deleteOne" });
130
+
131
+ this.__is_deleted = true;
132
+
133
+ return true;
134
+ }
135
+ }
@@ -0,0 +1,65 @@
1
+ import { DBConnection } from "./DBConnection";
2
+ import { Model } from "./Model";
3
+
4
+ export class QueryBuilder {
5
+ database: string | null = null;
6
+ query: any[] = [];
7
+ queryTable: string = "";
8
+ mode = "get";
9
+ modelMapping = null;
10
+
11
+ setModelMapping(model?: any | null) {
12
+ this.modelMapping = model;
13
+ }
14
+
15
+ table(table: string) {
16
+ this.queryTable = table;
17
+ }
18
+
19
+ where(column: any, operator: any, value: any) {
20
+ this.query.push({
21
+ type: "where",
22
+ column: column,
23
+ operator: operator,
24
+ value: value,
25
+ });
26
+ }
27
+
28
+ async get(options?: any) {
29
+ this.mode = "get";
30
+
31
+ const result = await DBConnection.runQuery(this);
32
+ if (options && options.disableModelMapping) {
33
+ return result && result.length > 0 ? result : null;
34
+ }
35
+
36
+ return result && result.length > 0 && this.modelMapping !== null
37
+ ? result.map((el: Model) =>
38
+ DBConnection.mapDataToModel(this.modelMapping, el)
39
+ )
40
+ : result && result.length > 0
41
+ ? result
42
+ : null;
43
+ }
44
+
45
+ async first(options?: any) {
46
+ this.mode = "first";
47
+
48
+ const result = await DBConnection.runQuery(this);
49
+ if (options && options.disableModelMapping) {
50
+ return result ? result : null;
51
+ }
52
+
53
+ return result && this.modelMapping !== null
54
+ ? DBConnection.mapDataToModel(this.modelMapping, result)
55
+ : null;
56
+ }
57
+
58
+ async delete(options?: { mode: string }) {
59
+ this.mode =
60
+ options && options.mode === "deleteOne" ? "deleteOne" : "delete";
61
+
62
+ await DBConnection.runQuery(this);
63
+ return true;
64
+ }
65
+ }
@@ -0,0 +1,12 @@
1
+ export type Request = {
2
+ body?: any;
3
+ bodyPlain?: any;
4
+ params: object;
5
+ httpVersionMajor: any;
6
+ httpVersionMinor: any;
7
+ httpVersion: any;
8
+ headers: object;
9
+ rawHeaders: object;
10
+ url: string;
11
+ method: string;
12
+ };
@@ -0,0 +1,61 @@
1
+ import { ServerResponse } from "http";
2
+
3
+ export class Response {
4
+ res?: ServerResponse;
5
+
6
+ /**
7
+ * Sets the ServerResponse (res) Object (nodejs/http module)
8
+ * @param res
9
+ * @returns
10
+ */
11
+ setServerResponse(res: ServerResponse) {
12
+ this.res = res;
13
+ return this;
14
+ }
15
+
16
+ /**
17
+ * Set the http-status-code
18
+ *
19
+ * @param status
20
+ * @returns
21
+ */
22
+ status(status: number): Response {
23
+ if (!this.res) {
24
+ return this;
25
+ }
26
+
27
+ this.res.statusCode = status;
28
+ return this;
29
+ }
30
+
31
+ /**
32
+ * Set a header attribute: Content-Type: application/json
33
+ *
34
+ * @param type
35
+ * @param value
36
+ * @returns
37
+ */
38
+ header(type: string, value: string) {
39
+ if (!this.res) {
40
+ return this;
41
+ }
42
+
43
+ this.res.setHeader(type, value);
44
+ return this;
45
+ }
46
+
47
+ /**
48
+ * Sends data to the client
49
+ * Any objects will be stringified to JSON
50
+ *
51
+ * @param content
52
+ */
53
+ send(content: any) {
54
+ if (typeof content === "object") {
55
+ this.res?.setHeader("Content-Type", "application/json");
56
+ this.res?.write(JSON.stringify(content));
57
+ } else {
58
+ this.res?.write(content);
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,159 @@
1
+ import { Router } from "./Router";
2
+
3
+ export class RouteFacade {
4
+ tempGroupPrefix: string | null = null;
5
+ tempGroupMiddleware: string[] | null = null;
6
+
7
+ /**
8
+ * Register a GET-Method
9
+ *
10
+ * @param path
11
+ * @param component
12
+ * @param options
13
+ */
14
+ get(path: string, component: any, options?: { [key: string]: any }) {
15
+ Router.register(
16
+ "GET",
17
+ (this.tempGroupPrefix ? this.tempGroupPrefix : "") + path,
18
+ component,
19
+ options || this.tempGroupMiddleware
20
+ ? {
21
+ ...(options ? options : {}),
22
+ middleware:
23
+ this.tempGroupMiddleware || (options && options.middleware)
24
+ ? [
25
+ ...(this.tempGroupMiddleware
26
+ ? this.tempGroupMiddleware
27
+ : []),
28
+ ...(options && options.middleware
29
+ ? options.middleware
30
+ : []),
31
+ ]
32
+ : null,
33
+ }
34
+ : {}
35
+ );
36
+ }
37
+
38
+ /**
39
+ * Register a POST-Method
40
+ *
41
+ * @param path
42
+ * @param component
43
+ * @param options
44
+ */
45
+ post(path: string, component: any, options?: { [key: string]: any }) {
46
+ Router.register(
47
+ "POST",
48
+ (this.tempGroupPrefix ? this.tempGroupPrefix : "") + path,
49
+ component,
50
+ options || this.tempGroupMiddleware
51
+ ? {
52
+ ...(options ? options : {}),
53
+ middleware:
54
+ this.tempGroupMiddleware || (options && options.middleware)
55
+ ? [
56
+ ...(this.tempGroupMiddleware
57
+ ? this.tempGroupMiddleware
58
+ : []),
59
+ ...(options && options.middleware
60
+ ? options.middleware
61
+ : []),
62
+ ]
63
+ : null,
64
+ }
65
+ : {}
66
+ );
67
+ }
68
+
69
+ /**
70
+ * Register a PUT-Method
71
+ *
72
+ * @param path
73
+ * @param component
74
+ * @param options
75
+ */
76
+ put(path: string, component: any, options?: { [key: string]: any }) {
77
+ Router.register(
78
+ "PUT",
79
+ (this.tempGroupPrefix ? this.tempGroupPrefix : "") + path,
80
+ component,
81
+ options || this.tempGroupMiddleware
82
+ ? {
83
+ ...(options ? options : {}),
84
+ middleware:
85
+ this.tempGroupMiddleware || (options && options.middleware)
86
+ ? [
87
+ ...(this.tempGroupMiddleware
88
+ ? this.tempGroupMiddleware
89
+ : []),
90
+ ...(options && options.middleware
91
+ ? options.middleware
92
+ : []),
93
+ ]
94
+ : null,
95
+ }
96
+ : {}
97
+ );
98
+ }
99
+
100
+ /**
101
+ * Register a DELETE-Method
102
+ *
103
+ * @param path
104
+ * @param component
105
+ * @param options
106
+ */
107
+ delete(path: string, component: any, options?: { [key: string]: any }) {
108
+ Router.register(
109
+ "DELETE",
110
+ (this.tempGroupPrefix ? this.tempGroupPrefix : "") + path,
111
+ component,
112
+ options || this.tempGroupMiddleware
113
+ ? {
114
+ ...(options ? options : {}),
115
+ middleware:
116
+ this.tempGroupMiddleware || (options && options.middleware)
117
+ ? [
118
+ ...(this.tempGroupMiddleware
119
+ ? this.tempGroupMiddleware
120
+ : []),
121
+ ...(options && options.middleware
122
+ ? options.middleware
123
+ : []),
124
+ ]
125
+ : null,
126
+ }
127
+ : {}
128
+ );
129
+ }
130
+
131
+ group(config: { [key: string]: any }, children: any) {
132
+ // Handle group prefix
133
+ if (config && config.prefix && config.prefix.trim() !== "") {
134
+ this.tempGroupPrefix =
135
+ (this.tempGroupPrefix ? this.tempGroupPrefix : "") + config.prefix;
136
+ }
137
+
138
+ // Handle group middleware
139
+ if (config && config.middleware && config.middleware.length > 0) {
140
+ if (!this.tempGroupMiddleware || this.tempGroupMiddleware.length === 0) {
141
+ this.tempGroupMiddleware = config.middleware;
142
+ } else {
143
+ this.tempGroupMiddleware = [
144
+ ...this.tempGroupMiddleware,
145
+ ...config.middleware,
146
+ ];
147
+ }
148
+ }
149
+
150
+ // Handle children / group body
151
+ children();
152
+
153
+ // Reset temp-group data
154
+ this.tempGroupPrefix = null;
155
+ this.tempGroupMiddleware = null;
156
+ }
157
+ }
158
+
159
+ export const Route = new RouteFacade();
@@ -0,0 +1,295 @@
1
+ import qs from "qs";
2
+ import { IncomingMessage, ServerResponse } from "http";
3
+ import { Response } from "./Response";
4
+ import { Kernel } from "../../../../app/Kernel";
5
+
6
+ type RouteObject = {
7
+ path: string;
8
+ component: any;
9
+ options: { [key: string]: any };
10
+ params: object;
11
+ };
12
+
13
+ class RouterFacade {
14
+ /**
15
+ * GET Route Store
16
+ */
17
+ routesGET: {
18
+ [key: string]: {
19
+ path: string;
20
+ component: any;
21
+ options: object;
22
+ params?: object;
23
+ };
24
+ } = {};
25
+
26
+ /**
27
+ * POST Route Store
28
+ */
29
+ routesPOST: {
30
+ [key: string]: {
31
+ path: string;
32
+ component: any;
33
+ options: object;
34
+ params?: object;
35
+ };
36
+ } = {};
37
+
38
+ /**
39
+ * PUT Route Store
40
+ */
41
+ routesPUT: {
42
+ [key: string]: {
43
+ path: string;
44
+ component: any;
45
+ options: object;
46
+ params?: object;
47
+ };
48
+ } = {};
49
+
50
+ /**
51
+ * DELETE Route Store
52
+ */
53
+ routesDELETE: {
54
+ [key: string]: {
55
+ path: string;
56
+ component: any;
57
+ options: object;
58
+ params?: object;
59
+ };
60
+ } = {};
61
+
62
+ /**
63
+ * Load the application-routes
64
+ *
65
+ */
66
+ init() {
67
+ // Load routes
68
+ require(process.cwd() + "/app/routes");
69
+ }
70
+
71
+ /**
72
+ * Register a route
73
+ *
74
+ * @param type
75
+ * @param path
76
+ * @param component
77
+ * @param options
78
+ */
79
+ register(type: string, path: string, component: any, options: object) {
80
+ if (type === "GET") {
81
+ this.routesGET[path] = {
82
+ path: path,
83
+ component: component,
84
+ options: options,
85
+ };
86
+ } else if (type === "POST") {
87
+ this.routesPOST[path] = {
88
+ path: path,
89
+ component: component,
90
+ options: options,
91
+ };
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Loads the matching route based on the request-url
97
+ *
98
+ * @param req
99
+ * @returns
100
+ */
101
+ dissolve(req: IncomingMessage): RouteObject | null {
102
+ if (!req.url) {
103
+ return null;
104
+ }
105
+
106
+ if (req.method === "GET") {
107
+ return this.dissolveRoute(this.routesGET, req.url);
108
+ } else if (req.method === "POST") {
109
+ return this.dissolveRoute(this.routesPOST, req.url);
110
+ } else if (req.method === "PUT") {
111
+ return this.dissolveRoute(this.routesPUT, req.url);
112
+ } else if (req.method === "DELETE") {
113
+ return this.dissolveRoute(this.routesDELETE, req.url);
114
+ }
115
+ return null;
116
+ }
117
+
118
+ /**
119
+ * Dissolves the matching route based on the request-url and an object of routes
120
+ *
121
+ * @param routes
122
+ * @param url
123
+ * @returns
124
+ */
125
+ dissolveRoute(routes: any, url: string) {
126
+ if (routes[url]) {
127
+ // If url matches directly -> instant return
128
+ return { ...routes[url], params: {} };
129
+ } else {
130
+ // Try to find the matching route: Run through the slash-separated parts of the route
131
+ const partsRequest = url.split("/");
132
+
133
+ for (let path in routes) {
134
+ let status = true;
135
+ const parts = path.split("/");
136
+ const params: { [key: string]: any } = {};
137
+
138
+ for (let i in partsRequest) {
139
+ if (partsRequest[i] !== parts[i]) {
140
+ // Handle URL-Params
141
+ if (parts[i] && parts[i].trim().substring(0, 1) === ":") {
142
+ params[parts[i].trim().substring(1).toString()] = partsRequest[i];
143
+ continue;
144
+ }
145
+
146
+ status = false;
147
+ continue;
148
+ }
149
+ }
150
+
151
+ if (status) {
152
+ return { ...routes[path], params: params };
153
+ }
154
+ }
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Sends the default 404-Page
160
+ *
161
+ * @param res
162
+ */
163
+ sendError(res: ServerResponse, code: number, message?: string) {
164
+ res.writeHead(code, { "Content-Type": "text/html" });
165
+ res.end(
166
+ message
167
+ ? message
168
+ : code === 404
169
+ ? "Oops! Page not found ..."
170
+ : "Oops! There was an unexpected error ..."
171
+ );
172
+ }
173
+
174
+ /**
175
+ * Processes a request
176
+ *
177
+ * @param req
178
+ * @param res
179
+ */
180
+ async handleRequest(req: IncomingMessage, res: ServerResponse) {
181
+ const route = this.dissolve(req);
182
+
183
+ // Check-Middleware
184
+ if (
185
+ route &&
186
+ route.options &&
187
+ route.options.middleware &&
188
+ route.options.middleware.length > 0
189
+ ) {
190
+ for (let middlewareKey of route.options.middleware) {
191
+ const middleware = Kernel.middleware[middlewareKey];
192
+ if (!middleware) {
193
+ return this.sendError(
194
+ res,
195
+ 500,
196
+ "Unknown middleware `" + middlewareKey + "` ..."
197
+ );
198
+ }
199
+
200
+ try {
201
+ await new Promise((resolve, reject) => {
202
+ try {
203
+ middleware(
204
+ resolve,
205
+ (status: number, data: any) => {
206
+ reject({ status: status, data: data });
207
+ },
208
+ req,
209
+ res
210
+ );
211
+ } catch (e) {
212
+ console.error(e);
213
+
214
+ return this.sendError(
215
+ res,
216
+ 500,
217
+ "Error in middleware `" + middlewareKey + "` ..."
218
+ );
219
+ }
220
+ });
221
+ } catch (e: any) {
222
+ // Send Middleware-Abort
223
+ res.writeHead(
224
+ e.status,
225
+ typeof e.data === "object"
226
+ ? { "Content-Type": "application/json" }
227
+ : { "Content-Type": "text/html" }
228
+ );
229
+ res.end(
230
+ typeof e.data === "object"
231
+ ? JSON.stringify(e.data)
232
+ : e.data
233
+ ? e.data
234
+ : " "
235
+ );
236
+ return;
237
+ }
238
+ }
239
+ }
240
+
241
+ // Resolve route-component (handle controller definition)
242
+ if (route && typeof route.component === "string") {
243
+ const split = route.component.split("@");
244
+ const controller = Kernel.controller[split[0]];
245
+ if (!controller) {
246
+ res.writeHead(500, { "Content-Type": "text/html" });
247
+ res.end("Controller `" + split[0] + "` not found ...");
248
+ return;
249
+ }
250
+
251
+ // Set Controller method as route-component
252
+ const controllerInstance = new controller();
253
+ route.component = controllerInstance[split[1]];
254
+ }
255
+
256
+ if (route && route.component !== undefined) {
257
+ let body: any = [];
258
+ const bodyPlain: any = await new Promise((resolve: Function) => {
259
+ req
260
+ .on("data", (chunk) => {
261
+ body.push(chunk);
262
+ })
263
+ .on("end", () => {
264
+ resolve(Buffer.concat(body).toString());
265
+ });
266
+ });
267
+
268
+ try {
269
+ await route.component(
270
+ {
271
+ body: bodyPlain ? qs.parse(bodyPlain) : {},
272
+ bodyPlain: bodyPlain,
273
+ params: route.params,
274
+ httpVersionMajor: req.httpVersionMajor,
275
+ httpVersionMinor: req.httpVersionMinor,
276
+ httpVersion: req.httpVersion,
277
+ headers: req.headers,
278
+ rawHeaders: req.rawHeaders,
279
+ url: req.url,
280
+ method: req.method,
281
+ },
282
+ new Response().setServerResponse(res)
283
+ );
284
+ res.end();
285
+ } catch (e) {
286
+ console.error(e);
287
+ this.sendError(res, 500);
288
+ }
289
+ } else {
290
+ this.sendError(res, 404);
291
+ }
292
+ }
293
+ }
294
+
295
+ export const Router = new RouterFacade();
@@ -0,0 +1,21 @@
1
+ import http, { Server } from "http";
2
+ import { Router } from "./Router/Router";
3
+
4
+ export class WebApplication {
5
+ private server!: Server;
6
+
7
+ /**
8
+ * Init the routes and start the http-server
9
+ */
10
+ boot() {
11
+ Router.init();
12
+
13
+ this.server = http
14
+ .createServer((req, res) => {
15
+ Router.handleRequest(req, res);
16
+ })
17
+ .listen(3000, () => {
18
+ console.log("Server started and listening on port 3000");
19
+ });
20
+ }
21
+ }