create-berna-stencil 1.0.24 → 1.0.26
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 +1 -0
- package/.gitignore +2 -2
- package/_tools/assistant.js +6 -6
- package/_tools/modules/updateOutputPath.js +1 -4
- package/_tools/modules/updatePage.js +33 -72
- package/_tools/res/templates/template.js +23 -0
- package/_tools/res/templates/template.njk +8 -0
- package/_tools/res/templates/template.scss +17 -0
- package/bin/create.js +1 -1
- package/package.json +1 -1
- package/src/api/.htaccess +6 -11
- package/src/api/config.example.php +5 -20
- package/src/api/config.php +5 -20
- package/src/api/core/composer.lock +492 -492
- package/src/api/{index.php → core/index.php} +53 -24
- package/src/api/core/init.php +0 -13
- package/src/api/core/vendor/composer/installed.php +2 -2
- package/src/api/database/Database.php +24 -0
- package/src/api/database/migrations/create_example_db.sql +1 -0
- package/src/api/database/migrations/create_users_table.sql +9 -0
- package/src/api/database/models/User.php +61 -0
- package/src/api/database/seeds/users.php +0 -0
- package/src/api/database/services/UserService.php +0 -0
- package/src/api/endpoints/protected/auth-system.php +63 -0
- package/src/api/endpoints/protected/example-protected.php +17 -0
- package/src/api/endpoints/public/auth/login.php +34 -0
- package/src/api/endpoints/public/auth/register.php +39 -0
- package/src/api/endpoints/public/example-public.php +17 -0
- package/src/api/web.config +6 -24
- package/src/components/exampleComponent.njk +5 -2
- package/src/components/layouts/base.njk +26 -1
- package/src/data/site.json +53 -53
- package/src/js/modules/langSwitcher.js +1 -1
- package/src/web.config +30 -33
- package/_tools/res/templates.json +0 -56
- package/src/api/endpoints/protected/example-protected-endpoint.php +0 -28
- package/src/api/endpoints/public/example-public-endpoint.php +0 -28
|
@@ -5,43 +5,71 @@ declare(strict_types=1);
|
|
|
5
5
|
define('CORE_ACCESS', true);
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Load dependencies and initial configuration.
|
|
9
9
|
*/
|
|
10
|
-
require_once __DIR__ . '/
|
|
11
|
-
require_once __DIR__ . '/
|
|
10
|
+
require_once __DIR__ . '/init.php';
|
|
11
|
+
require_once __DIR__ . '/modules/Response.php';
|
|
12
12
|
|
|
13
13
|
// =====================================================
|
|
14
|
-
// 1.
|
|
14
|
+
// 1. REQUEST PARSING
|
|
15
15
|
// =====================================================
|
|
16
16
|
|
|
17
17
|
$method = $_SERVER['REQUEST_METHOD'];
|
|
18
18
|
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
|
19
19
|
|
|
20
|
-
// Pulizia URI: rimuoviamo /api e eventuali slash finali
|
|
21
20
|
$uri = rtrim(preg_replace('#^/api#', '', $uri), '/') ?: '/';
|
|
22
21
|
$parts = array_values(array_filter(explode('/', $uri)));
|
|
23
22
|
|
|
24
|
-
$resource = $parts[0] ?? null;
|
|
25
|
-
|
|
26
23
|
// =====================================================
|
|
27
|
-
// 2.
|
|
24
|
+
// 2. ENDPOINT RESOLUTION (ROUTING WITH SUBDIRECTORIES)
|
|
28
25
|
// =====================================================
|
|
29
26
|
|
|
30
|
-
$
|
|
31
|
-
$
|
|
27
|
+
$basePublic = __DIR__ . '/../endpoints/public/';
|
|
28
|
+
$baseProtected = __DIR__ . '/../endpoints/protected/';
|
|
29
|
+
|
|
30
|
+
$endpointFile = null;
|
|
31
|
+
$isProtected = false;
|
|
32
|
+
$requestParams = [];
|
|
33
|
+
|
|
34
|
+
// Temporary variables for the lookup loop
|
|
35
|
+
$checkParts = $parts;
|
|
36
|
+
$params = [];
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Dynamic routing logic: find the deepest matching endpoint file.
|
|
40
|
+
* At each iteration, the last URL segment is peeled off and stored
|
|
41
|
+
* as a route parameter until a matching file is found.
|
|
42
|
+
*/
|
|
43
|
+
while (count($checkParts) > 0) {
|
|
44
|
+
$relativePath = implode('/', $checkParts) . '.php';
|
|
45
|
+
|
|
46
|
+
// Check first whether this is a public route
|
|
47
|
+
if (file_exists($basePublic . $relativePath)) {
|
|
48
|
+
$endpointFile = $basePublic . $relativePath;
|
|
49
|
+
$isProtected = false;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Then check whether this is a protected route
|
|
54
|
+
if (file_exists($baseProtected . $relativePath)) {
|
|
55
|
+
$endpointFile = $baseProtected . $relativePath;
|
|
56
|
+
$isProtected = true;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
32
59
|
|
|
33
|
-
|
|
34
|
-
$
|
|
60
|
+
// No file matched: treat the last segment as a route parameter and try again
|
|
61
|
+
array_unshift($params, array_pop($checkParts));
|
|
62
|
+
}
|
|
35
63
|
|
|
36
64
|
/**
|
|
37
|
-
*
|
|
65
|
+
* IF THE ENDPOINT DOES NOT EXIST, RETURN AN HTML 404 PAGE.
|
|
38
66
|
*/
|
|
39
|
-
if (!$
|
|
67
|
+
if (!$endpointFile) {
|
|
40
68
|
http_response_code(404);
|
|
41
|
-
|
|
42
|
-
//
|
|
69
|
+
|
|
70
|
+
// Look for the 404.html file generated by Eleventy in the server document root
|
|
43
71
|
$errorPage = $_SERVER['DOCUMENT_ROOT'] . '/404.html';
|
|
44
|
-
|
|
72
|
+
|
|
45
73
|
if (file_exists($errorPage)) {
|
|
46
74
|
header('Content-Type: text/html; charset=UTF-8');
|
|
47
75
|
echo file_get_contents($errorPage);
|
|
@@ -53,7 +81,7 @@ if (!$isPublic && !$isProtected) {
|
|
|
53
81
|
}
|
|
54
82
|
|
|
55
83
|
// =====================================================
|
|
56
|
-
// 3. HEADERS
|
|
84
|
+
// 3. HEADERS AND CORS (Only if the endpoint exists)
|
|
57
85
|
// =====================================================
|
|
58
86
|
|
|
59
87
|
header('Content-Type: application/json; charset=UTF-8');
|
|
@@ -75,7 +103,7 @@ if ($method === 'OPTIONS') {
|
|
|
75
103
|
}
|
|
76
104
|
|
|
77
105
|
// =====================================================
|
|
78
|
-
// 4.
|
|
106
|
+
// 4. AUTHENTICATION GUARD
|
|
79
107
|
// =====================================================
|
|
80
108
|
|
|
81
109
|
if ($isProtected) {
|
|
@@ -83,15 +111,16 @@ if ($isProtected) {
|
|
|
83
111
|
$validKey = $config['API_KEY'] ?? '';
|
|
84
112
|
|
|
85
113
|
if ($validKey === '' || $apiKey !== $validKey) {
|
|
86
|
-
Response::error('Unauthorized', 401);
|
|
114
|
+
Response::error('Unauthorized. X_API_KEY is incorrect or missing', 401);
|
|
87
115
|
}
|
|
88
116
|
}
|
|
89
117
|
|
|
90
118
|
// =====================================================
|
|
91
|
-
// 5.
|
|
119
|
+
// 5. DISPATCH
|
|
92
120
|
// =====================================================
|
|
93
121
|
|
|
94
|
-
|
|
122
|
+
// Parameters are the remaining URL segments not consumed during endpoint resolution
|
|
123
|
+
$requestParams = $params;
|
|
95
124
|
|
|
96
|
-
//
|
|
97
|
-
require $
|
|
125
|
+
// Load and execute the matched endpoint file
|
|
126
|
+
require $endpointFile;
|
package/src/api/core/init.php
CHANGED
|
@@ -2,19 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
declare(strict_types=1);
|
|
4
4
|
|
|
5
|
-
// Impedisce l'accesso diretto a questo file
|
|
6
|
-
if (!defined('CORE_ACCESS')) {
|
|
7
|
-
$errorPage = $_SERVER['DOCUMENT_ROOT'] . '/404.html';
|
|
8
|
-
http_response_code(404);
|
|
9
|
-
if (file_exists($errorPage)) {
|
|
10
|
-
header('Content-Type: text/html; charset=UTF-8');
|
|
11
|
-
echo file_get_contents($errorPage);
|
|
12
|
-
} else {
|
|
13
|
-
echo "404 Not Found";
|
|
14
|
-
}
|
|
15
|
-
exit;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
5
|
require_once __DIR__ . '/vendor/autoload.php';
|
|
19
6
|
require_once __DIR__ . '/modules/Response.php';
|
|
20
7
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
'name' => '__root__',
|
|
4
4
|
'pretty_version' => 'dev-main',
|
|
5
5
|
'version' => 'dev-main',
|
|
6
|
-
'reference' => '
|
|
6
|
+
'reference' => 'a6cf6a6a677f9f341c6206144990bb04047857bf',
|
|
7
7
|
'type' => 'library',
|
|
8
8
|
'install_path' => __DIR__ . '/../../',
|
|
9
9
|
'aliases' => array(),
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
'__root__' => array(
|
|
14
14
|
'pretty_version' => 'dev-main',
|
|
15
15
|
'version' => 'dev-main',
|
|
16
|
-
'reference' => '
|
|
16
|
+
'reference' => 'a6cf6a6a677f9f341c6206144990bb04047857bf',
|
|
17
17
|
'type' => 'library',
|
|
18
18
|
'install_path' => __DIR__ . '/../../',
|
|
19
19
|
'aliases' => array(),
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
declare(strict_types=1);
|
|
3
|
+
|
|
4
|
+
class Database {
|
|
5
|
+
private static ?PDO $instance = null;
|
|
6
|
+
|
|
7
|
+
private function __construct() {}
|
|
8
|
+
|
|
9
|
+
public static function getInstance(): PDO {
|
|
10
|
+
if (self::$instance === null) {
|
|
11
|
+
$config = require __DIR__ . '/../config.php';
|
|
12
|
+
|
|
13
|
+
$dsn = "mysql:host={$config['DB_HOST']};dbname={$config['DB_NAME']};charset=utf8mb4";
|
|
14
|
+
$options = [
|
|
15
|
+
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
16
|
+
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
17
|
+
PDO::ATTR_EMULATE_PREPARES => false,
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
self::$instance = new PDO($dsn, $config['DB_USER'], $config['DB_PASS'], $options);
|
|
21
|
+
}
|
|
22
|
+
return self::$instance;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
CREATE DATABASE example_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
USE example_db;
|
|
2
|
+
|
|
3
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
4
|
+
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
5
|
+
nickname VARCHAR(50) NOT NULL UNIQUE,
|
|
6
|
+
email VARCHAR(255) NOT NULL UNIQUE,
|
|
7
|
+
password VARCHAR(255) NOT NULL,
|
|
8
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
9
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
declare(strict_types=1);
|
|
3
|
+
|
|
4
|
+
require_once __DIR__ . '/../Database.php';
|
|
5
|
+
|
|
6
|
+
class User {
|
|
7
|
+
private PDO $db;
|
|
8
|
+
|
|
9
|
+
public function __construct() {
|
|
10
|
+
$this->db = Database::getInstance();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public function getAll(): array {
|
|
14
|
+
return $this->db->query("SELECT id, nickname, email, created_at FROM users")->fetchAll();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public function getById(int $id): ?array {
|
|
18
|
+
$stmt = $this->db->prepare("SELECT id, nickname, email, created_at FROM users WHERE id = :id");
|
|
19
|
+
$stmt->execute(['id' => $id]);
|
|
20
|
+
return $stmt->fetch() ?: null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public function findByEmail(string $email): ?array {
|
|
24
|
+
$stmt = $this->db->prepare("SELECT id, nickname, email, password, created_at FROM users WHERE email = :email");
|
|
25
|
+
$stmt->execute(['email' => filter_var(trim($email), FILTER_SANITIZE_EMAIL)]);
|
|
26
|
+
return $stmt->fetch() ?: null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public function create(string $nickname, string $email, string $password = ''): int {
|
|
30
|
+
$stmt = $this->db->prepare("INSERT INTO users (nickname, email, password) VALUES (:nickname, :email, :password)");
|
|
31
|
+
$stmt->execute([
|
|
32
|
+
'nickname' => htmlspecialchars(strip_tags(trim($nickname))),
|
|
33
|
+
'email' => filter_var(trim($email), FILTER_SANITIZE_EMAIL),
|
|
34
|
+
'password' => $password !== '' ? password_hash($password, PASSWORD_BCRYPT) : '',
|
|
35
|
+
]);
|
|
36
|
+
return (int)$this->db->lastInsertId();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public function update(int $id, array $data): bool {
|
|
40
|
+
$fields = [];
|
|
41
|
+
$params = ['id' => $id];
|
|
42
|
+
|
|
43
|
+
if (isset($data['nickname'])) {
|
|
44
|
+
$fields[] = 'nickname = :nickname';
|
|
45
|
+
$params['nickname'] = htmlspecialchars(strip_tags($data['nickname']));
|
|
46
|
+
}
|
|
47
|
+
if (isset($data['email'])) {
|
|
48
|
+
$fields[] = 'email = :email';
|
|
49
|
+
$params['email'] = filter_var($data['email'], FILTER_SANITIZE_EMAIL);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (empty($fields)) return false;
|
|
53
|
+
|
|
54
|
+
$sql = "UPDATE users SET " . implode(', ', $fields) . " WHERE id = :id";
|
|
55
|
+
return $this->db->prepare($sql)->execute($params);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public function delete(int $id): bool {
|
|
59
|
+
return $this->db->prepare("DELETE FROM users WHERE id = :id")->execute(['id' => $id]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
declare(strict_types=1);
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
// 2. Richiamo il tuo modulo Response e il Modello
|
|
6
|
+
require_once __DIR__ . '/../../core/modules/Response.php';
|
|
7
|
+
require_once __DIR__ . '/../../database/models/User.php';
|
|
8
|
+
|
|
9
|
+
$user = new User();
|
|
10
|
+
$id = isset($requestParams[0]) ? (int)$requestParams[0] : null;
|
|
11
|
+
$input = json_decode(file_get_contents('php://input'), true) ?? [];
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
switch ($method) {
|
|
15
|
+
case 'GET':
|
|
16
|
+
$data = $id ? $user->getById($id) : $user->getAll();
|
|
17
|
+
if ($id && !$data) {
|
|
18
|
+
Response::error('User not found', 404);
|
|
19
|
+
}
|
|
20
|
+
// Sostituito con Response::success()
|
|
21
|
+
Response::success($data);
|
|
22
|
+
break;
|
|
23
|
+
|
|
24
|
+
case 'POST':
|
|
25
|
+
if (empty($input['nickname']) || empty($input['email'])) {
|
|
26
|
+
Response::error('Missing fields', 400);
|
|
27
|
+
}
|
|
28
|
+
if (!filter_var($input['email'], FILTER_VALIDATE_EMAIL)) {
|
|
29
|
+
Response::error('Invalid email', 400);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
$newId = $user->create($input['nickname'], $input['email']);
|
|
33
|
+
http_response_code(201);
|
|
34
|
+
Response::success(['message' => 'Created', 'id' => $newId]);
|
|
35
|
+
break;
|
|
36
|
+
|
|
37
|
+
case 'PUT':
|
|
38
|
+
case 'PATCH':
|
|
39
|
+
if (!$id) Response::error('ID required', 400);
|
|
40
|
+
if (!$user->update($id, $input)) {
|
|
41
|
+
Response::error('Not found or no changes', 404);
|
|
42
|
+
}
|
|
43
|
+
Response::success(['message' => 'Updated']);
|
|
44
|
+
break;
|
|
45
|
+
|
|
46
|
+
case 'DELETE':
|
|
47
|
+
if (!$id) Response::error('ID required', 400);
|
|
48
|
+
if (!$user->delete($id)) {
|
|
49
|
+
Response::error('Not found', 404);
|
|
50
|
+
}
|
|
51
|
+
Response::success(['message' => 'Deleted']);
|
|
52
|
+
break;
|
|
53
|
+
|
|
54
|
+
default:
|
|
55
|
+
Response::error('Method not allowed', 405);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
} catch (PDOException $e) {
|
|
59
|
+
if ($e->getCode() === '23000') {
|
|
60
|
+
Response::error('Nickname or email already exists', 409);
|
|
61
|
+
}
|
|
62
|
+
Response::error('Database error', 500);
|
|
63
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
declare(strict_types=1);
|
|
3
|
+
|
|
4
|
+
require_once __DIR__ . '/../../core/modules/Response.php';
|
|
5
|
+
|
|
6
|
+
if ($method !== 'GET') {
|
|
7
|
+
Response::error('Method not allowed', 405);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
Response::success([
|
|
11
|
+
'message' => 'Protected endpoint is working',
|
|
12
|
+
'params' => $requestParams,
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
Response::error([
|
|
16
|
+
'message' => 'Error text',
|
|
17
|
+
]);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
declare(strict_types=1);
|
|
3
|
+
|
|
4
|
+
require_once __DIR__ . '/../../../core/modules/Response.php';
|
|
5
|
+
require_once __DIR__ . '/../../../database/models/User.php';
|
|
6
|
+
|
|
7
|
+
if ($method !== 'POST') {
|
|
8
|
+
Response::error('Method not allowed', 405);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
$input = json_decode(file_get_contents('php://input'), true) ?? [];
|
|
12
|
+
|
|
13
|
+
$email = trim(filter_var($input['email'] ?? '', FILTER_SANITIZE_EMAIL));
|
|
14
|
+
$password = trim($input['password'] ?? '');
|
|
15
|
+
|
|
16
|
+
if (empty($email) || empty($password)) {
|
|
17
|
+
Response::error('Missing fields', 400);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
21
|
+
Response::error('Invalid email', 400);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
$user = new User();
|
|
25
|
+
$found = $user->findByEmail($email);
|
|
26
|
+
|
|
27
|
+
if (!$found || !password_verify($password, $found['password'])) {
|
|
28
|
+
Response::error('Invalid credentials', 401);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
unset($found['password']);
|
|
32
|
+
Response::success([
|
|
33
|
+
'user' => $found,
|
|
34
|
+
]);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
declare(strict_types=1);
|
|
3
|
+
|
|
4
|
+
require_once __DIR__ . '/../../../core/modules/Response.php';
|
|
5
|
+
require_once __DIR__ . '/../../../database/models/User.php';
|
|
6
|
+
|
|
7
|
+
if ($method !== 'POST') {
|
|
8
|
+
Response::error('Method not allowed', 405);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
$input = json_decode(file_get_contents('php://input'), true) ?? [];
|
|
12
|
+
|
|
13
|
+
$nickname = htmlspecialchars(strip_tags(trim($input['nickname'] ?? '')));
|
|
14
|
+
$email = trim(filter_var($input['email'] ?? '', FILTER_SANITIZE_EMAIL));
|
|
15
|
+
$password = trim($input['password'] ?? '');
|
|
16
|
+
|
|
17
|
+
if (empty($nickname) || empty($email) || empty($password)) {
|
|
18
|
+
Response::error('Missing fields', 400);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
22
|
+
Response::error('Invalid email', 400);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (strlen($password) < 8) {
|
|
26
|
+
Response::error('Password must be at least 8 characters', 400);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
$user = new User();
|
|
31
|
+
$newId = $user->create($nickname, $email, $password);
|
|
32
|
+
http_response_code(201);
|
|
33
|
+
Response::success(['id' => $newId]);
|
|
34
|
+
} catch (PDOException $e) {
|
|
35
|
+
if ($e->getCode() === '23000') {
|
|
36
|
+
Response::error('Nickname or email already exists', 409);
|
|
37
|
+
}
|
|
38
|
+
Response::error('Database error', 500);
|
|
39
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
declare(strict_types=1);
|
|
3
|
+
|
|
4
|
+
require_once __DIR__ . '/../../core/modules/Response.php';
|
|
5
|
+
|
|
6
|
+
if ($method !== 'GET') {
|
|
7
|
+
Response::error('Method not allowed', 405);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
Response::success([
|
|
11
|
+
'message' => 'Public endpoint is working',
|
|
12
|
+
'params' => $requestParams,
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
Response::error([
|
|
16
|
+
'message' => 'Error text',
|
|
17
|
+
]);
|
package/src/api/web.config
CHANGED
|
@@ -4,35 +4,17 @@
|
|
|
4
4
|
<rewrite>
|
|
5
5
|
<rules>
|
|
6
6
|
|
|
7
|
-
<!-- 1. Permetti index.php
|
|
7
|
+
<!-- 1. Permetti l'esecuzione diretta solo a index.php -->
|
|
8
8
|
<rule name="Allow API index.php" stopProcessing="true">
|
|
9
|
-
<match url="^index\.php$" ignoreCase="true" />
|
|
9
|
+
<match url="^core/index\.php$" ignoreCase="true" />
|
|
10
10
|
<action type="None" />
|
|
11
11
|
</rule>
|
|
12
12
|
|
|
13
|
-
<!--
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<action type="Rewrite" url="index.php" />
|
|
17
|
-
</rule>
|
|
18
|
-
|
|
19
|
-
<!-- 2b. Manda le richieste per le sottocartelle protette a index.php -->
|
|
20
|
-
<rule name="Protect Core API Directories" stopProcessing="true">
|
|
21
|
-
<match url="^(core|endpoints|modules|vendor)($|/)" ignoreCase="true" />
|
|
22
|
-
<action type="Rewrite" url="index.php" />
|
|
23
|
-
</rule>
|
|
24
|
-
|
|
25
|
-
<!-- 3. Regola per gli endpoint (se non è un file o una cartella, vai a index.php) -->
|
|
26
|
-
<rule name="API Endpoints Front Controller" stopProcessing="true">
|
|
13
|
+
<!-- 2. Front Controller: Invia TUTTO il resto a index.php -->
|
|
14
|
+
<!-- Nessun controllo sull'esistenza di file o directory. -->
|
|
15
|
+
<rule name="API Endpoints Front Controller Catch-All" stopProcessing="true">
|
|
27
16
|
<match url="^(.*)$" ignoreCase="true" />
|
|
28
|
-
<
|
|
29
|
-
<!-- !-f -->
|
|
30
|
-
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
|
31
|
-
<!-- !-d -->
|
|
32
|
-
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
|
|
33
|
-
</conditions>
|
|
34
|
-
<!-- appendQueryString="true" equivale al flag [QSA] di Apache -->
|
|
35
|
-
<action type="Rewrite" url="index.php" appendQueryString="true" />
|
|
17
|
+
<action type="Rewrite" url="core/index.php" appendQueryString="true" />
|
|
36
18
|
</rule>
|
|
37
19
|
|
|
38
20
|
</rules>
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
<h2 data-lang-key="test"></h2>
|
|
4
4
|
|
|
5
5
|
<div class="btn-group" role="group">
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
{# enLangName is the key that contains the language name accordint to the current language #}
|
|
8
|
+
<input type="radio" class="btn-check" name="lang-button" id="lang-en" value="en"/>
|
|
7
9
|
<label class="btn btn-outline-primary" for="lang-en" data-lang-key="enLangName"></label>
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
{# itLangName is the key that contains the language name accordint to the current language #}
|
|
12
|
+
<input type="radio" class="btn-check" name="lang-button" id="lang-it" value="it"/>
|
|
10
13
|
<label class="btn btn-outline-primary" for="lang-it" data-lang-key="itLangName"></label>
|
|
11
14
|
</div>
|
|
12
15
|
</div>
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
<meta property="og:type" content="website">
|
|
26
26
|
<meta property="og:url" content="{{ site.url }}{{ page.url }}">
|
|
27
27
|
<meta property="og:image" content="{{ site.url }}{{ site.logo }}">
|
|
28
|
-
<meta property="og:
|
|
28
|
+
<meta property="og:site-name" content="{{ site.site-name }}">
|
|
29
29
|
|
|
30
30
|
<!-- Twitter Card -->
|
|
31
31
|
<meta name="twitter:card" content="summary_large_image">
|
|
@@ -33,6 +33,31 @@
|
|
|
33
33
|
<meta name="twitter:description" content="{{ pageData.seo.description }}">
|
|
34
34
|
<meta name="twitter:image" content="{{ site.url }}{{ site.logo }}">
|
|
35
35
|
|
|
36
|
+
<!-- ========================================== -->
|
|
37
|
+
<!-- JSON-LD / Structured data for SEO and AI -->
|
|
38
|
+
<!-- ========================================== -->
|
|
39
|
+
<script type="application/ld+json">
|
|
40
|
+
{
|
|
41
|
+
"@context": "https://schema.org",
|
|
42
|
+
"@type": "WebPage",
|
|
43
|
+
"name": "{{ pageData.seo.title or title or site.title }}",
|
|
44
|
+
"description": "{{ pageData.seo.description or site.description }}",
|
|
45
|
+
"url": "{{ site.url }}{{ page.url }}",
|
|
46
|
+
"author": {
|
|
47
|
+
"@type": "Person",
|
|
48
|
+
"name": "{{ site.author }}"
|
|
49
|
+
},
|
|
50
|
+
"publisher": {
|
|
51
|
+
"@type": "Organization",
|
|
52
|
+
"name": "{{ site.site_name }}",
|
|
53
|
+
"logo": {
|
|
54
|
+
"@type": "ImageObject",
|
|
55
|
+
"url": "{{ site.url }}{{ site.logo }}"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
</script>
|
|
60
|
+
|
|
36
61
|
<!-- Favicon -->
|
|
37
62
|
<link rel="icon" type="image/svg+xml" href="{{ site.favicon }}">
|
|
38
63
|
<!-- (If you want to use a PNG favicon) -->
|
package/src/data/site.json
CHANGED
|
@@ -1,54 +1,54 @@
|
|
|
1
|
-
{
|
|
2
|
-
"site_name": "Site name",
|
|
3
|
-
"title": "Site title",
|
|
4
|
-
"description": "Site description",
|
|
5
|
-
"keywords": "keyword1, keyword2, keyword3",
|
|
6
|
-
"domain": "yoursite.com",
|
|
7
|
-
"url": "https://yoursite.com",
|
|
8
|
-
"lang": "en",
|
|
9
|
-
"author": "Name and surname",
|
|
10
|
-
"data_bs_theme": "dark",
|
|
11
|
-
"favicon": "/assets/brand/favicon.svg",
|
|
12
|
-
"logo": "/assets/brand/logo.svg",
|
|
13
|
-
"copyright": {
|
|
14
|
-
"year": "2026",
|
|
15
|
-
"text": "Copyright text"
|
|
16
|
-
},
|
|
17
|
-
"legal": {
|
|
18
|
-
"privacy": "",
|
|
19
|
-
"cookie": "",
|
|
20
|
-
"cookieControls": "",
|
|
21
|
-
"terms": ""
|
|
22
|
-
},
|
|
23
|
-
"pages": {
|
|
24
|
-
"404": {
|
|
25
|
-
"seo": {
|
|
26
|
-
"title": "404 - Not found"
|
|
27
|
-
},
|
|
28
|
-
"cdn": {
|
|
29
|
-
"css": [],
|
|
30
|
-
"js": []
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
"homepage": {
|
|
34
|
-
"seo": {
|
|
35
|
-
"title": "Homepage",
|
|
36
|
-
"description": "Description"
|
|
37
|
-
},
|
|
38
|
-
"cdn": {
|
|
39
|
-
"css": [],
|
|
40
|
-
"js": []
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
"anotherPage": {
|
|
44
|
-
"seo": {
|
|
45
|
-
"title": "Another Page",
|
|
46
|
-
"description": "description"
|
|
47
|
-
},
|
|
48
|
-
"cdn": {
|
|
49
|
-
"css": [],
|
|
50
|
-
"js": []
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"site_name": "Site name",
|
|
3
|
+
"title": "Site title",
|
|
4
|
+
"description": "Site description",
|
|
5
|
+
"keywords": "keyword1, keyword2, keyword3",
|
|
6
|
+
"domain": "yoursite.com",
|
|
7
|
+
"url": "https://yoursite.com",
|
|
8
|
+
"lang": "en",
|
|
9
|
+
"author": "Name and surname",
|
|
10
|
+
"data_bs_theme": "dark",
|
|
11
|
+
"favicon": "/assets/brand/favicon.svg",
|
|
12
|
+
"logo": "/assets/brand/logo.svg",
|
|
13
|
+
"copyright": {
|
|
14
|
+
"year": "2026",
|
|
15
|
+
"text": "Copyright text"
|
|
16
|
+
},
|
|
17
|
+
"legal": {
|
|
18
|
+
"privacy": "",
|
|
19
|
+
"cookie": "",
|
|
20
|
+
"cookieControls": "",
|
|
21
|
+
"terms": ""
|
|
22
|
+
},
|
|
23
|
+
"pages": {
|
|
24
|
+
"404": {
|
|
25
|
+
"seo": {
|
|
26
|
+
"title": "404 - Not found"
|
|
27
|
+
},
|
|
28
|
+
"cdn": {
|
|
29
|
+
"css": [],
|
|
30
|
+
"js": []
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"homepage": {
|
|
34
|
+
"seo": {
|
|
35
|
+
"title": "Homepage",
|
|
36
|
+
"description": "Description"
|
|
37
|
+
},
|
|
38
|
+
"cdn": {
|
|
39
|
+
"css": [],
|
|
40
|
+
"js": []
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"anotherPage": {
|
|
44
|
+
"seo": {
|
|
45
|
+
"title": "Another Page",
|
|
46
|
+
"description": "description"
|
|
47
|
+
},
|
|
48
|
+
"cdn": {
|
|
49
|
+
"css": [],
|
|
50
|
+
"js": []
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
54
|
}
|