nibula 1.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 (81) hide show
  1. package/.eleventy.js +96 -0
  2. package/.eleventyignore +4 -0
  3. package/CHANGELOG.md +0 -0
  4. package/LICENSE +170 -0
  5. package/NOTICE +5 -0
  6. package/README.md +105 -0
  7. package/_tools/assistant.js +152 -0
  8. package/_tools/buildJs.js +37 -0
  9. package/_tools/cleanOutput.js +25 -0
  10. package/_tools/modules/constants.js +66 -0
  11. package/_tools/modules/pageComponents.js +77 -0
  12. package/_tools/modules/updateData.js +90 -0
  13. package/_tools/modules/updateOutputPath.js +112 -0
  14. package/_tools/modules/updatePage.js +162 -0
  15. package/_tools/modules/utils.js +27 -0
  16. package/_tools/modules/validation.js +30 -0
  17. package/_tools/res/templates/template.js +5 -0
  18. package/_tools/res/templates/template.njk +9 -0
  19. package/_tools/res/templates/template.scss +23 -0
  20. package/_tools/res/templates/template.ts +5 -0
  21. package/bin/create.js +407 -0
  22. package/bin/nibula.js +281 -0
  23. package/docs/Assistant CLI.md +66 -0
  24. package/docs/Backend.md +151 -0
  25. package/docs/Components.md +96 -0
  26. package/docs/Creating pages.md +65 -0
  27. package/docs/Deploy.md +101 -0
  28. package/docs/Head and SEO.md +117 -0
  29. package/docs/Javascript.md +53 -0
  30. package/docs/Styling with SCSS.md +136 -0
  31. package/nginx.conf +47 -0
  32. package/nibula-1.0.0.tgz +0 -0
  33. package/package.json +74 -0
  34. package/src/backend/.htaccess +7 -0
  35. package/src/backend/_core/composer.json +5 -0
  36. package/src/backend/_core/composer.lock +492 -0
  37. package/src/backend/_core/index.php +148 -0
  38. package/src/backend/_core/init.php +34 -0
  39. package/src/backend/_core/modules/RateLimiter.php +31 -0
  40. package/src/backend/_core/modules/Response.php +49 -0
  41. package/src/backend/api/protected/example-protected.php +17 -0
  42. package/src/backend/api/public/example-public.php +17 -0
  43. package/src/backend/database/Database.php +24 -0
  44. package/src/backend/database/migrations/create_example_db.sql +1 -0
  45. package/src/backend/example.config.php +28 -0
  46. package/src/backend/web.config +17 -0
  47. package/src/frontend/.htaccess +16 -0
  48. package/src/frontend/404.njk +17 -0
  49. package/src/frontend/assets/brand/favicon.svg +37 -0
  50. package/src/frontend/assets/brand/logo.svg +37 -0
  51. package/src/frontend/components/global/footer.njk +25 -0
  52. package/src/frontend/components/global/header.njk +7 -0
  53. package/src/frontend/components/welcome.njk +116 -0
  54. package/src/frontend/data/site.json +54 -0
  55. package/src/frontend/index.njk +9 -0
  56. package/src/frontend/js/modules/exampleModule.js +3 -0
  57. package/src/frontend/js/pages/404.js +7 -0
  58. package/src/frontend/js/pages/homepage.js +7 -0
  59. package/src/frontend/layouts/base.njk +142 -0
  60. package/src/frontend/layouts/page-components.njk +14 -0
  61. package/src/frontend/llms.njk +18 -0
  62. package/src/frontend/robots.njk +8 -0
  63. package/src/frontend/scss/modules/_animations.scss +25 -0
  64. package/src/frontend/scss/modules/_footer.scss +28 -0
  65. package/src/frontend/scss/modules/_global.scss +44 -0
  66. package/src/frontend/scss/modules/_header.scss +28 -0
  67. package/src/frontend/scss/modules/_mobile.scss +30 -0
  68. package/src/frontend/scss/modules/_root.scss +35 -0
  69. package/src/frontend/scss/modules/_typography.scss +15 -0
  70. package/src/frontend/scss/modules/frameworks/_bootstrap.scss +110 -0
  71. package/src/frontend/scss/modules/frameworks/_bulma.scss +109 -0
  72. package/src/frontend/scss/modules/frameworks/_foundation.scss +139 -0
  73. package/src/frontend/scss/modules/frameworks/_uikit.scss +110 -0
  74. package/src/frontend/scss/pages/404.scss +28 -0
  75. package/src/frontend/scss/pages/homepage.scss +23 -0
  76. package/src/frontend/sitemap.njk +18 -0
  77. package/src/frontend/ts/modules/exampleModule.ts +3 -0
  78. package/src/frontend/ts/pages/404.ts +7 -0
  79. package/src/frontend/ts/pages/homepage.ts +7 -0
  80. package/src/frontend/web.config +27 -0
  81. package/tsconfig.json +25 -0
