nibula 1.6.0 → 1.6.2

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 (99) hide show
  1. package/.eleventy.js +82 -81
  2. package/CHANGELOG.md +395 -377
  3. package/LICENSE +169 -169
  4. package/NOTICE +4 -4
  5. package/README.md +110 -110
  6. package/bin/create.js +540 -505
  7. package/bin/delegate.js +58 -57
  8. package/bin/nibula.js +302 -302
  9. package/docs/Assistant CLI.md +65 -65
  10. package/docs/Backend.md +201 -201
  11. package/docs/Components.md +140 -140
  12. package/docs/Creating pages.md +69 -69
  13. package/docs/Deploy.md +210 -210
  14. package/docs/Head and SEO.md +139 -139
  15. package/docs/Javascript.md +126 -126
  16. package/docs/Styling with SCSS.md +141 -139
  17. package/nginx.conf +113 -113
  18. package/package.json +69 -69
  19. package/src/backend/.htaccess +6 -6
  20. package/src/backend/_core/composer.json +5 -5
  21. package/src/backend/_core/composer.lock +492 -492
  22. package/src/backend/_core/index.js +267 -267
  23. package/src/backend/_core/index.php +160 -160
  24. package/src/backend/_core/init.js +52 -52
  25. package/src/backend/_core/init.php +33 -33
  26. package/src/backend/_core/modules/RateLimiter.js +58 -58
  27. package/src/backend/_core/modules/RateLimiter.php +30 -30
  28. package/src/backend/_core/modules/Response.js +59 -59
  29. package/src/backend/_core/modules/Response.php +48 -48
  30. package/src/backend/api/protected/example-protected.js +23 -23
  31. package/src/backend/api/protected/example-protected.php +16 -16
  32. package/src/backend/api/public/example-public.js +24 -24
  33. package/src/backend/api/public/example-public.php +16 -16
  34. package/src/backend/database/Database.js +46 -46
  35. package/src/backend/database/Database.php +23 -23
  36. package/src/backend/example.config.js +37 -37
  37. package/src/backend/example.config.php +37 -37
  38. package/src/backend/package.json +17 -17
  39. package/src/backend/web.config +43 -43
  40. package/src/frontend/.htaccess +51 -51
  41. package/src/frontend/404.njk +17 -17
  42. package/src/frontend/assets/brand/favicon.svg +54 -54
  43. package/src/frontend/assets/brand/logo.svg +54 -54
  44. package/src/frontend/components/global/footer.njk +25 -25
  45. package/src/frontend/components/global/header.njk +6 -6
  46. package/src/frontend/components/welcome.njk +115 -115
  47. package/src/frontend/index.njk +8 -8
  48. package/src/frontend/js/global.js +10 -10
  49. package/src/frontend/js/modules/exampleModule.js +2 -2
  50. package/src/frontend/js/pages/404.js +8 -8
  51. package/src/frontend/js/pages/homepage.js +8 -8
  52. package/src/frontend/layouts/base.njk +135 -136
  53. package/src/frontend/layouts/page-components.njk +13 -13
  54. package/src/frontend/llms.njk +17 -17
  55. package/src/frontend/robots.njk +7 -7
  56. package/src/frontend/scss/_global.scss +46 -46
  57. package/src/frontend/scss/_root.scss +13 -13
  58. package/src/frontend/scss/modules/_animations.scss +24 -24
  59. package/src/frontend/scss/modules/_footer.scss +28 -28
  60. package/src/frontend/scss/modules/_header.scss +26 -26
  61. package/src/frontend/scss/modules/_mobile.scss +29 -29
  62. package/src/frontend/scss/modules/_typography.scss +18 -18
  63. package/src/frontend/scss/modules/frameworks/_bootstrap.scss +109 -109
  64. package/src/frontend/scss/modules/frameworks/_bulma.scss +108 -108
  65. package/src/frontend/scss/modules/frameworks/_foundation.scss +137 -137
  66. package/src/frontend/scss/modules/frameworks/_uikit.scss +109 -109
  67. package/src/frontend/scss/pages/404.scss +27 -27
  68. package/src/frontend/scss/pages/homepage.scss +22 -22
  69. package/src/frontend/sitemap.njk +17 -17
  70. package/src/frontend/ts/global.ts +11 -11
  71. package/src/frontend/ts/modules/exampleModule.ts +2 -2
  72. package/src/frontend/ts/pages/404.ts +8 -8
  73. package/src/frontend/ts/pages/homepage.ts +8 -8
  74. package/src/frontend/web.config +51 -51
  75. package/tools/assistant.js +122 -122
  76. package/tools/buildJs.js +39 -39
  77. package/tools/cleanOutput.js +23 -23
  78. package/tools/cli/prompt.js +40 -40
  79. package/tools/cli/ui.js +87 -87
  80. package/tools/config/messages.json +80 -80
  81. package/tools/config/settings.json +130 -130
  82. package/tools/lib/colors.js +17 -17
  83. package/tools/lib/files.js +75 -75
  84. package/tools/lib/logger.js +22 -22
  85. package/tools/lib/outputPath.js +141 -141
  86. package/tools/lib/pageActions.js +146 -146
  87. package/tools/lib/pageArtifacts.js +46 -46
  88. package/tools/lib/pageComponents.js +88 -88
  89. package/tools/lib/pagesData.js +87 -87
  90. package/tools/lib/paths.js +63 -63
  91. package/tools/lib/project.js +54 -54
  92. package/tools/lib/text.js +47 -47
  93. package/tools/lib/validation.js +40 -40
  94. package/tools/res/templates/template.js +7 -7
  95. package/tools/res/templates/template.njk +8 -8
  96. package/tools/res/templates/template.scss +22 -22
  97. package/tools/res/templates/template.ts +8 -8
  98. package/tsconfig.json +24 -24
  99. package/src/frontend/scss/pages/prova.scss +0 -23
