nibula 2.4.7 → 3.0.0

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.
Files changed (51) hide show
  1. package/.eleventy.js +0 -7
  2. package/CHANGELOG.md +48 -2
  3. package/bin/create.js +43 -45
  4. package/bin/nibula.js +0 -6
  5. package/nginx.conf +1 -1
  6. package/package.json +3 -7
  7. package/src/backend/.htaccess +2 -2
  8. package/src/backend/core/index.js +329 -0
  9. package/src/backend/core/index.php +207 -0
  10. package/src/backend/core/init.js +37 -0
  11. package/src/backend/core/init.php +54 -0
  12. package/src/backend/core/modules/RateLimiter.js +55 -0
  13. package/src/backend/core/modules/RateLimiter.php +39 -0
  14. package/src/backend/{_core → core}/modules/Response.js +15 -10
  15. package/src/backend/{_core → core}/modules/Response.php +14 -17
  16. package/src/backend/core/modules/Validate.js +56 -0
  17. package/src/backend/core/modules/Validate.php +52 -0
  18. package/src/backend/endpoints/protected/example-protected.js +39 -0
  19. package/src/backend/endpoints/protected/example-protected.php +47 -0
  20. package/src/backend/endpoints/public/example-public.js +39 -0
  21. package/src/backend/endpoints/public/example-public.php +47 -0
  22. package/src/backend/example.config.js +7 -6
  23. package/src/backend/example.config.php +8 -7
  24. package/src/backend/package.json +3 -3
  25. package/src/backend/web.config +2 -2
  26. package/src/frontend/hosting/.htaccess +1 -1
  27. package/src/frontend/hosting/web.config +1 -1
  28. package/src/frontend/layouts/base.njk +3 -7
  29. package/src/frontend/scss/{_global.scss → global.scss} +0 -2
  30. package/src/frontend/scss/modules/frameworks/_bootstrap.scss +0 -2
  31. package/src/frontend/scss/modules/frameworks/_bulma.scss +0 -2
  32. package/src/frontend/scss/pages/404.scss +0 -3
  33. package/src/frontend/scss/pages/homepage.scss +0 -3
  34. package/tools/config/settings.json +1 -13
  35. package/tools/lib/pagesData.js +13 -3
  36. package/tools/res/templates/template.scss +0 -3
  37. package/src/backend/_core/index.js +0 -230
  38. package/src/backend/_core/index.php +0 -118
  39. package/src/backend/_core/init.js +0 -32
  40. package/src/backend/_core/init.php +0 -30
  41. package/src/backend/_core/modules/RateLimiter.js +0 -47
  42. package/src/backend/_core/modules/RateLimiter.php +0 -31
  43. package/src/backend/api/protected/example-protected.js +0 -16
  44. package/src/backend/api/protected/example-protected.php +0 -17
  45. package/src/backend/api/public/example-public.js +0 -16
  46. package/src/backend/api/public/example-public.php +0 -17
  47. package/src/frontend/scss/modules/frameworks/_foundation.scss +0 -138
  48. package/src/frontend/scss/modules/frameworks/_uikit.scss +0 -110
  49. package/tools/buildJs.js +0 -39
  50. /package/src/backend/{_core → core}/composer.json +0 -0
  51. /package/src/backend/{_core → core}/composer.lock +0 -0
