create-berna-stencil 2.0.4 → 2.0.6
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/_tools/res/templates/template.js +5 -3
- package/bin/create.js +213 -35
- package/docs/Backend.md +15 -108
- package/docs/Javascript.md +3 -2
- package/package.json +2 -2
- package/src/backend/config.example.php +3 -2
- package/src/backend/config.php +2 -1
- package/src/frontend/404.njk +1 -1
- package/src/frontend/components/welcome.njk +220 -187
- package/src/frontend/js/pages/404.js +5 -3
- package/src/frontend/js/pages/homepage.js +5 -3
- package/src/frontend/llms.njk +1 -1
- package/src/frontend/scss/modules/_global.scss +6 -0
- package/src/frontend/scss/pages/404.scss +9 -4
- package/src/frontend/scss/pages/homepage.scss +1 -1
- package/src/backend/api/protected/auth-system.php +0 -67
- package/src/backend/api/public/auth/login.php +0 -38
- package/src/backend/api/public/auth/register.php +0 -44
- package/src/backend/database/migrations/create_users_table.sql +0 -9
- package/src/backend/database/models/User.php +0 -61
- /package/src/backend/api/protected/{subfolder/example-protected.php → example-protected.php} +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
//===========================
|
|
2
2
|
// JAVASCRIPT MODULES IMPORTS
|
|
3
|
-
|
|
3
|
+
//===========================
|
|
4
4
|
|
|
5
5
|
// Call anywhere
|
|
6
6
|
import { showNotification } from '../modules/notification.js';
|
|
7
7
|
|
|
8
|
-
// Uncomment
|
|
8
|
+
// Uncomment of pre-existing modules
|
|
9
9
|
// import { initTextAreaAutoExpand } from '../modules/forms/textAreaAutoExpand.js';
|
|
10
10
|
// import { initNormalizePhoneNumber } from '../modules/forms/normalizePhoneNumber.js';
|
|
11
11
|
|
|
@@ -14,6 +14,8 @@ import { showNotification } from '../modules/notification.js';
|
|
|
14
14
|
//==========================
|
|
15
15
|
|
|
16
16
|
document.addEventListener("DOMContentLoaded", () => {
|
|
17
|
+
// initTextAreaAutoExpand();
|
|
18
|
+
// initNormalizePhoneNumber();
|
|
17
19
|
});
|
|
18
20
|
|
|
19
21
|
showNotification("Example notification", "success", 3000);
|
package/bin/create.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const readline = require('readline');
|
|
5
6
|
|
|
6
7
|
const targetDir = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd();
|
|
7
8
|
const templateDir = path.join(__dirname, '..');
|
|
@@ -15,7 +16,7 @@ const COPY_TARGETS = [
|
|
|
15
16
|
|
|
16
17
|
const PROJECT_PACKAGE = {
|
|
17
18
|
name: path.basename(targetDir),
|
|
18
|
-
version: '2.0.
|
|
19
|
+
version: '2.0.6',
|
|
19
20
|
private: true,
|
|
20
21
|
scripts: {
|
|
21
22
|
"build:css": "sass src/frontend/scss:out/css --no-source-map --style=compressed --quiet --load-path=node_modules",
|
|
@@ -54,12 +55,63 @@ out/
|
|
|
54
55
|
src/backend/config.php
|
|
55
56
|
`;
|
|
56
57
|
|
|
58
|
+
// Framework configurations
|
|
59
|
+
const FRAMEWORKS = {
|
|
60
|
+
bootstrap: {
|
|
61
|
+
label: 'Bootstrap',
|
|
62
|
+
scss: 'bootstrap',
|
|
63
|
+
njk: [
|
|
64
|
+
'<script src="/js/bootstrap.bundle.min.js" defer></script>',
|
|
65
|
+
],
|
|
66
|
+
eleventy: [
|
|
67
|
+
'"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js": "js/bootstrap.bundle.min.js",',
|
|
68
|
+
'"node_modules/bootstrap-icons/font/fonts": "css/fonts",',
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
bulma: {
|
|
72
|
+
label: 'Bulma',
|
|
73
|
+
scss: 'bulma',
|
|
74
|
+
njk: [],
|
|
75
|
+
eleventy: [],
|
|
76
|
+
},
|
|
77
|
+
foundation: {
|
|
78
|
+
label: 'Foundation',
|
|
79
|
+
scss: 'foundation',
|
|
80
|
+
njk: ['<script src="/js/foundation.min.js" defer></script>'],
|
|
81
|
+
eleventy: ['"node_modules/foundation-sites/dist/js/foundation.min.js": "js/foundation.min.js",'],
|
|
82
|
+
},
|
|
83
|
+
uikit: {
|
|
84
|
+
label: 'UIkit',
|
|
85
|
+
scss: 'uikit',
|
|
86
|
+
njk: [
|
|
87
|
+
'<script src="/js/uikit.min.js" defer></script>',
|
|
88
|
+
'<script src="/js/uikit-icons.min.js" defer></script>',
|
|
89
|
+
],
|
|
90
|
+
eleventy: [
|
|
91
|
+
'"node_modules/uikit/dist/js/uikit.min.js": "js/uikit.min.js",',
|
|
92
|
+
'"node_modules/uikit/dist/js/uikit-icons.min.js": "js/uikit-icons.min.js",',
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
none: {
|
|
96
|
+
label: 'None',
|
|
97
|
+
scss: null,
|
|
98
|
+
njk: [],
|
|
99
|
+
eleventy: [],
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const ALL_FRAMEWORKS = ['bootstrap', 'bulma', 'foundation', 'uikit'];
|
|
104
|
+
|
|
57
105
|
const { writeSync } = require('fs');
|
|
58
106
|
|
|
59
107
|
function log(msg) {
|
|
60
108
|
writeSync(1, msg + '\n');
|
|
61
109
|
}
|
|
62
110
|
|
|
111
|
+
function escapeRegex(str) {
|
|
112
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
113
|
+
}
|
|
114
|
+
|
|
63
115
|
function copyRecursive(src, dest) {
|
|
64
116
|
const stat = fs.statSync(src);
|
|
65
117
|
if (stat.isDirectory()) {
|
|
@@ -85,45 +137,171 @@ function deleteFileRecursive(dir, filename) {
|
|
|
85
137
|
}
|
|
86
138
|
}
|
|
87
139
|
|
|
88
|
-
|
|
89
|
-
|
|
140
|
+
function slashComment(content, line) {
|
|
141
|
+
content = content.replace(new RegExp(`^([ \\t]*)// (${escapeRegex(line)})$`, 'gm'), '$1$2');
|
|
142
|
+
return content.replace(new RegExp(`^([ \\t]*)(${escapeRegex(line)})$`, 'gm'), '$1// $2');
|
|
90
143
|
}
|
|
91
144
|
|
|
92
|
-
|
|
145
|
+
function slashUncomment(content, line) {
|
|
146
|
+
return content.replace(new RegExp(`^([ \\t]*)// (${escapeRegex(line)})$`, 'gm'), '$1$2');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function njkComment(content, line) {
|
|
150
|
+
content = content.split(`{# ${line} #}`).join(line);
|
|
151
|
+
return content.split(line).join(`{# ${line} #}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function njkUncomment(content, line) {
|
|
155
|
+
return content.split(`{# ${line} #}`).join(line);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function applyFramework(framework) {
|
|
159
|
+
const config = FRAMEWORKS[framework];
|
|
160
|
+
|
|
161
|
+
const globalScssPath = path.join(targetDir, 'src/frontend/scss/modules/_global.scss');
|
|
162
|
+
if (fs.existsSync(globalScssPath)) {
|
|
163
|
+
let content = fs.readFileSync(globalScssPath, 'utf8');
|
|
164
|
+
ALL_FRAMEWORKS.forEach(fw => {
|
|
165
|
+
content = slashComment(content, `@import "../modules/frameworks/${fw}";`);
|
|
166
|
+
});
|
|
167
|
+
if (config.scss) {
|
|
168
|
+
content = slashUncomment(content, `@import "../modules/frameworks/${config.scss}";`);
|
|
169
|
+
}
|
|
170
|
+
fs.writeFileSync(globalScssPath, content);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const baseNjkPath = path.join(targetDir, 'src/frontend/components/layouts/base.njk');
|
|
174
|
+
if (fs.existsSync(baseNjkPath)) {
|
|
175
|
+
let content = fs.readFileSync(baseNjkPath, 'utf8');
|
|
176
|
+
ALL_FRAMEWORKS.forEach(fw => {
|
|
177
|
+
FRAMEWORKS[fw].njk.forEach(line => {
|
|
178
|
+
content = njkComment(content, line);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
config.njk.forEach(line => {
|
|
182
|
+
content = njkUncomment(content, line);
|
|
183
|
+
});
|
|
184
|
+
fs.writeFileSync(baseNjkPath, content);
|
|
185
|
+
}
|
|
93
186
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
187
|
+
const eleventyPath = path.join(targetDir, '.eleventy.js');
|
|
188
|
+
if (fs.existsSync(eleventyPath)) {
|
|
189
|
+
let content = fs.readFileSync(eleventyPath, 'utf8');
|
|
190
|
+
ALL_FRAMEWORKS.forEach(fw => {
|
|
191
|
+
FRAMEWORKS[fw].eleventy.forEach(line => {
|
|
192
|
+
content = slashComment(content, line);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
config.eleventy.forEach(line => {
|
|
196
|
+
content = slashUncomment(content, line);
|
|
197
|
+
});
|
|
198
|
+
fs.writeFileSync(eleventyPath, content);
|
|
100
199
|
}
|
|
101
200
|
}
|
|
102
201
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
202
|
+
function askFramework() {
|
|
203
|
+
return new Promise((resolve) => {
|
|
204
|
+
const choices = [
|
|
205
|
+
{ label: 'Bootstrap', value: 'bootstrap' },
|
|
206
|
+
{ label: 'Bulma', value: 'bulma' },
|
|
207
|
+
{ label: 'Foundation', value: 'foundation' },
|
|
208
|
+
{ label: 'UIkit', value: 'uikit' },
|
|
209
|
+
{ label: 'None', value: 'none' }
|
|
210
|
+
];
|
|
211
|
+
let selectedIndex = 0;
|
|
212
|
+
|
|
213
|
+
log('\n>> Select a CSS framework (Use arrow keys and press Enter):\n');
|
|
214
|
+
|
|
215
|
+
const render = (firstTime = false) => {
|
|
216
|
+
if (!firstTime) {
|
|
217
|
+
process.stdout.write(`\x1B[${choices.length}A`);
|
|
218
|
+
}
|
|
219
|
+
let output = '';
|
|
220
|
+
choices.forEach((choice, index) => {
|
|
221
|
+
if (index === selectedIndex) {
|
|
222
|
+
output += ` \x1b[36m◉ ${choice.label}\x1b[0m\x1B[K\n`;
|
|
223
|
+
} else {
|
|
224
|
+
output += ` * ${choice.label}\x1B[K\n`;
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
process.stdout.write(output);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
readline.emitKeypressEvents(process.stdin);
|
|
231
|
+
if (process.stdin.isTTY) {
|
|
232
|
+
process.stdin.setRawMode(true);
|
|
233
|
+
}
|
|
234
|
+
process.stdin.resume();
|
|
235
|
+
|
|
236
|
+
const onKeyPress = (str, key) => {
|
|
237
|
+
if (key.ctrl && key.name === 'c') {
|
|
238
|
+
process.exit();
|
|
239
|
+
} else if (key.name === 'up') {
|
|
240
|
+
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : choices.length - 1;
|
|
241
|
+
render();
|
|
242
|
+
} else if (key.name === 'down') {
|
|
243
|
+
selectedIndex = selectedIndex < choices.length - 1 ? selectedIndex + 1 : 0;
|
|
244
|
+
render();
|
|
245
|
+
} else if (key.name === 'return' || key.name === 'enter') {
|
|
246
|
+
process.stdin.removeListener('keypress', onKeyPress);
|
|
247
|
+
if (process.stdin.isTTY) {
|
|
248
|
+
process.stdin.setRawMode(false);
|
|
249
|
+
}
|
|
250
|
+
process.stdin.pause();
|
|
251
|
+
resolve(choices[selectedIndex].value);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
process.stdin.on('keypress', onKeyPress);
|
|
256
|
+
render(true);
|
|
257
|
+
});
|
|
109
258
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
fs.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
log(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
259
|
+
|
|
260
|
+
async function init() {
|
|
261
|
+
if (!fs.existsSync(targetDir)) {
|
|
262
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
log(`\n>> Creating berna-stencil project in ${targetDir}\n`);
|
|
266
|
+
|
|
267
|
+
for (const target of COPY_TARGETS) {
|
|
268
|
+
const src = path.join(templateDir, target);
|
|
269
|
+
const dest = path.join(targetDir, target);
|
|
270
|
+
if (fs.existsSync(src)) {
|
|
271
|
+
copyRecursive(src, dest);
|
|
272
|
+
log(`+ ${target}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const configDest = path.join(targetDir, 'src/backend/config.php');
|
|
277
|
+
const configExample = path.join(targetDir, 'src/backend/config.example.php');
|
|
278
|
+
if (!fs.existsSync(configDest) && fs.existsSync(configExample)) {
|
|
279
|
+
fs.copyFileSync(configExample, configDest);
|
|
280
|
+
log('+ src/backend/config.php');
|
|
281
|
+
}
|
|
282
|
+
deleteFileRecursive(targetDir, 'config.example.php');
|
|
283
|
+
|
|
284
|
+
fs.writeFileSync(
|
|
285
|
+
path.join(targetDir, 'package.json'),
|
|
286
|
+
JSON.stringify(PROJECT_PACKAGE, null, 2)
|
|
287
|
+
);
|
|
288
|
+
log('+ package.json');
|
|
289
|
+
|
|
290
|
+
fs.writeFileSync(
|
|
291
|
+
path.join(targetDir, '.gitignore'),
|
|
292
|
+
GITIGNORE_CONTENT
|
|
293
|
+
);
|
|
294
|
+
log('+ .gitignore');
|
|
295
|
+
|
|
296
|
+
const framework = await askFramework();
|
|
297
|
+
applyFramework(framework);
|
|
298
|
+
|
|
299
|
+
log(`\n>> Done! Now run:\n`);
|
|
300
|
+
if (process.argv[2]) {
|
|
301
|
+
log(`cd ${process.argv[2]}`);
|
|
302
|
+
}
|
|
303
|
+
log('npm install');
|
|
304
|
+
log('npm run serve\n');
|
|
127
305
|
}
|
|
128
|
-
|
|
129
|
-
|
|
306
|
+
|
|
307
|
+
init();
|
package/docs/Backend.md
CHANGED
|
@@ -9,7 +9,7 @@ src/backend/
|
|
|
9
9
|
├── api/
|
|
10
10
|
│ ├── public/ # Endpoints accessible without an API key
|
|
11
11
|
│ └── protected/ # Endpoints requiring X-Api-Key header
|
|
12
|
-
├──
|
|
12
|
+
├── db/
|
|
13
13
|
│ ├── Database.php
|
|
14
14
|
│ ├── models/
|
|
15
15
|
│ └── migrations/
|
|
@@ -26,11 +26,13 @@ Copy `config.example.php` to `config.php` and fill in your values:
|
|
|
26
26
|
### config.php <small>(`src/backend/`)</small>
|
|
27
27
|
```php
|
|
28
28
|
return [
|
|
29
|
-
|
|
30
|
-
'API_KEY' => '
|
|
29
|
+
// Default key for protected endpoints that don't have a specific key in ENDPOINT_KEYS
|
|
30
|
+
'API_KEY' => 'default-key',
|
|
31
31
|
|
|
32
|
+
// If you want restrict access to protected endpoints to specific clients, you can define custom keys for each endpoint
|
|
33
|
+
// For subfolder endpoints, use the relative path ('subfolder/endpoint')
|
|
32
34
|
'ENDPOINT_KEYS' => [
|
|
33
|
-
'
|
|
35
|
+
'example-protected' => 'custom-key',
|
|
34
36
|
],
|
|
35
37
|
|
|
36
38
|
'DB_HOST' => '127.0.0.1',
|
|
@@ -57,7 +59,7 @@ Every endpoint file has access to:
|
|
|
57
59
|
|
|
58
60
|
Create a `.php` file anywhere inside `api/public/`
|
|
59
61
|
|
|
60
|
-
### api/public/
|
|
62
|
+
### api/public/example.php
|
|
61
63
|
```php
|
|
62
64
|
<?php
|
|
63
65
|
declare(strict_types=1);
|
|
@@ -73,13 +75,11 @@ $id = isset($requestParams[0]) ? (int)$requestParams[0] : null;
|
|
|
73
75
|
Response::success(['id' => $id]);
|
|
74
76
|
```
|
|
75
77
|
|
|
76
|
-
Reachable at `/api/posts` or `/api/posts/42`
|
|
77
|
-
|
|
78
78
|
## Creating a protected endpoint
|
|
79
79
|
|
|
80
80
|
Create a `.php` file inside `api/protected/`. The API key check happens automatically before your file runs.
|
|
81
81
|
|
|
82
|
-
### api/protected/
|
|
82
|
+
### api/protected/example.php
|
|
83
83
|
```php
|
|
84
84
|
<?php
|
|
85
85
|
declare(strict_types=1);
|
|
@@ -97,114 +97,21 @@ To assign a dedicated key, add it to `config.php`:
|
|
|
97
97
|
|
|
98
98
|
```php
|
|
99
99
|
'ENDPOINT_KEYS' => [
|
|
100
|
-
'
|
|
100
|
+
'endpoint' => 'custom-key',
|
|
101
101
|
],
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
## The Response helper
|
|
105
105
|
|
|
106
106
|
```php
|
|
107
|
-
Response::success($data, $code);
|
|
107
|
+
Response::success($data, $code); // default 200
|
|
108
108
|
Response::error($message, $code, $details); // default 400
|
|
109
|
-
Response::noContent();
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
## Handling multiple methods
|
|
113
|
-
|
|
114
|
-
```php
|
|
115
|
-
$id = isset($requestParams[0]) ? (int)$requestParams[0] : null;
|
|
116
|
-
$input = json_decode(file_get_contents('php://input'), true) ?? [];
|
|
117
|
-
|
|
118
|
-
switch ($method) {
|
|
119
|
-
case 'GET':
|
|
120
|
-
Response::success(['id' => $id]);
|
|
121
|
-
break;
|
|
122
|
-
|
|
123
|
-
case 'POST':
|
|
124
|
-
if (empty($input['title'])) Response::error('Missing title', 400);
|
|
125
|
-
Response::success(['message' => 'Created'], 201);
|
|
126
|
-
break;
|
|
127
|
-
|
|
128
|
-
case 'DELETE':
|
|
129
|
-
if (!$id) Response::error('ID required', 400);
|
|
130
|
-
Response::success(['message' => 'Deleted']);
|
|
131
|
-
break;
|
|
132
|
-
|
|
133
|
-
default:
|
|
134
|
-
Response::error('Method not allowed', 405);
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
## Using the database
|
|
139
|
-
|
|
140
|
-
### database/models/Post.php
|
|
141
|
-
```php
|
|
142
|
-
<?php
|
|
143
|
-
declare(strict_types=1);
|
|
144
|
-
|
|
145
|
-
require_once __DIR__ . '/../Database.php';
|
|
146
|
-
|
|
147
|
-
class Post {
|
|
148
|
-
private PDO $db;
|
|
149
|
-
|
|
150
|
-
public function __construct() {
|
|
151
|
-
$this->db = Database::getInstance();
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
public function getAll(): array {
|
|
155
|
-
return $this->db->query("SELECT * FROM posts")->fetchAll();
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
public function getById(int $id): ?array {
|
|
159
|
-
$stmt = $this->db->prepare("SELECT * FROM posts WHERE id = :id");
|
|
160
|
-
$stmt->execute(['id' => $id]);
|
|
161
|
-
return $stmt->fetch() ?: null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
public function create(string $title): int {
|
|
165
|
-
$stmt = $this->db->prepare("INSERT INTO posts (title) VALUES (:title)");
|
|
166
|
-
$stmt->execute(['title' => htmlspecialchars(strip_tags(trim($title)))]);
|
|
167
|
-
return (int)$this->db->lastInsertId();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
Then use it inside an endpoint:
|
|
173
|
-
|
|
174
|
-
```php
|
|
175
|
-
require_once __DIR__ . '/../../database/models/Post.php';
|
|
176
|
-
|
|
177
|
-
$post = new Post();
|
|
178
|
-
Response::success($post->getAll());
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
Migrations live in `database/migrations/` as plain SQL files — run them manually against your database.
|
|
182
|
-
|
|
183
|
-
## Calling endpoints from the frontend
|
|
184
|
-
|
|
185
|
-
```js
|
|
186
|
-
// Public
|
|
187
|
-
const res = await fetch('/api/posts/42');
|
|
188
|
-
|
|
189
|
-
// Protected
|
|
190
|
-
const res = await fetch('/api/admin/stats', {
|
|
191
|
-
headers: { 'X-Api-Key': 'secret-stats-key' }
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// POST
|
|
195
|
-
const res = await fetch('/api/posts', {
|
|
196
|
-
method: 'POST',
|
|
197
|
-
headers: { 'Content-Type': 'application/json', 'X-Api-Key': 'your-key' },
|
|
198
|
-
body: JSON.stringify({ title: 'Hello world' })
|
|
199
|
-
});
|
|
109
|
+
Response::noContent(); // 204
|
|
200
110
|
```
|
|
201
111
|
|
|
202
112
|
## Pre-built endpoints
|
|
203
113
|
|
|
204
|
-
| Route |
|
|
205
|
-
|
|
206
|
-
| `/api/example-public` |
|
|
207
|
-
| `/api/
|
|
208
|
-
| `/api/auth/register` | No | `POST` | Register a new user |
|
|
209
|
-
| `/api/auth/login` | No | `POST` | Login and retrieve user data |
|
|
210
|
-
| `/api/auth-system` | Yes | `GET POST PUT PATCH DELETE` | Full CRUD on users |
|
|
114
|
+
| Route | Method | Description |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `/api/example-public` | `GET` | Example endpoint that doesn't require any key |
|
|
117
|
+
| `/api/example-protected` | `GET` | Example endpoint that requires X-API-KEY |
|
package/docs/Javascript.md
CHANGED
|
@@ -10,11 +10,12 @@ Import only what the page needs.
|
|
|
10
10
|
|
|
11
11
|
### examplePage.js <small>(`src/frontend/js/pages/`)</small>
|
|
12
12
|
```js
|
|
13
|
-
import { initLangSwitcher } from '../modules/langSwitcher.js';
|
|
14
13
|
import { showNotification } from '../modules/notification.js';
|
|
15
14
|
|
|
15
|
+
import { initNormalizePhoneNumber } from '../modules/forms/normalizePhoneNumber.js';
|
|
16
|
+
|
|
16
17
|
document.addEventListener("DOMContentLoaded", () => {
|
|
17
|
-
|
|
18
|
+
initNormalizePhoneNumber();
|
|
18
19
|
});
|
|
19
20
|
|
|
20
21
|
showNotification("Page loaded", "success", 3000);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-berna-stencil",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "Eleventy boilerplate with per-page SCSS/JS pipeline, esbuild bundling, multi-framework CSS support and a built-in page management CLI",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"author": "Michele Garofalo",
|
|
@@ -58,4 +58,4 @@
|
|
|
58
58
|
"assistant": "node _tools/assistant.js",
|
|
59
59
|
"postinstall": "cd src/backend/_core && composer install --quiet"
|
|
60
60
|
}
|
|
61
|
-
}
|
|
61
|
+
}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
declare(strict_types=1);
|
|
3
3
|
|
|
4
4
|
return [
|
|
5
|
-
|
|
5
|
+
// Default key for protected endpoints that don't have a specific key in ENDPOINT_KEYS
|
|
6
|
+
'API_KEY' => 'DEFAULT_KEY',
|
|
6
7
|
|
|
7
8
|
// If you want restrict access to protected endpoints to specific clients, you can define custom keys for each endpoint
|
|
8
9
|
// For subfolder endpoints, use the relative path ('subfolder/endpoint')
|
|
9
10
|
'ENDPOINT_KEYS' => [
|
|
10
|
-
'subfolder/example-protected' => '
|
|
11
|
+
'subfolder/example-protected' => 'custom-key',
|
|
11
12
|
],
|
|
12
13
|
|
|
13
14
|
// Database configuration
|
package/src/backend/config.php
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
declare(strict_types=1);
|
|
3
3
|
|
|
4
4
|
return [
|
|
5
|
-
|
|
5
|
+
// Default key for protected endpoints that don't have a specific key in ENDPOINT_KEYS
|
|
6
|
+
'API_KEY' => 'DEFAULT_KEY',
|
|
6
7
|
|
|
7
8
|
// If you want restrict access to protected endpoints to specific clients, you can define custom keys for each endpoint
|
|
8
9
|
// For subfolder endpoints, use the relative path ('subfolder/endpoint')
|
package/src/frontend/404.njk
CHANGED
|
@@ -7,7 +7,7 @@ layout: base.njk
|
|
|
7
7
|
<!-- !IMPORTANT -->
|
|
8
8
|
<!-- This is the only page that you need to modify statically -->
|
|
9
9
|
|
|
10
|
-
<div class="fade-in
|
|
10
|
+
<div class="fade-in center">
|
|
11
11
|
<h1>Oops! Page not found</h1>
|
|
12
12
|
<a href="/">Return to homepage</a>
|
|
13
13
|
</div>
|