@@ -1,161 +1,161 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- define('CORE_ACCESS', true);
6
- define('CORE_PATH', __DIR__);
7
-
8
- /**
9
- * Load dependencies and initial configuration.
10
- */
11
- require_once __DIR__ . '/init.php';
12
- require_once __DIR__ . '/modules/Response.php';
13
-
14
- // =====================================================
15
- // 1. REQUEST PARSING
16
- // =====================================================
17
-
18
- $method = $_SERVER['REQUEST_METHOD'];
19
- $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
20
-
21
- $uri = rtrim(preg_replace('#^/api#', '', $uri), '/') ?: '/';
22
- $parts = array_values(array_filter(explode('/', $uri)));
23
-
24
- foreach ($parts as $part) {
25
- if ($part === '..' || $part === '.') {
26
- Response::error('Bad Request', 400);
27
- }
28
- }
29
-
30
- // =====================================================
31
- // 2. ENDPOINT RESOLUTION (ROUTING WITH SUBDIRECTORIES)
32
- // =====================================================
33
-
34
- $basePublic = __DIR__ . '/../api/public/';
35
- $baseProtected = __DIR__ . '/../api/protected/';
36
-
37
- $endpointFile = null;
38
- $isProtected = false;
39
- $requestParams = [];
40
-
41
- // Temporary variables for the lookup loop
42
- $checkParts = $parts;
43
- $params = [];
44
-
45
- /**
46
- * Dynamic routing logic: find the deepest matching endpoint file.
47
- * At each iteration, the last URL segment is peeled off and stored
48
- * as a route parameter until a matching file is found.
49
- */
50
- while (count($checkParts) > 0) {
51
- $relativePath = implode('/', $checkParts) . '.php';
52
-
53
- // Check first whether this is a public route
54
- if (file_exists($basePublic . $relativePath)) {
55
- $endpointFile = $basePublic . $relativePath;
56
- $isProtected = false;
57
- break;
58
- }
59
-
60
- // Then check whether this is a protected route
61
- if (file_exists($baseProtected . $relativePath)) {
62
- $endpointFile = $baseProtected . $relativePath;
63
- $isProtected = true;
64
- break;
65
- }
66
-
67
- // No file matched: treat the last segment as a route parameter and try again
68
- array_unshift($params, array_pop($checkParts));
69
- }
70
-
71
- /**
72
- * IF THE ENDPOINT DOES NOT EXIST, RETURN AN HTML 404 PAGE.
73
- */
74
- if (!$endpointFile) {
75
- http_response_code(404);
76
- header_remove('WWW-Authenticate');
77
-
78
- /**
79
- * In a Nibula build the backend lives at out/backend/, so the site root
80
- * (out/) is two levels up from _core. DOCUMENT_ROOT is preferred when it
81
- * resolves, but on shared hosting it often points elsewhere, so the
82
- * relative path is used as a fallback. Same resolution as index.js.
83
- */
84
- $documentRoot = $_SERVER['DOCUMENT_ROOT'] ?? '';
85
- $errorPage = $documentRoot . '/404.html';
86
-
87
- if (!file_exists($errorPage)) {
88
- $errorPage = __DIR__ . '/../../404.html';
89
- }
90
-
91
- header('Content-Type: text/html; charset=UTF-8');
92
-
93
- if (file_exists($errorPage)) {
94
- echo file_get_contents($errorPage);
95
- } else {
96
- echo "<h1>404 Not Found</h1>";
97
- echo "The requested URL was not found on this server.";
98
- }
99
- exit;
100
- }
101
-
102
- $base = $isProtected ? $baseProtected : $basePublic;
103
- $base = str_replace('\\', '/', $base);
104
- $endpointPath = str_replace('\\', '/', $endpointFile);
105
- $endpointPath = preg_replace('#\.php$#', '', str_replace($base, '', $endpointPath));
106
-
107
- // =====================================================
108
- // 3. HEADERS AND CORS (Only if the endpoint exists)
109
- // =====================================================
110
-
111
- header('Content-Type: application/json; charset=UTF-8');
112
- header_remove('WWW-Authenticate');
113
- header('X-Content-Type-Options: nosniff');
114
- header('X-Frame-Options: DENY');
115
- header('Referrer-Policy: strict-origin-when-cross-origin');
116
- header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
117
- header('Access-Control-Allow-Headers: Content-Type, X-Api-Key');
118
-
119
- $originsForEndpoint = $config['CUSTOM_ENDPOINT_ORIGINS'][$endpointPath]
120
- ?? $config['GENERAL_ALLOWED_ORIGINS']
121
- ?? [];
122
-
123
- $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
124
-
125
- if ($origin !== '' && in_array($origin, $originsForEndpoint, true)) {
126
- header('Access-Control-Allow-Origin: ' . $origin);
127
- header('Vary: Origin');
128
- } elseif (in_array('*', $originsForEndpoint, true)) {
129
- header('Access-Control-Allow-Origin: *');
130
- }
131
-
132
- if ($method === 'OPTIONS') {
133
- http_response_code(204);
134
- exit;
135
- }
136
-
137
- require_once __DIR__ . '/modules/RateLimiter.php';
138
- RateLimiter::check($_SERVER['REMOTE_ADDR'] ?? '', 60, 60);
139
-
140
- // =====================================================
141
- // 4. AUTHENTICATION GUARD
142
- // =====================================================
143
-
144
- if ($isProtected) {
145
- $validKey = $config['CUSTOM_ENDPOINT_KEYS'][$endpointPath] ?? $config['GENERAL_API_KEY'] ?? '';
146
- $apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
147
-
148
- if ($validKey === '' || !hash_equals($validKey, $apiKey)) {
149
- Response::error('Unauthorized. X_API_KEY is incorrect or missing', 403);
150
- }
151
- }
152
-
153
- // =====================================================
154
- // 5. DISPATCH
155
- // =====================================================
156
-
157
- // Parameters are the remaining URL segments not consumed during endpoint resolution
158
- $requestParams = $params;
159
-
160
- // Load and execute the matched endpoint file
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ define('CORE_ACCESS', true);
6
+ define('CORE_PATH', __DIR__);
7
+
8
+ /**
9
+ * Load dependencies and initial configuration.
10
+ */
11
+ require_once __DIR__ . '/init.php';
12
+ require_once __DIR__ . '/modules/Response.php';
13
+
14
+ // =====================================================
15
+ // 1. REQUEST PARSING
16
+ // =====================================================
17
+
18
+ $method = $_SERVER['REQUEST_METHOD'];
19
+ $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
20
+
21
+ $uri = rtrim(preg_replace('#^/api#', '', $uri), '/') ?: '/';
22
+ $parts = array_values(array_filter(explode('/', $uri)));
23
+
24
+ foreach ($parts as $part) {
25
+ if ($part === '..' || $part === '.') {
26
+ Response::error('Bad Request', 400);
27
+ }
28
+ }
29
+
30
+ // =====================================================
31
+ // 2. ENDPOINT RESOLUTION (ROUTING WITH SUBDIRECTORIES)
32
+ // =====================================================
33
+
34
+ $basePublic = __DIR__ . '/../api/public/';
35
+ $baseProtected = __DIR__ . '/../api/protected/';
36
+
37
+ $endpointFile = null;
38
+ $isProtected = false;
39
+ $requestParams = [];
40
+
41
+ // Temporary variables for the lookup loop
42
+ $checkParts = $parts;
43
+ $params = [];
44
+
45
+ /**
46
+ * Dynamic routing logic: find the deepest matching endpoint file.
47
+ * At each iteration, the last URL segment is peeled off and stored
48
+ * as a route parameter until a matching file is found.
49
+ */
50
+ while (count($checkParts) > 0) {
51
+ $relativePath = implode('/', $checkParts) . '.php';
52
+
53
+ // Check first whether this is a public route
54
+ if (file_exists($basePublic . $relativePath)) {
55
+ $endpointFile = $basePublic . $relativePath;
56
+ $isProtected = false;
57
+ break;
58
+ }
59
+
60
+ // Then check whether this is a protected route
61
+ if (file_exists($baseProtected . $relativePath)) {
62
+ $endpointFile = $baseProtected . $relativePath;
63
+ $isProtected = true;
64
+ break;
65
+ }
66
+
67
+ // No file matched: treat the last segment as a route parameter and try again
68
+ array_unshift($params, array_pop($checkParts));
69
+ }
70
+
71
+ /**
72
+ * IF THE ENDPOINT DOES NOT EXIST, RETURN AN HTML 404 PAGE.
73
+ */
74
+ if (!$endpointFile) {
75
+ http_response_code(404);
76
+ header_remove('WWW-Authenticate');
77
+
78
+ /**
79
+ * In a Nibula build the backend lives at out/backend/, so the site root
80
+ * (out/) is two levels up from _core. DOCUMENT_ROOT is preferred when it
81
+ * resolves, but on shared hosting it often points elsewhere, so the
82
+ * relative path is used as a fallback. Same resolution as index.js.
83
+ */
84
+ $documentRoot = $_SERVER['DOCUMENT_ROOT'] ?? '';
85
+ $errorPage = $documentRoot . '/404.html';
86
+
87
+ if (!file_exists($errorPage)) {
88
+ $errorPage = __DIR__ . '/../../404.html';
89
+ }
90
+
91
+ header('Content-Type: text/html; charset=UTF-8');
92
+
93
+ if (file_exists($errorPage)) {
94
+ echo file_get_contents($errorPage);
95
+ } else {
96
+ echo "<h1>404 Not Found</h1>";
97
+ echo "The requested URL was not found on this server.";
98
+ }
99
+ exit;
100
+ }
101
+
102
+ $base = $isProtected ? $baseProtected : $basePublic;
103
+ $base = str_replace('\\', '/', $base);
104
+ $endpointPath = str_replace('\\', '/', $endpointFile);
105
+ $endpointPath = preg_replace('#\.php$#', '', str_replace($base, '', $endpointPath));
106
+
107
+ // =====================================================
108
+ // 3. HEADERS AND CORS (Only if the endpoint exists)
109
+ // =====================================================
110
+
111
+ header('Content-Type: application/json; charset=UTF-8');
112
+ header_remove('WWW-Authenticate');
113
+ header('X-Content-Type-Options: nosniff');
114
+ header('X-Frame-Options: DENY');
115
+ header('Referrer-Policy: strict-origin-when-cross-origin');
116
+ header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
117
+ header('Access-Control-Allow-Headers: Content-Type, X-Api-Key');
118
+
119
+ $originsForEndpoint = $config['CUSTOM_ENDPOINT_ORIGINS'][$endpointPath]
120
+ ?? $config['GENERAL_ALLOWED_ORIGINS']
121
+ ?? [];
122
+
123
+ $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
124
+
125
+ if ($origin !== '' && in_array($origin, $originsForEndpoint, true)) {
126
+ header('Access-Control-Allow-Origin: ' . $origin);
127
+ header('Vary: Origin');
128
+ } elseif (in_array('*', $originsForEndpoint, true)) {
129
+ header('Access-Control-Allow-Origin: *');
130
+ }
131
+
132
+ if ($method === 'OPTIONS') {
133
+ http_response_code(204);
134
+ exit;
135
+ }
136
+
137
+ require_once __DIR__ . '/modules/RateLimiter.php';
138
+ RateLimiter::check($_SERVER['REMOTE_ADDR'] ?? '', 60, 60);
139
+
140
+ // =====================================================
141
+ // 4. AUTHENTICATION GUARD
142
+ // =====================================================
143
+
144
+ if ($isProtected) {
145
+ $validKey = $config['CUSTOM_ENDPOINT_KEYS'][$endpointPath] ?? $config['GENERAL_API_KEY'] ?? '';
146
+ $apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
147
+
148
+ if ($validKey === '' || !hash_equals($validKey, $apiKey)) {
149
+ Response::error('Unauthorized. X_API_KEY is incorrect or missing', 403);
150
+ }
151
+ }
152
+
153
+ // =====================================================
154
+ // 5. DISPATCH
155
+ // =====================================================
156
+
157
+ // Parameters are the remaining URL segments not consumed during endpoint resolution
158
+ $requestParams = $params;
159
+
160
+ // Load and execute the matched endpoint file
161
161
  require $endpointFile;