@@ -0,0 +1,47 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ require_once CORE_PATH . '/modules/Response.php';
6
+ require_once CORE_PATH . '/modules/Validate.php';
7
+
8
+ /**
9
+ * Return one closure per HTTP method you want to support. Any other method
10
+ * gets an automatic 405, so you never have to check the method yourself.
11
+ *
12
+ * Each closure receives $request, an array with:
13
+ * method the HTTP method, as a string
14
+ * requestParams extra URL segments after the endpoint path
15
+ * query query string values: ?page=2 gives ['page' => '2']
16
+ * body the JSON body, already decoded into an array
17
+ * rawBody the body as a raw string, if you need it
18
+ * headers the request headers
19
+ * config everything from config.php
20
+ *
21
+ * Response::success($data, $code) and Response::error($message, $code) send the
22
+ * answer and stop the request: nothing after them runs.
23
+ */
24
+
25
+ return [
26
+ 'get' => function (array $request) {
27
+ Response::success([
28
+ 'message' => 'Endpoint is working',
29
+ 'params' => $request['requestParams'],
30
+ 'query' => $request['query'],
31
+ ]);
32
+ },
33
+
34
+ 'post' => function (array $request) {
35
+ $body = $request['body'];
36
+
37
+ // Validate:: stops the request with a 422 when a value is missing or
38
+ // malformed, so the returned value is always safe to use.
39
+ $name = Validate::required($body['name'] ?? null, 'name');
40
+ $email = Validate::email($body['email'] ?? null, 'email');
41
+
42
+ Response::success([
43
+ 'name' => $name,
44
+ 'email' => $email,
45
+ ], 201);
46
+ },
47
+ ];
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Export one function per HTTP method you want to support. Any other method
5
+ * gets an automatic 405, so you never have to check the method yourself.
6
+ *
7
+ * Each function receives the request context:
8
+ * method the HTTP method, as a string
9
+ * requestParams extra URL segments after the endpoint path
10
+ * query query string values: ?page=2 gives { page: '2' }
11
+ * body the JSON body, already parsed into an object
12
+ * rawBody the body as a raw string, if you need it
13
+ * headers the request headers
14
+ * config everything from config.js
15
+ * Response sends the answer
16
+ * Validate checks incoming values
17
+ *
18
+ * Response.success(data, code) and Response.error(message, code) send the
19
+ * answer and stop the request: nothing after them runs.
20
+ */
21
+
22
+ module.exports = {
23
+ get: ({ requestParams, query, Response }) => {
24
+ Response.success({
25
+ message: "Endpoint is working",
26
+ params: requestParams,
27
+ query: query,
28
+ });
29
+ },
30
+
31
+ post: ({ body, Response, Validate }) => {
32
+ // Validate stops the request with a 422 when a value is missing or
33
+ // malformed, so the returned value is always safe to use.
34
+ const name = Validate.required(body.name, "name");
35
+ const email = Validate.email(body.email, "email");
36
+
37
+ Response.success({ name, email }, 201);
38
+ },
39
+ };
@@ -0,0 +1,47 @@
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ require_once CORE_PATH . '/modules/Response.php';
6
+ require_once CORE_PATH . '/modules/Validate.php';
7
+
8
+ /**
9
+ * Return one closure per HTTP method you want to support. Any other method
10
+ * gets an automatic 405, so you never have to check the method yourself
11
+ *
12
+ * Each closure receives $request, an array with:
13
+ * method the HTTP method, as a string
14
+ * requestParams extra URL segments after the endpoint path
15
+ * query query string values: ?page=2 gives ['page' => '2']
16
+ * body the JSON body, already decoded into an array
17
+ * rawBody the body as a raw string, if you need it
18
+ * headers the request headers
19
+ * config everything from config.php
20
+ *
21
+ * Response::success($data, $code) and Response::error($message, $code) send the
22
+ * answer and stop the request: nothing after them runs
23
+ */
24
+
25
+ return [
26
+ 'get' => function (array $request) {
27
+ Response::success([
28
+ 'message' => 'Endpoint is working',
29
+ 'params' => $request['requestParams'],
30
+ 'query' => $request['query'],
31
+ ]);
32
+ },
33
+
34
+ 'post' => function (array $request) {
35
+ $body = $request['body'];
36
+
37
+ // Validate:: stops the request with a 422 when a value is missing or
38
+ // malformed, so the returned value is always safe to use
39
+ $name = Validate::required($body['name'] ?? null, 'name');
40
+ $email = Validate::email($body['email'] ?? null, 'email');
41
+
42
+ Response::success([
43
+ 'name' => $name,
44
+ 'email' => $email,
45
+ ], 201);
46
+ },
47
+ ];
@@ -1,12 +1,16 @@
1
1
  "use strict";
2
2
 
3
3
  /**
4
- * Mirror of src/backend/example.config.php
5
- *
6
- * Copy this file to config.js and fill in real values. config.js is git-ignored and stays local.
4
+ * Template for config.php, which holds the real values and is git-ignored.
5
+ * If config.js is missing, copy this file over it.
7
6
  */
8
7
 