@@ -0,0 +1,492 @@
1
+ {
2
+ "_readme": [
3
+ "This file locks the dependencies of your project to a known state",
4
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5
+ "This file is @generated automatically"
6
+ ],
7
+ "content-hash": "108be68e4e2b97fed51d36a10eed0849",
8
+ "packages": [
9
+ {
10
+ "name": "graham-campbell/result-type",
11
+ "version": "v1.1.4",
12
+ "source": {
13
+ "type": "git",
14
+ "url": "https://github.com/GrahamCampbell/Result-Type.git",
15
+ "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b"
16
+ },
17
+ "dist": {
18
+ "type": "zip",
19
+ "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b",
20
+ "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b",
21
+ "shasum": ""
22
+ },
23
+ "require": {
24
+ "php": "^7.2.5 || ^8.0",
25
+ "phpoption/phpoption": "^1.9.5"
26
+ },
27
+ "require-dev": {
28
+ "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7"
29
+ },
30
+ "type": "library",
31
+ "autoload": {
32
+ "psr-4": {
33
+ "GrahamCampbell\\ResultType\\": "src/"
34
+ }
35
+ },
36
+ "notification-url": "https://packagist.org/downloads/",
37
+ "license": [
38
+ "MIT"
39
+ ],
40
+ "authors": [
41
+ {
42
+ "name": "Graham Campbell",
43
+ "email": "hello@gjcampbell.co.uk",
44
+ "homepage": "https://github.com/GrahamCampbell"
45
+ }
46
+ ],
47
+ "description": "An Implementation Of The Result Type",
48
+ "keywords": [
49
+ "Graham Campbell",
50
+ "GrahamCampbell",
51
+ "Result Type",
52
+ "Result-Type",
53
+ "result"
54
+ ],
55
+ "support": {
56
+ "issues": "https://github.com/GrahamCampbell/Result-Type/issues",
57
+ "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4"
58
+ },
59
+ "funding": [
60
+ {
61
+ "url": "https://github.com/GrahamCampbell",
62
+ "type": "github"
63
+ },
64
+ {
65
+ "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
66
+ "type": "tidelift"
67
+ }
68
+ ],
69
+ "time": "2025-12-27T19:43:20+00:00"
70
+ },
71
+ {
72
+ "name": "phpoption/phpoption",
73
+ "version": "1.9.5",
74
+ "source": {
75
+ "type": "git",
76
+ "url": "https://github.com/schmittjoh/php-option.git",
77
+ "reference": "75365b91986c2405cf5e1e012c5595cd487a98be"
78
+ },
79
+ "dist": {
80
+ "type": "zip",
81
+ "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be",
82
+ "reference": "75365b91986c2405cf5e1e012c5595cd487a98be",
83
+ "shasum": ""
84
+ },
85
+ "require": {
86
+ "php": "^7.2.5 || ^8.0"
87
+ },
88
+ "require-dev": {
89
+ "bamarni/composer-bin-plugin": "^1.8.2",
90
+ "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34"
91
+ },
92
+ "type": "library",
93
+ "extra": {
94
+ "bamarni-bin": {
95
+ "bin-links": true,
96
+ "forward-command": false
97
+ },
98
+ "branch-alias": {
99
+ "dev-master": "1.9-dev"
100
+ }
101
+ },
102
+ "autoload": {
103
+ "psr-4": {
104
+ "PhpOption\\": "src/PhpOption/"
105
+ }
106
+ },
107
+ "notification-url": "https://packagist.org/downloads/",
108
+ "license": [
109
+ "Apache-2.0"
110
+ ],
111
+ "authors": [
112
+ {
113
+ "name": "Johannes M. Schmitt",
114
+ "email": "schmittjoh@gmail.com",
115
+ "homepage": "https://github.com/schmittjoh"
116
+ },
117
+ {
118
+ "name": "Graham Campbell",
119
+ "email": "hello@gjcampbell.co.uk",
120
+ "homepage": "https://github.com/GrahamCampbell"
121
+ }
122
+ ],
123
+ "description": "Option Type for PHP",
124
+ "keywords": [
125
+ "language",
126
+ "option",
127
+ "php",
128
+ "type"
129
+ ],
130
+ "support": {
131
+ "issues": "https://github.com/schmittjoh/php-option/issues",
132
+ "source": "https://github.com/schmittjoh/php-option/tree/1.9.5"
133
+ },
134
+ "funding": [
135
+ {
136
+ "url": "https://github.com/GrahamCampbell",
137
+ "type": "github"
138
+ },
139
+ {
140
+ "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
141
+ "type": "tidelift"
142
+ }
143
+ ],
144
+ "time": "2025-12-27T19:41:33+00:00"
145
+ },
146
+ {
147
+ "name": "symfony/polyfill-ctype",
148
+ "version": "v1.37.0",
149
+ "source": {
150
+ "type": "git",
151
+ "url": "https://github.com/symfony/polyfill-ctype.git",
152
+ "reference": "141046a8f9477948ff284fa65be2095baafb94f2"
153
+ },
154
+ "dist": {
155
+ "type": "zip",
156
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2",
157
+ "reference": "141046a8f9477948ff284fa65be2095baafb94f2",
158
+ "shasum": ""
159
+ },
160
+ "require": {
161
+ "php": ">=7.2"
162
+ },
163
+ "provide": {
164
+ "ext-ctype": "*"
165
+ },
166
+ "suggest": {
167
+ "ext-ctype": "For best performance"
168
+ },
169
+ "type": "library",
170
+ "extra": {
171
+ "thanks": {
172
+ "url": "https://github.com/symfony/polyfill",
173
+ "name": "symfony/polyfill"
174
+ }
175
+ },
176
+ "autoload": {
177
+ "files": [
178
+ "bootstrap.php"
179
+ ],
180
+ "psr-4": {
181
+ "Symfony\\Polyfill\\Ctype\\": ""
182
+ }
183
+ },
184
+ "notification-url": "https://packagist.org/downloads/",
185
+ "license": [
186
+ "MIT"
187
+ ],
188
+ "authors": [
189
+ {
190
+ "name": "Gert de Pagter",
191
+ "email": "BackEndTea@gmail.com"
192
+ },
193
+ {
194
+ "name": "Symfony Community",
195
+ "homepage": "https://symfony.com/contributors"
196
+ }
197
+ ],
198
+ "description": "Symfony polyfill for ctype functions",
199
+ "homepage": "https://symfony.com",
200
+ "keywords": [
201
+ "compatibility",
202
+ "ctype",
203
+ "polyfill",
204
+ "portable"
205
+ ],
206
+ "support": {
207
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0"
208
+ },
209
+ "funding": [
210
+ {
211
+ "url": "https://symfony.com/sponsor",
212
+ "type": "custom"
213
+ },
214
+ {
215
+ "url": "https://github.com/fabpot",
216
+ "type": "github"
217
+ },
218
+ {
219
+ "url": "https://github.com/nicolas-grekas",
220
+ "type": "github"
221
+ },
222
+ {
223
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
224
+ "type": "tidelift"
225
+ }
226
+ ],
227
+ "time": "2026-04-10T16:19:22+00:00"
228
+ },
229
+ {
230
+ "name": "symfony/polyfill-mbstring",
231
+ "version": "v1.37.0",
232
+ "source": {
233
+ "type": "git",
234
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
235
+ "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315"
236
+ },
237
+ "dist": {
238
+ "type": "zip",
239
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315",
240
+ "reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315",
241
+ "shasum": ""
242
+ },
243
+ "require": {
244
+ "ext-iconv": "*",
245
+ "php": ">=7.2"
246
+ },
247
+ "provide": {
248
+ "ext-mbstring": "*"
249
+ },
250
+ "suggest": {
251
+ "ext-mbstring": "For best performance"
252
+ },
253
+ "type": "library",
254
+ "extra": {
255
+ "thanks": {
256
+ "url": "https://github.com/symfony/polyfill",
257
+ "name": "symfony/polyfill"
258
+ }
259
+ },
260
+ "autoload": {
261
+ "files": [
262
+ "bootstrap.php"
263
+ ],
264
+ "psr-4": {
265
+ "Symfony\\Polyfill\\Mbstring\\": ""
266
+ }
267
+ },
268
+ "notification-url": "https://packagist.org/downloads/",
269
+ "license": [
270
+ "MIT"
271
+ ],
272
+ "authors": [
273
+ {
274
+ "name": "Nicolas Grekas",
275
+ "email": "p@tchwork.com"
276
+ },
277
+ {
278
+ "name": "Symfony Community",
279
+ "homepage": "https://symfony.com/contributors"
280
+ }
281
+ ],
282
+ "description": "Symfony polyfill for the Mbstring extension",
283
+ "homepage": "https://symfony.com",
284
+ "keywords": [
285
+ "compatibility",
286
+ "mbstring",
287
+ "polyfill",
288
+ "portable",
289
+ "shim"
290
+ ],
291
+ "support": {
292
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.37.0"
293
+ },
294
+ "funding": [
295
+ {
296
+ "url": "https://symfony.com/sponsor",
297
+ "type": "custom"
298
+ },
299
+ {
300
+ "url": "https://github.com/fabpot",
301
+ "type": "github"
302
+ },
303
+ {
304
+ "url": "https://github.com/nicolas-grekas",
305
+ "type": "github"
306
+ },
307
+ {
308
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
309
+ "type": "tidelift"
310
+ }
311
+ ],
312
+ "time": "2026-04-10T17:25:58+00:00"
313
+ },
314
+ {
315
+ "name": "symfony/polyfill-php80",
316
+ "version": "v1.37.0",
317
+ "source": {
318
+ "type": "git",
319
+ "url": "https://github.com/symfony/polyfill-php80.git",
320
+ "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
321
+ },
322
+ "dist": {
323
+ "type": "zip",
324
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
325
+ "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
326
+ "shasum": ""
327
+ },
328
+ "require": {
329
+ "php": ">=7.2"
330
+ },
331
+ "type": "library",
332
+ "extra": {
333
+ "thanks": {
334
+ "url": "https://github.com/symfony/polyfill",
335
+ "name": "symfony/polyfill"
336
+ }
337
+ },
338
+ "autoload": {
339
+ "files": [
340
+ "bootstrap.php"
341
+ ],
342
+ "psr-4": {
343
+ "Symfony\\Polyfill\\Php80\\": ""
344
+ },
345
+ "classmap": [
346
+ "Resources/stubs"
347
+ ]
348
+ },
349
+ "notification-url": "https://packagist.org/downloads/",
350
+ "license": [
351
+ "MIT"
352
+ ],
353
+ "authors": [
354
+ {
355
+ "name": "Ion Bazan",
356
+ "email": "ion.bazan@gmail.com"
357
+ },
358
+ {
359
+ "name": "Nicolas Grekas",
360
+ "email": "p@tchwork.com"
361
+ },
362
+ {
363
+ "name": "Symfony Community",
364
+ "homepage": "https://symfony.com/contributors"
365
+ }
366
+ ],
367
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
368
+ "homepage": "https://symfony.com",
369
+ "keywords": [
370
+ "compatibility",
371
+ "polyfill",
372
+ "portable",
373
+ "shim"
374
+ ],
375
+ "support": {
376
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0"
377
+ },
378
+ "funding": [
379
+ {
380
+ "url": "https://symfony.com/sponsor",
381
+ "type": "custom"
382
+ },
383
+ {
384
+ "url": "https://github.com/fabpot",
385
+ "type": "github"
386
+ },
387
+ {
388
+ "url": "https://github.com/nicolas-grekas",
389
+ "type": "github"
390
+ },
391
+ {
392
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
393
+ "type": "tidelift"
394
+ }
395
+ ],
396
+ "time": "2026-04-10T16:19:22+00:00"
397
+ },
398
+ {
399
+ "name": "vlucas/phpdotenv",
400
+ "version": "v5.6.3",
401
+ "source": {
402
+ "type": "git",
403
+ "url": "https://github.com/vlucas/phpdotenv.git",
404
+ "reference": "955e7815d677a3eaa7075231212f2110983adecc"
405
+ },
406
+ "dist": {
407
+ "type": "zip",
408
+ "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc",
409
+ "reference": "955e7815d677a3eaa7075231212f2110983adecc",
410
+ "shasum": ""
411
+ },
412
+ "require": {
413
+ "ext-pcre": "*",
414
+ "graham-campbell/result-type": "^1.1.4",
415
+ "php": "^7.2.5 || ^8.0",
416
+ "phpoption/phpoption": "^1.9.5",
417
+ "symfony/polyfill-ctype": "^1.26",
418
+ "symfony/polyfill-mbstring": "^1.26",
419
+ "symfony/polyfill-php80": "^1.26"
420
+ },
421
+ "require-dev": {
422
+ "bamarni/composer-bin-plugin": "^1.8.2",
423
+ "ext-filter": "*",
424
+ "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
425
+ },
426
+ "suggest": {
427
+ "ext-filter": "Required to use the boolean validator."
428
+ },
429
+ "type": "library",
430
+ "extra": {
431
+ "bamarni-bin": {
432
+ "bin-links": true,
433
+ "forward-command": false
434
+ },
435
+ "branch-alias": {
436
+ "dev-master": "5.6-dev"
437
+ }
438
+ },
439
+ "autoload": {
440
+ "psr-4": {
441
+ "Dotenv\\": "src/"
442
+ }
443
+ },
444
+ "notification-url": "https://packagist.org/downloads/",
445
+ "license": [
446
+ "BSD-3-Clause"
447
+ ],
448
+ "authors": [
449
+ {
450
+ "name": "Graham Campbell",
451
+ "email": "hello@gjcampbell.co.uk",
452
+ "homepage": "https://github.com/GrahamCampbell"
453
+ },
454
+ {
455
+ "name": "Vance Lucas",
456
+ "email": "vance@vancelucas.com",
457
+ "homepage": "https://github.com/vlucas"
458
+ }
459
+ ],
460
+ "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
461
+ "keywords": [
462
+ "dotenv",
463
+ "env",
464
+ "environment"
465
+ ],
466
+ "support": {
467
+ "issues": "https://github.com/vlucas/phpdotenv/issues",
468
+ "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3"
469
+ },
470
+ "funding": [
471
+ {
472
+ "url": "https://github.com/GrahamCampbell",
473
+ "type": "github"
474
+ },
475
+ {
476
+ "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
477
+ "type": "tidelift"
478
+ }
479
+ ],
480
+ "time": "2025-12-27T19:49:13+00:00"
481
+ }
482
+ ],
483
+ "packages-dev": [],
484
+ "aliases": [],
485
+ "minimum-stability": "stable",
486
+ "stability-flags": {},
487
+ "prefer-stable": false,
488
+ "prefer-lowest": false,
489
+ "platform": {},
490
+ "platform-dev": {},
491
+ "plugin-api-version": "2.6.0"
492
+ }
@@ -0,0 +1,148 @@
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
+
77
+ // Look for the 404.html file generated by Eleventy in the server document root
78
+ $errorPage = $_SERVER['DOCUMENT_ROOT'] . '/404.html';
79
+
80
+ if (file_exists($errorPage)) {
81
+ header('Content-Type: text/html; charset=UTF-8');
82
+ echo file_get_contents($errorPage);
83
+ } else {
84
+ echo "<h1>404 Not Found</h1>";
85
+ echo "The requested URL was not found on this server.";
86
+ }
87
+ exit;
88
+ }
89
+
90
+ $base = $isProtected ? $baseProtected : $basePublic;
91
+ $base = str_replace('\\', '/', $base);
92
+ $endpointPath = str_replace('\\', '/', $endpointFile);
93
+ $endpointPath = preg_replace('#\.php$#', '', str_replace($base, '', $endpointPath));
94
+
95
+ // =====================================================
96
+ // 3. HEADERS AND CORS (Only if the endpoint exists)
97
+ // =====================================================
98
+
99
+ header('Content-Type: application/json; charset=UTF-8');
100
+ header('X-Content-Type-Options: nosniff');
101
+ header('X-Frame-Options: DENY');
102
+ header('Referrer-Policy: strict-origin-when-cross-origin');
103
+ header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
104
+ header('Access-Control-Allow-Headers: Content-Type, X-Api-Key');
105
+
106
+ $originsForEndpoint = $config['CUSTOM_ENDPOINT_ORIGINS'][$endpointPath]
107
+ ?? $config['GENERAL_ALLOWED_ORIGINS']
108
+ ?? [];
109
+
110
+ $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
111
+
112
+ if ($origin !== '' && in_array($origin, $originsForEndpoint, true)) {
113
+ header('Access-Control-Allow-Origin: ' . $origin);
114
+ header('Vary: Origin');
115
+ } elseif (in_array('*', $originsForEndpoint, true)) {
116
+ header('Access-Control-Allow-Origin: *');
117
+ }
118
+
119
+ if ($method === 'OPTIONS') {
120
+ http_response_code(204);
121
+ exit;
122
+ }
123
+
124
+ require_once __DIR__ . '/modules/RateLimiter.php';
125
+ RateLimiter::check($_SERVER['REMOTE_ADDR'] ?? '', 60, 60);
126
+
127
+ // =====================================================
128
+ // 4. AUTHENTICATION GUARD
129
+ // =====================================================
130
+
131
+ if ($isProtected) {
132
+ $validKey = $config['CUSTOM_ENDPOINT_KEYS'][$endpointPath] ?? $config['GENERAL_API_KEY'] ?? '';
133
+ $apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
134
+
135
+ if ($validKey === '' || !hash_equals($validKey, $apiKey)) {
136
+ Response::error('Unauthorized. X_API_KEY is incorrect or missing', 401);
137
+ }
138
+ }
139
+
140
+ // =====================================================
141
+ // 5. DISPATCH
142
+ // =====================================================
143
+
144
+ // Parameters are the remaining URL segments not consumed during endpoint resolution
145
+ $requestParams = $params;
146
+
147
+ // Load and execute the matched endpoint file
148
+ require $endpointFile;
@@ -0,0 +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);
34
+ }