@@ -1,52 +1,52 @@
1
- 'use strict';
2
-
3
- /**
4
- * Mirror of src/backend/_core/init.php
5
- *
6
- * init.php did three things:
7
- * 1. registered a global exception handler that turns any throwable into a
8
- * 500 JSON response (verbose in debug, generic in production);
9
- * 2. registered an error handler that promoted PHP warnings to exceptions;
10
- * 3. loaded config.php.
11
- *
12
- * In Node there is no per-request global handler, so the exception-to-500
13
- * translation lives in the front controller (index.js), which calls
14
- * handleException() below. Here we expose config loading and that helper.
15
- */
16
-
17
- const fs = require('fs');
18
- const path = require('path');
19
-
20
- /**
21
- * Load configuration. dirname(__dirname) in PHP pointed at the folder holding
22
- * config.php (the backend root). Here that is the parent of _core.
23
- * If config.js is missing (e.g. fresh clone) we fall back to example.config.js,
24
- * mirroring the "copy example.config to config" guidance.
25
- */
26
- function loadConfig() {
27
- const backendRoot = path.join(__dirname, '..');
28
- const configPath = path.join(backendRoot, 'config.js');
29
- const examplePath = path.join(backendRoot, 'example.config.js');
30
-
31
- if (fs.existsSync(configPath)) {
32
- return require(configPath);
33
- }
34
- return require(examplePath);
35
- }
36
-
37
- /**
38
- * Turn any thrown error into a 500 response, mirroring set_exception_handler.
39
- * @param {Error} exception
40
- * @param {object} config
41
- * @param {object} Response request-bound helper
42
- */
43
- function handleException(exception, config, Response) {
44
- const isDebug = (config.APP_ENV || 'production') !== 'production';
45
- Response.error(
46
- isDebug ? exception.message : 'Internal server error',
47
- 500,
48
- isDebug ? { stack: exception.stack } : null
49
- );
50
- }
51
-
52
- module.exports = { loadConfig, handleException };
1
+ 'use strict';
2
+
3
+ /**
4
+ * Mirror of src/backend/_core/init.php
5
+ *
6
+ * init.php did three things:
7
+ * 1. registered a global exception handler that turns any throwable into a
8
+ * 500 JSON response (verbose in debug, generic in production);
9
+ * 2. registered an error handler that promoted PHP warnings to exceptions;
10
+ * 3. loaded config.php.
11
+ *
12
+ * In Node there is no per-request global handler, so the exception-to-500
13
+ * translation lives in the front controller (index.js), which calls
14
+ * handleException() below. Here we expose config loading and that helper.
15
+ */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+
20
+ /**
21
+ * Load configuration. dirname(__dirname) in PHP pointed at the folder holding
22
+ * config.php (the backend root). Here that is the parent of _core.
23
+ * If config.js is missing (e.g. fresh clone) we fall back to example.config.js,
24
+ * mirroring the "copy example.config to config" guidance.
25
+ */
26
+ function loadConfig() {
27
+ const backendRoot = path.join(__dirname, '..');
28
+ const configPath = path.join(backendRoot, 'config.js');
29
+ const examplePath = path.join(backendRoot, 'example.config.js');
30
+
31
+ if (fs.existsSync(configPath)) {
32
+ return require(configPath);
33
+ }
34
+ return require(examplePath);
35
+ }
36
+
37
+ /**
38
+ * Turn any thrown error into a 500 response, mirroring set_exception_handler.
39
+ * @param {Error} exception
40
+ * @param {object} config
41
+ * @param {object} Response request-bound helper
42
+ */
43
+ function handleException(exception, config, Response) {
44
+ const isDebug = (config.APP_ENV || 'production') !== 'production';
45
+ Response.error(
46
+ isDebug ? exception.message : 'Internal server error',
47
+ 500,
48
+ isDebug ? { stack: exception.stack } : null
49
+ );
50
+ }
51
+
52
+ module.exports = { loadConfig, handleException };
@@ -1,34 +1,34 @@
1
- <?php
2
-
3
- declare(strict_types=1);
4
-
5
- require_once __DIR__ . '/vendor/autoload.php';
6
- require_once __DIR__ . '/modules/Response.php';
7
-
8
- // --- GESTORE GLOBALE ERRORI E ECCEZIONI ---
9
- set_exception_handler(function ($exception) use (&$config) {
10
- $isDebug = ($config['APP_ENV'] ?? 'production') !== 'production';
11
- Response::error(
12
- $isDebug ? $exception->getMessage() : 'Internal server error',
13
- 500,
14
- $isDebug ? ['file' => $exception->getFile(), 'line' => $exception->getLine()] : null
15
- );
16
- });
17
-
18
- set_error_handler(function ($severity, $message, $file, $line) {
19
- if (!(error_reporting() & $severity)) return;
20
- throw new ErrorException($message, 0, $severity, $file, $line);
21
- });
22
-
23
- // --- CARICAMENTO CONFIGURAZIONE ---
24
- // dirname(__DIR__) punta alla cartella /api/ dove ora si trova config.php
25
- $config = require dirname(__DIR__) . '/config.php';
26
-
27
- // --- CONFIGURAZIONE AMBIENTE ---
28
- if (($config['APP_ENV'] ?? 'production') === 'production') {
29
- ini_set('display_errors', '0');
30
- error_reporting(0);
31
- } else {
32
- ini_set('display_errors', '1');
33
- error_reporting(E_ALL);
1
+ <?php
2
+
3
+ declare(strict_types=1);
4
+
5
+ require_once __DIR__ . '/vendor/autoload.php';
6
+ require_once __DIR__ . '/modules/Response.php';
7
+
8
+ // --- GESTORE GLOBALE ERRORI E ECCEZIONI ---
9
+ set_exception_handler(function ($exception) use (&$config) {
10
+ $isDebug = ($config['APP_ENV'] ?? 'production') !== 'production';
11
+ Response::error(
12
+ $isDebug ? $exception->getMessage() : 'Internal server error',
13
+ 500,
14
+ $isDebug ? ['file' => $exception->getFile(), 'line' => $exception->getLine()] : null
15
+ );
16
+ });
17
+
18
+ set_error_handler(function ($severity, $message, $file, $line) {
19
+ if (!(error_reporting() & $severity)) return;
20
+ throw new ErrorException($message, 0, $severity, $file, $line);
21
+ });
22
+
23
+ // --- CARICAMENTO CONFIGURAZIONE ---
24
+ // dirname(__DIR__) punta alla cartella /api/ dove ora si trova config.php
25
+ $config = require dirname(__DIR__) . '/config.php';
26
+
27
+ // --- CONFIGURAZIONE AMBIENTE ---
28
+ if (($config['APP_ENV'] ?? 'production') === 'production') {
29
+ ini_set('display_errors', '0');
30
+ error_reporting(0);
31
+ } else {
32
+ ini_set('display_errors', '1');
33
+ error_reporting(E_ALL);
34
34
  }
@@ -1,58 +1,58 @@
1
- 'use strict';
2
-
3
- /**
4
- * Mirror of src/backend/_core/modules/RateLimiter.php
5
- *
6
- * Same algorithm and same on-disk format: one JSON file per IP under ../../cache,
7
- * holding an array of request timestamps. Old timestamps outside the window are
8
- * pruned, the current one appended, and if the count exceeds the limit a 429 is
9
- * returned (with a Retry-After header) through the request-bound Response helper.
10
- */
11
-
12
- const fs = require('fs');
13
- const path = require('path');
14
- const crypto = require('crypto');
15
-
16
- class RateLimiter {
17
- /**
18
- * @param {string} ip
19
- * @param {number} maxRequests
20
- * @param {number} windowSeconds
21
- * @param {object} Response request-bound helper from createResponse(res)
22
- * @param {object} res raw http response (to set Retry-After)
23
- */
24
- static check(ip, maxRequests = 60, windowSeconds = 60, Response, res) {
25
- const cacheDir = path.join(__dirname, '..', '..', 'cache');
26
-
27
- if (!fs.existsSync(cacheDir)) {
28
- fs.mkdirSync(cacheDir, { recursive: true, mode: 0o755 });
29
- }
30
-
31
- const hash = crypto.createHash('md5').update(ip).digest('hex');
32
- const cacheFile = path.join(cacheDir, 'rl_' + hash + '.json');
33
- const now = Math.floor(Date.now() / 1000);
34
- let data = [];
35
-
36
- if (fs.existsSync(cacheFile)) {
37
- try {
38
- data = JSON.parse(fs.readFileSync(cacheFile, 'utf8')) || [];
39
- } catch (e) {
40
- data = [];
41
- }
42
- }
43
-
44
- data = data.filter((ts) => ts > (now - windowSeconds));
45
- data.push(now);
46
-
47
- // LOCK_EX equivalent: an exclusive write. Node's writeFileSync is atomic
48
- // enough for this single-process model.
49
- fs.writeFileSync(cacheFile, JSON.stringify(data));
50
-
51
- if (data.length > maxRequests) {
52
- res.setHeader('Retry-After', String(windowSeconds));
53
- Response.error('Too Many Requests', 429);
54
- }
55
- }
56
- }
57
-
58
- module.exports = RateLimiter;
1
+ 'use strict';
2
+
3
+ /**
4
+ * Mirror of src/backend/_core/modules/RateLimiter.php
5
+ *
6
+ * Same algorithm and same on-disk format: one JSON file per IP under ../../cache,
7
+ * holding an array of request timestamps. Old timestamps outside the window are
8
+ * pruned, the current one appended, and if the count exceeds the limit a 429 is
9
+ * returned (with a Retry-After header) through the request-bound Response helper.
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const crypto = require('crypto');
15
+
16
+ class RateLimiter {
17
+ /**
18
+ * @param {string} ip
19
+ * @param {number} maxRequests
20
+ * @param {number} windowSeconds
21
+ * @param {object} Response request-bound helper from createResponse(res)
22
+ * @param {object} res raw http response (to set Retry-After)
23
+ */
24
+ static check(ip, maxRequests = 60, windowSeconds = 60, Response, res) {
25
+ const cacheDir = path.join(__dirname, '..', '..', 'cache');
26
+
27
+ if (!fs.existsSync(cacheDir)) {
28
+ fs.mkdirSync(cacheDir, { recursive: true, mode: 0o755 });
29
+ }
30
+
31
+ const hash = crypto.createHash('md5').update(ip).digest('hex');
32
+ const cacheFile = path.join(cacheDir, 'rl_' + hash + '.json');
33
+ const now = Math.floor(Date.now() / 1000);
34
+ let data = [];
35
+
36
+ if (fs.existsSync(cacheFile)) {
37
+ try {
38
+ data = JSON.parse(fs.readFileSync(cacheFile, 'utf8')) || [];
39
+ } catch (e) {
40
+ data = [];
41
+ }
42
+ }
43
+
44
+ data = data.filter((ts) => ts > (now - windowSeconds));
45
+ data.push(now);
46
+
47
+ // LOCK_EX equivalent: an exclusive write. Node's writeFileSync is atomic
48
+ // enough for this single-process model.
49
+ fs.writeFileSync(cacheFile, JSON.stringify(data));
50
+
51
+ if (data.length > maxRequests) {
52
+ res.setHeader('Retry-After', String(windowSeconds));
53
+ Response.error('Too Many Requests', 429);
54
+ }
55
+ }
56
+ }
57
+
58
+ module.exports = RateLimiter;