9
8
  module.exports = {
9
+
10
+ // Environment: "production" hides error details from anyone calling your API
11
+ // Switch to "debug" while you build to see full error messages
12
+ APP_ENV: "production",
13
+
10
14
  // Default key for protected endpoints that don't have a specific key in CUSTOM_ENDPOINT_KEYS
11
15
  GENERAL_API_KEY: "DEFAULT_KEY",
12
16
 
@@ -30,7 +34,4 @@ module.exports = {
30
34
  DB_NAME: "example_db",
31
35
  DB_USER: "root",
32
36
  DB_PASS: "",
33
-
34
- // Environment: 'production' hides error details; anything else = debug.
35
- APP_ENV: "production",
36
37
  };
@@ -2,12 +2,16 @@
2
2
  declare(strict_types=1);
3
3
 
4
4
  /**
5
- * Mirror of src/backend/example.config.php
6
- *
7
- * Copy this file to config.php and fill in real values. config.php is git-ignored and stays local.
5
+ * Template for config.php, which holds the real values and is git-ignored.
6
+ * If config.php is missing, copy this file over it.
8
7
  */
9
8
 
10
9
  return [
10
+
11
+ // Environment: 'production' hides error details from anyone calling your API
12
+ // Switch to 'debug' while you build to see full error messages
13
+ 'APP_ENV' => 'production',
14
+
11
15
  // Default key for protected endpoints that don't have a specific key in CUSTOM_ENDPOINT_KEYS
12
16
  'GENERAL_API_KEY' => 'DEFAULT_KEY',
13
17
 
@@ -30,8 +34,5 @@ return [
30
34
  'DB_HOST' => '127.0.0.1',
31
35
  'DB_NAME' => 'example_db',
32
36
  'DB_USER' => 'root',
33
- 'DB_PASS' => '',
34
-
35
- // Environment: 'production' hides error details; anything else = debug.
36
- 'APP_ENV' => 'production',
37
+ 'DB_PASS' => ''
37
38
  ];
@@ -2,11 +2,11 @@
2
2
  "name": "backend-node",
3
3
  "version": "1.0.0",
4
4
  "private": true,
5
- "description": "Node.js backend for a Nibula site. Coexists with the PHP backend; pick one at scaffold time. Front controller: _core/index.js.",
6
- "main": "_core/index.js",
5
+ "description": "Node.js backend for a Nibula site. Coexists with the PHP backend; pick one at scaffold time. Front controller: core/index.js.",
6
+ "main": "core/index.js",
7
7
  "type": "commonjs",
8
8
  "scripts": {
9
- "start": "node _core/index.js"
9
+ "start": "node core/index.js"
10
10
  },
11
11
  "engines": {
12
12
  "node": ">=18"
@@ -19,13 +19,13 @@
19
19
  <rules>
20
20
 
21
21
  <rule name="PassThroughCoreIndex" stopProcessing="true">
22
- <match url="^_core/index\.php$" ignoreCase="true" />
22
+ <match url="^core/index\.php$" ignoreCase="true" />
23
23
  <action type="None" />
24
24
  </rule>
25
25
 
26
26
  <rule name="RewriteToFrontController" stopProcessing="true">
27
27
  <match url="^(.*)$" ignoreCase="true" />
28
- <action type="Rewrite" url="_core/index.php" appendQueryString="true" />
28
+ <action type="Rewrite" url="core/index.php" appendQueryString="true" />
29
29
  </rule>
30
30
 
31
31
  </rules>
@@ -40,7 +40,7 @@ ErrorDocument 403 /404.html
40
40
  <IfModule mod_rewrite.c>
41
41
  RewriteEngine On
42
42
  RewriteBase /
43
- RewriteRule ^api/(.*)$ backend/_core/index.php [QSA,L]
43
+ RewriteRule ^api/(.*)$ backend/core/index.php [QSA,L]
44
44
  </IfModule>
45
45
 
46
46
  # === Static files ===========================================================
@@ -27,7 +27,7 @@
27
27
  <!-- PHP backend: rewrite /api to the PHP front controller. -->
28
28
  <rule name="ApiToPhp" stopProcessing="true">
29
29
  <match url="^api/(.*)" ignoreCase="true" />
30
- <action type="Rewrite" url="backend/_core/index.php" appendQueryString="true" />
30
+ <action type="Rewrite" url="backend/core/index.php" appendQueryString="true" />
31
31
  </rule>
32
32
 
33
33
  <!-- Node backend (requires ARR). Delete "ApiToPhp" above to use this.
@@ -86,6 +86,9 @@
86
86
  {# Syntax highlighting for code blocks #}
87
87
  <link rel="stylesheet" href="{{ '/css/prism.css' | url }}">
88
88
 
89
+ {# Global CSS #}
90
+ <link rel="stylesheet" href="{{ '/css/global.css' | url }}"/>
91
+
89
92
  {# Page CSS #}
90
93
  <link rel="stylesheet" href="{{ ('/css/pages/' + title + '.css') | url }}"/>
91
94
 
@@ -116,13 +119,6 @@
116
119
  {# Bootstrap JS #}
117
120
  <script src="{{ '/js/bootstrap.bundle.min.js' | url }}" defer></script>
118
121
 
119
- {# Foundation JS #}
120
- {# <script src="{{ '/js/foundation.min.js' | url }}" defer></script> #}
121
-
122
- {# UIkit JS #}
123
- {# <script src="{{ '/js/uikit.min.js' | url }}" defer></script> #}
124
- {# <script src="{{ '/js/uikit-icons.min.js' | url }}" defer></script> #}
125
-
126
122
  {# Bulma — no JS needed #}
127
123
 
128
124
  {# Per-page CDN JS #}
@@ -4,8 +4,6 @@
4
4
  // Frameworks imports
5
5
  @import "modules/frameworks/bootstrap";
6
6
  // @import "modules/frameworks/bulma";
7
- // @import "modules/frameworks/foundation";
8
- // @import "modules/frameworks/uikit";
9
7
 
10
8
  // Markdown styling. Applies to content rendered with renderFile
11
9
  @import "modules/markdown";
@@ -5,8 +5,6 @@
5
5
  // ╚══════════════════════════════════════════════════════╝
6
6
 
7
7
  // Imports can be filtered by commenting them
8
- // Example:
9
- // @import "bootstrap/scss/example-bootstrap-module";
10
8
 
11
9
  //==========================
12
10
  // 1️⃣ CORE – Functions and variables
@@ -5,8 +5,6 @@
5
5
  // ╚══════════════════════════════════════════════════════╝
6
6
 
7
7
  // Imports can be filtered by commenting them
8
- // Example:
9
- // @import "bulma/sass/example-bulma-module";
10
8
 
11
9
  //==========================
12
10
  // 1️⃣ CORE – Utilities and variables
@@ -1,9 +1,6 @@
1
1
  // Getting root rules from _root.scss
2
2
  @use "../root" as root; // Usage: root.$value-name
3
3
 
4
- // This global module contains the import framework, necessary modules for each page and any other common styles
5
- @import "../global";
6
-
7
4
  // Import any other module you need by import down here
8
5
  // @import "../modules/moduleName";
9
6
 
@@ -1,9 +1,6 @@
1
1
  // Getting root rules from _root.scss
2
2
  @use "../root" as root; // Usage: root.$value-name
3
3
 
4
- // This global module contains the import framework, necessary modules for each page and any other common styles
5
- @import "../global";
6
-
7
4
  // Import any other module you need by import down here
8
5
  // @import "../modules/moduleName";
9
6
 
@@ -71,19 +71,7 @@
71
71
  "styleTemplate": "template.scss",
72
72
  "routeTemplate": "template.njk",
73
73
  "layoutExtension": "njk",
74
- "defaultData": {
75
- "seo": {
76
- "title": "{title}",
77
- "description": "Description",
78
- "keywords": "",
79
- "noindex": false,
80
- "canonical": ""
81
- },
82
- "cdn": {
83
- "css": [],
84
- "js": []
85
- }
86
- }
74
+ "dataTemplate": "template.json"
87
75
  },
88
76
  "frontMatter": {
89
77
  "title": "title: \"{camelName}\"",
@@ -1,7 +1,7 @@
1
1
  const path = require('path');
2
2
  const settings = require('../config/settings.json');
3
3
  const { PATHS } = require('./paths');
4
- const { exists, readText, writeJson } = require('./files');
4
+ const { exists, readText, readJson, writeJson } = require('./files');
5
5
  const { log } = require('./logger');
6
6
  const { toCamelCase, toTitleCase, formatValue } = require('./text');
7
7
 
@@ -28,7 +28,14 @@ function writePagesData(data) {
28
28
  }
29
29
 
30
30
  function createRecord(pageName) {
31
- return formatValue(settings.page.defaultData, { title: toTitleCase(pageName) });
31
+ const templateFile = path.join(PATHS.templates, settings.page.dataTemplate);
32
+
33
+ if (!exists(templateFile)) {
34
+ log('page.templateMissing', { path: templateFile });
35
+ return null;
36
+ }
37
+
38
+ return formatValue(readJson(templateFile), { title: toTitleCase(pageName) });
32
39
  }
33
40
 
34
41
  function addPageData(pageName) {
@@ -41,7 +48,10 @@ function addPageData(pageName) {
41
48
  return;
42
49
  }
43
50
 
44
- data[camelName] = createRecord(pageName);
51
+ const record = createRecord(pageName);
52
+ if (!record) return;
53
+
54
+ data[camelName] = record;
45
55
 
46
56
  writePagesData(data);
47
57
  log('pagesData.recordAdded', { name: camelName });
@@ -1,9 +1,6 @@
1
1
  // Getting root rules from _root.scss
2
2
  @use "../root" as root; // Usage: root.$value-name
3
3
 
4
- // This global module contains the import framework, necessary modules for each page and any other common styles
5
- @import "../global";
6
-
7
4
  // Import any other module you need by import down here
8
5
  // @import "../modules/moduleName";
9
6
 
@@ -1,230 +0,0 @@
1
- "use strict";
2
-
3
- const http = require("http");
4
- const fs = require("fs");
5
- const path = require("path");
6
- const crypto = require("crypto");
7
-
8
- const { createResponse, HALT } = require("./modules/Response");
9
- const RateLimiter = require("./modules/RateLimiter");
10
- const { loadConfig, handleException } = require("./init");
11
-
12
- const CORE_PATH = __dirname;
13
-
14
- const config = loadConfig();
15
-
16
- const APP_ENV = config.APP_ENV || "production";
17
-
18
- const DOCUMENT_ROOT =
19
- process.env.DOCUMENT_ROOT || path.join(CORE_PATH, "..", "..");
20
-
21
- const basePublic = path.join(CORE_PATH, "..", "api", "public");
22
- const baseProtected = path.join(CORE_PATH, "..", "api", "protected");
23
-
24
- function hashEquals(known, given) {
25
- const a = Buffer.from(String(known));
26
- const b = Buffer.from(String(given));
27
- if (a.length !== b.length) {
28
- return false;
29
- }
30
- return crypto.timingSafeEqual(a, b);
31
- }
32
-
33
- function send404(res) {
34
- res.statusCode = 404;
35
- const errorPage = DOCUMENT_ROOT ? path.join(DOCUMENT_ROOT, "404.html") : "";
36
- if (errorPage && fs.existsSync(errorPage)) {
37
- res.setHeader("Content-Type", "text/html; charset=UTF-8");
38
- res.end(fs.readFileSync(errorPage));
39
- } else {
40
- res.setHeader("Content-Type", "text/html; charset=UTF-8");
41
- res.end(
42
- "<h1>404 Not Found</h1>The requested URL was not found on this server.",
43
- );
44
- }
45
- }
46
-
47
- function readBody(req) {
48
- return new Promise((resolve) => {
49
- const chunks = [];
50
- req.on("data", (c) => chunks.push(c));
51
- req.on("end", () => {
52
- const raw = Buffer.concat(chunks).toString("utf8");
53
- if (!raw) return resolve({ raw: "", json: null });
54
- let json = null;
55
- try {
56
- json = JSON.parse(raw);
57
- } catch (e) {
58
- json = null;
59
- }
60
- resolve({ raw, json });
61
- });
62
- req.on("error", () => resolve({ raw: "", json: null }));
63
- });
64
- }
65
-
66
- async function handleRequest(req, res) {
67
- const Response = createResponse(res);
68
-
69
- try {
70
- const method = req.method;
71
- const parsedUrl = new URL(req.url, "http://localhost");
72
- let uri = parsedUrl.pathname;
73
-
74
- uri = uri.replace(/^\/api/, "");
75
- uri = uri.replace(/\/+$/, "");
76
- if (uri === "") uri = "/";
77
-
78
- const parts = uri.split("/").filter((p) => p !== "");
79
-
80
- for (const part of parts) {
81
- if (part === ".." || part === ".") {
82
- Response.error("Bad Request", 400);
83
- }
84
- }
85
-
86
- let endpointFile = null;
87
- let isProtected = false;
88
-
89
- const checkParts = parts.slice();
90
- const params = [];
91
-
92
- while (checkParts.length > 0) {
93
- const relativePath = checkParts.join("/") + ".js";
94
-
95
- const publicCandidate = path.join(basePublic, relativePath);
96
- if (fs.existsSync(publicCandidate)) {
97
- endpointFile = publicCandidate;
98
- isProtected = false;
99
- break;
100
- }
101
-
102
- const protectedCandidate = path.join(baseProtected, relativePath);
103
- if (fs.existsSync(protectedCandidate)) {
104
- endpointFile = protectedCandidate;
105
- isProtected = true;
106
- break;
107
- }
108
-
109
- params.unshift(checkParts.pop());
110
- }
111
-
112
- if (!endpointFile) {
113
- send404(res);
114
- return;
115
- }
116
-
117
- const base = isProtected ? baseProtected : basePublic;
118
- let endpointPath = endpointFile
119
- .split(path.sep)
120
- .join("/")
121
- .replace(base.split(path.sep).join("/"), "")
122
- .replace(/\.js$/, "")
123
- .replace(/^\//, "");
124
-
125
- res.setHeader("Content-Type", "application/json; charset=UTF-8");
126
- res.setHeader("X-Content-Type-Options", "nosniff");
127
- res.setHeader("X-Frame-Options", "DENY");
128
- res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
129
- res.setHeader(
130
- "Access-Control-Allow-Methods",
131
- "GET, POST, PUT, PATCH, DELETE, OPTIONS",
132
- );
133
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Api-Key");
134
-
135
- const originsForEndpoint =
136
- (config.CUSTOM_ENDPOINT_ORIGINS &&
137
- config.CUSTOM_ENDPOINT_ORIGINS[endpointPath]) ||
138
- config.GENERAL_ALLOWED_ORIGINS ||
139
- [];
140
-
141
- const origin = req.headers["origin"] || "";
142
-
143
- if (origin !== "" && originsForEndpoint.includes(origin)) {
144
- res.setHeader("Access-Control-Allow-Origin", origin);
145
- res.setHeader("Vary", "Origin");
146
- } else if (originsForEndpoint.includes("*")) {
147
- res.setHeader("Access-Control-Allow-Origin", "*");
148
- }
149
-
150
- if (method === "OPTIONS") {
151
- res.statusCode = 204;
152
- res.end();
153
- return;
154
- }
155
-
156
- // Rate limit: 60 requests / 60 seconds per IP
157
- const ip =
158
- (req.headers["x-forwarded-for"] || "").split(",")[0].trim() ||
159
- req.socket.remoteAddress ||
160
- "";
161
- RateLimiter.check(ip, 60, 60, Response, res);
162
-
163
- if (isProtected) {
164
- const validKey =
165
- (config.CUSTOM_ENDPOINT_KEYS &&
166
- config.CUSTOM_ENDPOINT_KEYS[endpointPath]) ||
167
- config.GENERAL_API_KEY ||
168
- "";
169
- const apiKey = req.headers["x-api-key"] || "";
170
-
171
- if (validKey === "" || !hashEquals(validKey, apiKey)) {
172
- Response.error("Unauthorized. X_API_KEY is incorrect or missing", 403);
173
- }
174
- }
175
-
176
- const handler = require(endpointFile);
177
-
178
- const context = {
179
- method, // HTTP method
180
- requestParams: params, // leftover URL segments (route params)
181
- Response, // request-bound Response helper
182
- query: Object.fromEntries(parsedUrl.searchParams), // parsed query string
183
- body: body.json, // parsed JSON body (or null)
184
- rawBody: body.raw, // raw body string
185
- headers: req.headers,
186
- req,
187
- res,
188
- config,
189
- CORE_PATH,
190
- };
191
-
192
- await handler(context);
193
-
194
- if (!res.writableEnded) {
195
- res.end();
196
- }
197
- } catch (err) {
198
- if (err === HALT) {
199
- return;
200
- }
201
- try {
202
- handleException(err, config, Response);
203
- } catch (inner) {
204
- if (inner === HALT) return;
205
- if (!res.writableEnded) {
206
- res.statusCode = 500;
207
- res.end(
208
- JSON.stringify({
209
- status: "error",
210
- message: "Internal server error",
211
- code: 500,
212
- }),
213
- );
214
- }
215
- }
216
- }
217
- }
218
-
219
- const HOST = process.env.HOST || "127.0.0.1";
220
- const PORT = parseInt(process.env.PORT || "3000", 10);
221
-
222
- const server = http.createServer(handleRequest);
223
-
224
- server.listen(PORT, HOST, () => {
225
- console.log(
226
- `[backend-node] listening on http://${HOST}:${PORT} (env: ${APP_ENV})`,
227
- );
228
- });
229
-
230
- module.exports = server;