nibula 2.5.0 → 3.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.
- package/.eleventy.js +0 -7
- package/CHANGELOG.md +53 -3
- package/bin/create.js +43 -45
- package/bin/nibula.js +2 -8
- package/nginx.conf +1 -1
- package/package.json +3 -7
- package/src/backend/.htaccess +2 -2
- package/src/backend/core/index.js +329 -0
- package/src/backend/core/index.php +207 -0
- package/src/backend/core/init.js +37 -0
- package/src/backend/core/init.php +54 -0
- package/src/backend/core/modules/RateLimiter.js +55 -0
- package/src/backend/core/modules/RateLimiter.php +39 -0
- package/src/backend/{_core → core}/modules/Response.js +15 -10
- package/src/backend/{_core → core}/modules/Response.php +14 -17
- package/src/backend/core/modules/Validate.js +56 -0
- package/src/backend/core/modules/Validate.php +52 -0
- package/src/backend/endpoints/protected/example-protected.js +39 -0
- package/src/backend/endpoints/protected/example-protected.php +47 -0
- package/src/backend/endpoints/public/example-public.js +39 -0
- package/src/backend/endpoints/public/example-public.php +47 -0
- package/src/backend/example.config.js +7 -6
- package/src/backend/example.config.php +8 -7
- package/src/backend/package.json +3 -3
- package/src/backend/web.config +2 -2
- package/src/frontend/hosting/.htaccess +1 -1
- package/src/frontend/hosting/web.config +1 -1
- package/src/frontend/layouts/base.njk +3 -7
- package/src/frontend/scss/{_global.scss → global.scss} +0 -2
- package/src/frontend/scss/modules/frameworks/_bootstrap.scss +0 -2
- package/src/frontend/scss/modules/frameworks/_bulma.scss +0 -2
- package/src/frontend/scss/pages/404.scss +0 -3
- package/src/frontend/scss/pages/homepage.scss +0 -3
- package/tools/res/templates/template.json +13 -0
- package/tools/res/templates/template.scss +0 -3
- package/src/backend/_core/index.js +0 -230
- package/src/backend/_core/index.php +0 -118
- package/src/backend/_core/init.js +0 -32
- package/src/backend/_core/init.php +0 -30
- package/src/backend/_core/modules/RateLimiter.js +0 -47
- package/src/backend/_core/modules/RateLimiter.php +0 -31
- package/src/backend/api/protected/example-protected.js +0 -16
- package/src/backend/api/protected/example-protected.php +0 -17
- package/src/backend/api/public/example-public.js +0 -16
- package/src/backend/api/public/example-public.php +0 -17
- package/src/frontend/scss/modules/frameworks/_foundation.scss +0 -138
- package/src/frontend/scss/modules/frameworks/_uikit.scss +0 -110
- package/tools/buildJs.js +0 -39
- /package/src/backend/{_core → core}/composer.json +0 -0
- /package/src/backend/{_core → core}/composer.lock +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
class RateLimiter
|
|
6
|
+
{
|
|
7
|
+
private const CACHE_FOLDER = '/../../cache';
|
|
8
|
+
private const CACHE_PREFIX = 'rl_';
|
|
9
|
+
private const CACHE_EXTENSION = '.json';
|
|
10
|
+
private const FOLDER_PERMISSIONS = 0755;
|
|
11
|
+
private const TOO_MANY_REQUESTS = 429;
|
|
12
|
+
|
|
13
|
+
public static function check(string $ip, int $maxRequests, int $windowSeconds): void
|
|
14
|
+
{
|
|
15
|
+
$cacheDir = __DIR__ . self::CACHE_FOLDER;
|
|
16
|
+
|
|
17
|
+
if (!is_dir($cacheDir)) {
|
|
18
|
+
mkdir($cacheDir, self::FOLDER_PERMISSIONS, true);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
$cacheFile = $cacheDir . '/' . self::CACHE_PREFIX . md5($ip) . self::CACHE_EXTENSION;
|
|
22
|
+
$now = time();
|
|
23
|
+
$timestamps = [];
|
|
24
|
+
|
|
25
|
+
if (file_exists($cacheFile)) {
|
|
26
|
+
$timestamps = json_decode(file_get_contents($cacheFile), true) ?: [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
$timestamps = array_filter($timestamps, fn($timestamp) => $timestamp > ($now - $windowSeconds));
|
|
30
|
+
$timestamps[] = $now;
|
|
31
|
+
|
|
32
|
+
file_put_contents($cacheFile, json_encode(array_values($timestamps)), LOCK_EX);
|
|
33
|
+
|
|
34
|
+
if (count($timestamps) > $maxRequests) {
|
|
35
|
+
header('Retry-After: ' . $windowSeconds);
|
|
36
|
+
Response::error('Too Many Requests', self::TOO_MANY_REQUESTS);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
const HALT = Symbol("RESPONSE_HALT");
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
const STATUS_OK = 200;
|
|
6
|
+
const STATUS_BAD_REQUEST = 400;
|
|
7
|
+
const STATUS_NO_CONTENT = 204;
|
|
8
|
+
|
|
9
|
+
const JSON_CONTENT_TYPE = "application/json; charset=UTF-8";
|
|
8
10
|
|
|
9
11
|
function createResponse(res) {
|
|
10
12
|
return {
|
|
11
|
-
success(data = null, code =
|
|
13
|
+
success(data = null, code = STATUS_OK) {
|
|
12
14
|
res.statusCode = code;
|
|
13
|
-
res.setHeader("Content-Type",
|
|
15
|
+
res.setHeader("Content-Type", JSON_CONTENT_TYPE);
|
|
14
16
|
res.end(
|
|
15
|
-
|
|
17
|
+
JSON.stringify({
|
|
16
18
|
status: "success",
|
|
17
19
|
data: data,
|
|
18
20
|
}),
|
|
@@ -20,23 +22,26 @@ function createResponse(res) {
|
|
|
20
22
|
throw HALT;
|
|
21
23
|
},
|
|
22
24
|
|
|
23
|
-
error(message, code =
|
|
25
|
+
error(message, code = STATUS_BAD_REQUEST, details = null) {
|
|
24
26
|
res.statusCode = code;
|
|
25
|
-
res.setHeader("Content-Type",
|
|
27
|
+
res.setHeader("Content-Type", JSON_CONTENT_TYPE);
|
|
28
|
+
|
|
26
29
|
const body = {
|
|
27
30
|
status: "error",
|
|
28
31
|
message: message,
|
|
29
32
|
code: code,
|
|
30
33
|
};
|
|
34
|
+
|
|
31
35
|
if (details !== null) {
|
|
32
36
|
body.details = details;
|
|
33
37
|
}
|
|
34
|
-
|
|
38
|
+
|
|
39
|
+
res.end(JSON.stringify(body));
|
|
35
40
|
throw HALT;
|
|
36
41
|
},
|
|
37
42
|
|
|
38
43
|
noContent() {
|
|
39
|
-
res.statusCode =
|
|
44
|
+
res.statusCode = STATUS_NO_CONTENT;
|
|
40
45
|
res.end();
|
|
41
46
|
throw HALT;
|
|
42
47
|
},
|
|
@@ -2,48 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
declare(strict_types=1);
|
|
4
4
|
|
|
5
|
-
if (!defined('CORE_ACCESS')) {
|
|
6
|
-
$errorPage = $_SERVER['DOCUMENT_ROOT'] . '/404.html';
|
|
7
|
-
http_response_code(404);
|
|
8
|
-
if (file_exists($errorPage)) {
|
|
9
|
-
header('Content-Type: text/html; charset=UTF-8');
|
|
10
|
-
echo file_get_contents($errorPage);
|
|
11
|
-
} else {
|
|
12
|
-
echo "404 Not Found";
|
|
13
|
-
}
|
|
14
|
-
exit;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
5
|
class Response
|
|
18
6
|
{
|
|
19
|
-
|
|
7
|
+
private const STATUS_OK = 200;
|
|
8
|
+
private const STATUS_BAD_REQUEST = 400;
|
|
9
|
+
private const STATUS_NO_CONTENT = 204;
|
|
10
|
+
|
|
11
|
+
private const JSON_FLAGS = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
|
|
12
|
+
|
|
13
|
+
public static function success(mixed $data = null, int $code = self::STATUS_OK): never
|
|
20
14
|
{
|
|
21
15
|
http_response_code($code);
|
|
22
16
|
echo json_encode([
|
|
23
17
|
'status' => 'success',
|
|
24
18
|
'data' => $data,
|
|
25
|
-
],
|
|
19
|
+
], self::JSON_FLAGS);
|
|
26
20
|
exit;
|
|
27
21
|
}
|
|
28
22
|
|
|
29
|
-
public static function error(string $message, int $code =
|
|
23
|
+
public static function error(string $message, int $code = self::STATUS_BAD_REQUEST, mixed $details = null): never
|
|
30
24
|
{
|
|
31
25
|
http_response_code($code);
|
|
26
|
+
|
|
32
27
|
$body = [
|
|
33
28
|
'status' => 'error',
|
|
34
29
|
'message' => $message,
|
|
35
30
|
'code' => $code,
|
|
36
31
|
];
|
|
32
|
+
|
|
37
33
|
if ($details !== null) {
|
|
38
34
|
$body['details'] = $details;
|
|
39
35
|
}
|
|
40
|
-
|
|
36
|
+
|
|
37
|
+
echo json_encode($body, self::JSON_FLAGS);
|
|
41
38
|
exit;
|
|
42
39
|
}
|
|
43
40
|
|
|
44
41
|
public static function noContent(): never
|
|
45
42
|
{
|
|
46
|
-
http_response_code(
|
|
43
|
+
http_response_code(self::STATUS_NO_CONTENT);
|
|
47
44
|
exit;
|
|
48
45
|
}
|
|
49
46
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const VALIDATION_STATUS = 422;
|
|
4
|
+
const EMAIL_PATTERN = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
|
|
5
|
+
const INTEGER_PATTERN = /^\d+$/;
|
|
6
|
+
|
|
7
|
+
function createValidator(Response) {
|
|
8
|
+
const validator = {
|
|
9
|
+
required(value, field) {
|
|
10
|
+
if (value === undefined || value === null || value === "") {
|
|
11
|
+
Response.error(`The ${field} field is required`, VALIDATION_STATUS);
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
integer(value, field) {
|
|
17
|
+
validator.required(value, field);
|
|
18
|
+
|
|
19
|
+
if (!INTEGER_PATTERN.test(String(value))) {
|
|
20
|
+
Response.error(
|
|
21
|
+
`The ${field} field must be a number`,
|
|
22
|
+
VALIDATION_STATUS,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return Number(value);
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
email(value, field) {
|
|
29
|
+
validator.required(value, field);
|
|
30
|
+
|
|
31
|
+
if (!EMAIL_PATTERN.test(String(value))) {
|
|
32
|
+
Response.error(
|
|
33
|
+
`The ${field} field must be a valid email`,
|
|
34
|
+
VALIDATION_STATUS,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return String(value);
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
minLength(value, field, length) {
|
|
41
|
+
validator.required(value, field);
|
|
42
|
+
|
|
43
|
+
if (String(value).length < length) {
|
|
44
|
+
Response.error(
|
|
45
|
+
`The ${field} field must be at least ${length} characters`,
|
|
46
|
+
VALIDATION_STATUS,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return String(value);
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return validator;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { createValidator };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
class Validate
|
|
6
|
+
{
|
|
7
|
+
private const VALIDATION_STATUS = 422;
|
|
8
|
+
private const EMAIL_PATTERN = '/^[^@\s]+@[^@\s]+\.[^@\s]+$/';
|
|
9
|
+
private const INTEGER_PATTERN = '/^\d+$/';
|
|
10
|
+
|
|
11
|
+
public static function required(mixed $value, string $field): mixed
|
|
12
|
+
{
|
|
13
|
+
if ($value === null || $value === '') {
|
|
14
|
+
Response::error("The {$field} field is required", self::VALIDATION_STATUS);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return $value;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public static function integer(mixed $value, string $field): int
|
|
21
|
+
{
|
|
22
|
+
self::required($value, $field);
|
|
23
|
+
|
|
24
|
+
if (!preg_match(self::INTEGER_PATTERN, (string) $value)) {
|
|
25
|
+
Response::error("The {$field} field must be a number", self::VALIDATION_STATUS);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (int) $value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public static function email(mixed $value, string $field): string
|
|
32
|
+
{
|
|
33
|
+
self::required($value, $field);
|
|
34
|
+
|
|
35
|
+
if (!preg_match(self::EMAIL_PATTERN, (string) $value)) {
|
|
36
|
+
Response::error("The {$field} field must be a valid email", self::VALIDATION_STATUS);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (string) $value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public static function minLength(mixed $value, string $field, int $length): string
|
|
43
|
+
{
|
|
44
|
+
self::required($value, $field);
|
|
45
|
+
|
|
46
|
+
if (mb_strlen((string) $value) < $length) {
|
|
47
|
+
Response::error("The {$field} field must be at least {$length} characters", self::VALIDATION_STATUS);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (string) $value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -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
|
+
];
|
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
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
|
];
|
package/src/backend/package.json
CHANGED
|
@@ -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:
|
|
6
|
-
"main": "
|
|
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
|
|
9
|
+
"start": "node core/index.js"
|
|
10
10
|
},
|
|
11
11
|
"engines": {
|
|
12
12
|
"node": ">=18"
|
package/src/backend/web.config
CHANGED
|
@@ -19,13 +19,13 @@
|
|
|
19
19
|
<rules>
|
|
20
20
|
|
|
21
21
|
<rule name="PassThroughCoreIndex" stopProcessing="true">
|
|
22
|
-
<match url="^
|
|
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="
|
|
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/
|
|
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/
|
|
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";
|
|
@@ -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
|
|
|
@@ -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
|
|