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,4 +1,4 @@
|
|
|
1
|
-
<div class="container my-3">
|
|
1
|
+
<div class="layout-container layout-my-3">
|
|
2
2
|
<h1>Welcome to <span class="berna-stencil">Berna-Stencil</span></h1>
|
|
3
3
|
<div class="slogan">The boilerplate you need, simplified</div>
|
|
4
4
|
|
|
@@ -41,17 +41,30 @@
|
|
|
41
41
|
|
|
42
42
|
<div id="content-welcome" class="tab-content active">
|
|
43
43
|
<div class="grid" style="display: flex; gap: 1rem; flex-wrap: wrap;">
|
|
44
|
-
<a href="https://bernastencil.com" class="card" target="_blank" rel="noopener noreferrer" style="flex: 1; min-width: 250px;">
|
|
45
|
-
<i class="bi bi-book card-icon" aria-hidden="true"></i>
|
|
46
|
-
<h3>Documentation</h3>
|
|
47
|
-
<p>Everything you need to get started, from setup to advanced topics and customizations</p>
|
|
48
|
-
<span class="card-link">Go to documentation <span class="bi bi-arrow-right"></span></span>
|
|
49
|
-
</a>
|
|
50
44
|
<a href="https://github.com/rhaastrake/berna-stencil" class="card" target="_blank" rel="noopener noreferrer" style="flex: 1; min-width: 250px;">
|
|
51
|
-
<
|
|
45
|
+
<svg class="card-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
46
|
+
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path>
|
|
47
|
+
</svg>
|
|
52
48
|
<h3>Github repository</h3>
|
|
53
49
|
<p>Community-driven. Contributions, issues and PRs are welcome.</p>
|
|
54
|
-
<span class="card-link">Open the repository
|
|
50
|
+
<span class="card-link">Open the repository
|
|
51
|
+
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" style="margin-left:4px;">
|
|
52
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
|
|
53
|
+
</svg>
|
|
54
|
+
</span>
|
|
55
|
+
</a>
|
|
56
|
+
<a href="https://github.com/Rhaastrake/Berna-Stencil/tree/main/docs" class="card" target="_blank" rel="noopener noreferrer" style="flex: 1; min-width: 250px;">
|
|
57
|
+
<svg class="card-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
58
|
+
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
|
|
59
|
+
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
|
|
60
|
+
</svg>
|
|
61
|
+
<h3>Documentation</h3>
|
|
62
|
+
<p>Everything you need to get started, from setup to advanced topics and customizations</p>
|
|
63
|
+
<span class="card-link">Go to documentation
|
|
64
|
+
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" style="margin-left:4px;">
|
|
65
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
|
|
66
|
+
</svg>
|
|
67
|
+
</span>
|
|
55
68
|
</a>
|
|
56
69
|
</div>
|
|
57
70
|
</div>
|
|
@@ -215,23 +228,27 @@ body {
|
|
|
215
228
|
<div id="content-javascript" class="tab-content">
|
|
216
229
|
<div class="markdown-body">
|
|
217
230
|
<h2>JavaScript</h2>
|
|
231
|
+
|
|
218
232
|
<h3>Page JS</h3>
|
|
219
233
|
<p>Each page has its own JS entry point in <code>src/frontend/js/pages/</code></p>
|
|
220
234
|
<p>It is bundled and minified by esbuild and loaded automatically by <code>base.njk</code></p>
|
|
221
235
|
<p>Import only what the page needs.</p>
|
|
236
|
+
|
|
222
237
|
<h4>examplePage.js <small>(<code>src/frontend/js/pages/</code>)</small></h4>
|
|
223
|
-
<pre><code>
|
|
224
|
-
|
|
225
|
-
import {
|
|
238
|
+
<pre><code class="language-js">import { showNotification } from '../modules/notification.js';
|
|
239
|
+
|
|
240
|
+
import { initNormalizePhoneNumber } from '../modules/forms/normalizePhoneNumber.js';
|
|
226
241
|
|
|
227
242
|
document.addEventListener("DOMContentLoaded", () => {
|
|
228
|
-
|
|
243
|
+
initNormalizePhoneNumber();
|
|
229
244
|
});
|
|
230
245
|
|
|
231
246
|
showNotification("Page loaded", "success", 3000);</code></pre>
|
|
232
|
-
|
|
247
|
+
|
|
248
|
+
<h2>Modules</h2>
|
|
233
249
|
<p>Modules live in <code>src/frontend/js/modules/</code>. Some must be called inside <code>DOMContentLoaded</code> as they interact with the DOM; others create elements dynamically and can be called anywhere.</p>
|
|
234
|
-
|
|
250
|
+
|
|
251
|
+
<h3>Call inside <code>DOMContentLoaded</code></h3>
|
|
235
252
|
<table>
|
|
236
253
|
<thead>
|
|
237
254
|
<tr><th>Module</th><th>Function</th></tr>
|
|
@@ -243,7 +260,8 @@ showNotification("Page loaded", "success", 3000);</code></pre>
|
|
|
243
260
|
<tr><td><code>modules/forms/normalizePhoneNumber.js</code></td><td><code>initNormalizePhoneNumber()</code></td></tr>
|
|
244
261
|
</tbody>
|
|
245
262
|
</table>
|
|
246
|
-
|
|
263
|
+
|
|
264
|
+
<h3>Call anywhere</h3>
|
|
247
265
|
<table>
|
|
248
266
|
<thead>
|
|
249
267
|
<tr><th>Module</th><th>Function</th></tr>
|
|
@@ -252,7 +270,8 @@ showNotification("Page loaded", "success", 3000);</code></pre>
|
|
|
252
270
|
<tr><td><code>modules/notification.js</code></td><td><code>showNotification(text, type, duration)</code></td></tr>
|
|
253
271
|
</tbody>
|
|
254
272
|
</table>
|
|
255
|
-
|
|
273
|
+
|
|
274
|
+
<h3><code>showNotification</code> parameters</h3>
|
|
256
275
|
<table>
|
|
257
276
|
<thead>
|
|
258
277
|
<tr><th>Parameter</th><th>Type</th><th>Default</th><th>Values</th></tr>
|
|
@@ -263,18 +282,25 @@ showNotification("Page loaded", "success", 3000);</code></pre>
|
|
|
263
282
|
<tr><td><code>duration</code></td><td>number</td><td><code>5000</code></td><td>ms, or <code>-1</code> for persistent with spinner</td></tr>
|
|
264
283
|
</tbody>
|
|
265
284
|
</table>
|
|
266
|
-
|
|
285
|
+
|
|
286
|
+
<h2>Adding a module</h2>
|
|
267
287
|
<p>Create a new <code>.js</code> file in <code>src/frontend/js/modules/</code>. You can organize them into subfolders freely.</p>
|
|
268
288
|
<p>Use ESM syntax — esbuild handles the bundling:</p>
|
|
269
|
-
|
|
289
|
+
|
|
290
|
+
<h4>yourModule.js <small>(<code>src/frontend/js/modules/</code>)</small></h4>
|
|
291
|
+
<pre><code class="language-js">
|
|
270
292
|
export function yourFunction() {
|
|
271
293
|
// ...
|
|
272
294
|
}</code></pre>
|
|
295
|
+
|
|
273
296
|
<p>Then import it in the pages that need it:</p>
|
|
274
|
-
|
|
297
|
+
|
|
298
|
+
<pre><code class="language-js">import { yourFunction } from '../modules/yourModule.js';</code></pre>
|
|
299
|
+
|
|
275
300
|
<blockquote>⚠️ Files inside <code>_tools/</code> run directly in Node.js without a bundler — use CommonJS (<code>require</code> / <code>module.exports</code>) there, not ESM.</blockquote>
|
|
276
301
|
</div>
|
|
277
302
|
</div>
|
|
303
|
+
</div>
|
|
278
304
|
|
|
279
305
|
<div id="content-creating-pages" class="tab-content">
|
|
280
306
|
<div class="markdown-body">
|
|
@@ -375,39 +401,45 @@ export function yourFunction() {
|
|
|
375
401
|
<div class="markdown-body">
|
|
376
402
|
<h2>Head & SEO</h2>
|
|
377
403
|
<p>This json holds global settings used across all pages in <code>base.njk</code> and other components:</p>
|
|
404
|
+
|
|
378
405
|
<h3>site.json <small>(<code>src/frontend/data/</code>)</small></h3>
|
|
379
|
-
<pre><code>
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
406
|
+
<pre><code>"site_name": "Site name",
|
|
407
|
+
"title": "Site title",
|
|
408
|
+
"description": "Site description",
|
|
409
|
+
"keywords": "keyword1, keyword2, keyword3",
|
|
410
|
+
"domain": "yoursite.com",
|
|
411
|
+
"url": "https://yoursite.com",
|
|
412
|
+
"lang": "en",
|
|
413
|
+
"author": "Name and surname",
|
|
414
|
+
"data_bs_theme": "dark",
|
|
415
|
+
"favicon": "/assets/brand/favicon.svg",
|
|
416
|
+
"logo": "/assets/brand/logo.svg",
|
|
417
|
+
...</code></pre>
|
|
418
|
+
|
|
392
419
|
<h2>Per-page SEO and CDN</h2>
|
|
393
|
-
<p>Each page entry is keyed by its camelCase <code>title</code> from the front matter
|
|
420
|
+
<p>Each page entry is keyed by its camelCase <code>title</code> from the front matter:</p>
|
|
394
421
|
<p>If you don't want to use a particular CDN inserting it in <code>base.njk</code> for all pages, you can add extra specific CDN (CSS, JS) by inserting the link in each page of <code>site.json</code> separating them with a <code>,</code> and setting them in <code>""</code>.</p>
|
|
422
|
+
|
|
395
423
|
<h3>site.json <small>(<code>src/frontend/data/</code>)</small></h3>
|
|
396
424
|
<pre><code>"pages": {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
425
|
+
...
|
|
426
|
+
"examplePage": {
|
|
427
|
+
"seo": {
|
|
428
|
+
"title": "Example Page",
|
|
429
|
+
"description": "description"
|
|
430
|
+
},
|
|
431
|
+
"cdn": {
|
|
432
|
+
// You can leave the [] empty
|
|
433
|
+
"css": ["https://example1.com/lib.min.css", "https://example2.com/lib.min.css"],
|
|
434
|
+
"js": ["https://example1.com/lib.min.js", "https://example2.com/lib.min.js"]
|
|
407
435
|
}
|
|
436
|
+
}
|
|
437
|
+
...
|
|
408
438
|
}</code></pre>
|
|
439
|
+
|
|
409
440
|
<h2>AI & SEO bots</h2>
|
|
410
441
|
<p><code>llms.txt</code> and <code>robots.txt</code> are generated automatically from <code>site.json</code> via their respective <code>.njk</code> files — no manual editing needed.</p>
|
|
442
|
+
|
|
411
443
|
<table>
|
|
412
444
|
<thead>
|
|
413
445
|
<tr><th>File</th><th>Purpose</th><th>Reachable at</th></tr>
|
|
@@ -417,7 +449,30 @@ export function yourFunction() {
|
|
|
417
449
|
<tr><td><code>robots.njk</code></td><td>Controls search engine crawling</td><td><code>yoursite.com/robots.txt</code></td></tr>
|
|
418
450
|
</tbody>
|
|
419
451
|
</table>
|
|
452
|
+
|
|
420
453
|
<p>To customize them, edit <code>src/llms.njk</code> or <code>src/robots.njk</code> directly.</p>
|
|
454
|
+
|
|
455
|
+
<h3>Customizing llms.txt</h3>
|
|
456
|
+
<p><code>src/llms.njk</code> ships with a base template — <strong>replace the placeholders with your own content</strong>:</p>
|
|
457
|
+
|
|
458
|
+
<pre><code>{% raw %}# {{ site.site_name }}
|
|
459
|
+
|
|
460
|
+
> {{ site.description }}
|
|
461
|
+
|
|
462
|
+
Built by {{ site.author }} — {{ site.url }}
|
|
463
|
+
|
|
464
|
+
## Pages
|
|
465
|
+
|
|
466
|
+
- {{ site.url }}: Homepage
|
|
467
|
+
|
|
468
|
+
## Notes
|
|
469
|
+
|
|
470
|
+
- Language: {{ site.lang }}
|
|
471
|
+
- All content may be used for AI indexing unless otherwise stated
|
|
472
|
+
{% endraw %}</code></pre>
|
|
473
|
+
|
|
474
|
+
<blockquote>The more accurate and detailed your <code>llms.txt</code>, the better AI models will understand and reference your site.</blockquote>
|
|
475
|
+
|
|
421
476
|
<h2>Configuration field description</h2>
|
|
422
477
|
<table>
|
|
423
478
|
<thead>
|
|
@@ -441,52 +496,72 @@ export function yourFunction() {
|
|
|
441
496
|
|
|
442
497
|
<div id="content-backend" class="tab-content">
|
|
443
498
|
<div class="markdown-body">
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
│
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
│ ├──
|
|
454
|
-
│
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
499
|
+
<h2>Backend</h2>
|
|
500
|
+
<p>The backend is a PHP REST API located in <code>src/backend/</code>, copied to the output directory automatically at build time.</p>
|
|
501
|
+
|
|
502
|
+
<h3>Structure</h3>
|
|
503
|
+
<pre><code>src/backend/
|
|
504
|
+
├── api/
|
|
505
|
+
│ ├── public/ # Endpoints accessible without an API key
|
|
506
|
+
│ └── protected/ # Endpoints requiring X-Api-Key header
|
|
507
|
+
├── database/
|
|
508
|
+
│ ├── Database.php
|
|
509
|
+
│ ├── models/
|
|
510
|
+
│ └── migrations/
|
|
511
|
+
├── config.php # Your local config — never commit this
|
|
512
|
+
└── config.example.php</code></pre>
|
|
513
|
+
|
|
514
|
+
<h3>Configuration</h3>
|
|
515
|
+
<p><code>config.php</code> works like a <code>.env</code> file — it holds secrets and environment settings that stay local and out of version control.</p>
|
|
516
|
+
<p>Copy <code>config.example.php</code> to <code>config.php</code> and fill in your values:</p>
|
|
517
|
+
|
|
518
|
+
<h4>config.php <small>(<code>src/backend/</code>)</small></h4>
|
|
519
|
+
<pre><code class="language-php">return [
|
|
520
|
+
// Default key for protected endpoints that don't have a specific key in ENDPOINT_KEYS
|
|
521
|
+
'API_KEY' => 'default-key',
|
|
522
|
+
|
|
523
|
+
// If you want restrict access to protected endpoints to specific clients, you can define custom keys for each endpoint
|
|
524
|
+
// For subfolder endpoints, use the relative path ('subfolder/endpoint')
|
|
525
|
+
'ENDPOINT_KEYS' => [
|
|
526
|
+
'example-protected' => 'custom-key',
|
|
527
|
+
],
|
|
528
|
+
|
|
529
|
+
'DB_HOST' => '127.0.0.1',
|
|
530
|
+
'DB_NAME' => 'example_db',
|
|
531
|
+
'DB_USER' => 'root',
|
|
532
|
+
'DB_PASS' => '',
|
|
473
533
|
];</code></pre>
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
<
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
534
|
+
|
|
535
|
+
<p><code>API_KEY</code> is the fallback key for all protected endpoints. Use <code>ENDPOINT_KEYS</code> to assign a different key to a specific endpoint — for subfolder endpoints, use the relative path as the key.</p>
|
|
536
|
+
|
|
537
|
+
<h3>How routing works</h3>
|
|
538
|
+
<p>The file path inside <code>api/</code> maps directly to the URL. Extra URL segments become route parameters available as <code>$requestParams[]</code>.</p>
|
|
539
|
+
<p>Every endpoint file has access to:</p>
|
|
540
|
+
|
|
541
|
+
<table>
|
|
542
|
+
<thead>
|
|
543
|
+
<tr>
|
|
544
|
+
<th>Variable</th>
|
|
545
|
+
<th>Description</th>
|
|
546
|
+
</tr>
|
|
547
|
+
</thead>
|
|
548
|
+
<tbody>
|
|
549
|
+
<tr>
|
|
550
|
+
<td><code>$method</code></td>
|
|
551
|
+
<td>HTTP method (<code>GET</code>, <code>POST</code>, <code>PUT</code>, <code>PATCH</code>, <code>DELETE</code>)</td>
|
|
552
|
+
</tr>
|
|
553
|
+
<tr>
|
|
554
|
+
<td><code>$requestParams</code></td>
|
|
555
|
+
<td>Extra URL segments (e.g. <code>/api/posts/42</code> → <code>['42']</code>)</td>
|
|
556
|
+
</tr>
|
|
557
|
+
</tbody>
|
|
558
|
+
</table>
|
|
559
|
+
|
|
560
|
+
<h3>Creating a public endpoint</h3>
|
|
561
|
+
<p>Create a <code>.php</code> file anywhere inside <code>api/public/</code></p>
|
|
562
|
+
|
|
563
|
+
<h4>api/public/example.php</h4>
|
|
564
|
+
<pre><code class="language-php"><?php
|
|
490
565
|
declare(strict_types=1);
|
|
491
566
|
|
|
492
567
|
require_once CORE_PATH . '/modules/Response.php';
|
|
@@ -497,11 +572,13 @@ if ($method !== 'GET') {
|
|
|
497
572
|
|
|
498
573
|
$id = isset($requestParams[0]) ? (int)$requestParams[0] : null;
|
|
499
574
|
|
|
500
|
-
Response::success(['id'
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
575
|
+
Response::success(['id' => $id]);</code></pre>
|
|
576
|
+
|
|
577
|
+
<h3>Creating a protected endpoint</h3>
|
|
578
|
+
<p>Create a <code>.php</code> file inside <code>api/protected/</code>. The API key check happens automatically before your file runs.</p>
|
|
579
|
+
|
|
580
|
+
<h4>api/protected/example.php</h4>
|
|
581
|
+
<pre><code class="language-php"><?php
|
|
505
582
|
declare(strict_types=1);
|
|
506
583
|
|
|
507
584
|
require_once CORE_PATH . '/modules/Response.php';
|
|
@@ -510,107 +587,60 @@ if ($method !== 'GET') {
|
|
|
510
587
|
Response::error('Method not allowed', 405);
|
|
511
588
|
}
|
|
512
589
|
|
|
513
|
-
Response::success(['visits'
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
590
|
+
Response::success(['visits' => 1024]);</code></pre>
|
|
591
|
+
|
|
592
|
+
<p>To assign a dedicated key, add it to <code>config.php</code>:</p>
|
|
593
|
+
<pre><code class="language-php">'ENDPOINT_KEYS' => [
|
|
594
|
+
'endpoint' => 'custom-key',
|
|
517
595
|
],</code></pre>
|
|
518
|
-
|
|
519
|
-
|
|
596
|
+
|
|
597
|
+
<h3>The Response helper</h3>
|
|
598
|
+
<pre><code class="language-php">Response::success($data, $code); // default 200
|
|
520
599
|
Response::error($message, $code, $details); // default 400
|
|
521
600
|
Response::noContent(); // 204</code></pre>
|
|
522
|
-
<h3>Handling multiple methods</h3>
|
|
523
|
-
<pre><code>$id = isset($requestParams[0]) ? (int)$requestParams[0] : null;
|
|
524
|
-
$input = json_decode(file_get_contents('php://input'), true) ?? [];
|
|
525
|
-
|
|
526
|
-
switch ($method) {
|
|
527
|
-
case 'GET':
|
|
528
|
-
Response::success(['id' => $id]);
|
|
529
|
-
break;
|
|
530
|
-
|
|
531
|
-
case 'POST':
|
|
532
|
-
if (empty($input['title'])) Response::error('Missing title', 400);
|
|
533
|
-
Response::success(['message' => 'Created'], 201);
|
|
534
|
-
break;
|
|
535
|
-
|
|
536
|
-
case 'DELETE':
|
|
537
|
-
if (!$id) Response::error('ID required', 400);
|
|
538
|
-
Response::success(['message' => 'Deleted']);
|
|
539
|
-
break;
|
|
540
|
-
|
|
541
|
-
default:
|
|
542
|
-
Response::error('Method not allowed', 405);
|
|
543
|
-
}</code></pre>
|
|
544
|
-
<h3>Using the database</h3>
|
|
545
|
-
<h4>database/models/Post.php</h4>
|
|
546
|
-
<pre><code><?php
|
|
547
|
-
declare(strict_types=1);
|
|
548
|
-
|
|
549
|
-
require_once __DIR__ . '/../Database.php';
|
|
550
|
-
|
|
551
|
-
class Post {
|
|
552
|
-
private PDO $db;
|
|
553
|
-
|
|
554
|
-
public function __construct() {
|
|
555
|
-
$this->db = Database::getInstance();
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
public function getAll(): array {
|
|
559
|
-
return $this->db->query("SELECT * FROM posts")->fetchAll();
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
public function getById(int $id): ?array {
|
|
563
|
-
$stmt = $this->db->prepare("SELECT * FROM posts WHERE id = :id");
|
|
564
|
-
$stmt->execute(['id' => $id]);
|
|
565
|
-
return $stmt->fetch() ?: null;
|
|
566
|
-
}
|
|
567
601
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
const res = await fetch('/api/posts', {
|
|
591
|
-
method: 'POST',
|
|
592
|
-
headers: { 'Content-Type': 'application/json', 'X-Api-Key': 'your-key' },
|
|
593
|
-
body: JSON.stringify({ title: 'Hello world' })
|
|
594
|
-
});</code></pre>
|
|
595
|
-
<h3>Pre-built endpoints</h3>
|
|
596
|
-
<table>
|
|
597
|
-
<thead>
|
|
598
|
-
<tr><th>Route</th><th>Auth</th><th>Methods</th><th>Description</th></tr>
|
|
599
|
-
</thead>
|
|
600
|
-
<tbody>
|
|
601
|
-
<tr><td><code>/api/example-public</code></td><td>No</td><td><code>GET</code></td><td>Smoke test for public routing</td></tr>
|
|
602
|
-
<tr><td><code>/api/subfolder/example-protected</code></td><td>Yes</td><td><code>GET</code></td><td>Smoke test for protected routing</td></tr>
|
|
603
|
-
<tr><td><code>/api/auth/register</code></td><td>No</td><td><code>POST</code></td><td>Register a new user</td></tr>
|
|
604
|
-
<tr><td><code>/api/auth/login</code></td><td>No</td><td><code>POST</code></td><td>Login and retrieve user data</td></tr>
|
|
605
|
-
<tr><td><code>/api/auth-system</code></td><td>Yes</td><td><code>GET POST PUT PATCH DELETE</code></td><td>Full CRUD on users</td></tr>
|
|
606
|
-
</tbody>
|
|
607
|
-
</table>
|
|
602
|
+
<h3>Pre-built endpoints</h3>
|
|
603
|
+
<table>
|
|
604
|
+
<thead>
|
|
605
|
+
<tr>
|
|
606
|
+
<th>Route</th>
|
|
607
|
+
<th>Method</th>
|
|
608
|
+
<th>Description</th>
|
|
609
|
+
</tr>
|
|
610
|
+
</thead>
|
|
611
|
+
<tbody>
|
|
612
|
+
<tr>
|
|
613
|
+
<td><code>/api/example-public</code></td>
|
|
614
|
+
<td><code>GET</code></td>
|
|
615
|
+
<td>Example endpoint that doesn't require any key</td>
|
|
616
|
+
</tr>
|
|
617
|
+
<tr>
|
|
618
|
+
<td><code>/api/example-protected</code></td>
|
|
619
|
+
<td><code>GET</code></td>
|
|
620
|
+
<td>Example endpoint that requires X-API-KEY</td>
|
|
621
|
+
</tr>
|
|
622
|
+
</tbody>
|
|
623
|
+
</table>
|
|
608
624
|
</div>
|
|
609
625
|
</div>
|
|
610
626
|
</div>
|
|
611
627
|
</div>
|
|
612
628
|
|
|
613
629
|
<style>
|
|
630
|
+
/* Custom Replacement Classes for Bootstrap */
|
|
631
|
+
.layout-container {
|
|
632
|
+
width: 100%;
|
|
633
|
+
max-width: 1140px;
|
|
634
|
+
margin-right: auto;
|
|
635
|
+
margin-left: auto;
|
|
636
|
+
padding-right: 15px;
|
|
637
|
+
padding-left: 15px;
|
|
638
|
+
}
|
|
639
|
+
.layout-my-3 {
|
|
640
|
+
margin-top: 1rem;
|
|
641
|
+
margin-bottom: 1rem;
|
|
642
|
+
}
|
|
643
|
+
|
|
614
644
|
h1 {
|
|
615
645
|
text-align: center;
|
|
616
646
|
font-weight: 400;
|
|
@@ -659,7 +689,8 @@ const res = await fetch('/api/posts', {
|
|
|
659
689
|
transform: translateY(-2px);
|
|
660
690
|
}
|
|
661
691
|
.card-icon {
|
|
662
|
-
|
|
692
|
+
width: 2rem;
|
|
693
|
+
height: 2rem;
|
|
663
694
|
color: #42b883;
|
|
664
695
|
}
|
|
665
696
|
.card h3 {
|
|
@@ -824,6 +855,7 @@ const res = await fetch('/api/posts', {
|
|
|
824
855
|
}
|
|
825
856
|
});
|
|
826
857
|
});
|
|
858
|
+
|
|
827
859
|
document.querySelector('.nav-to-assistant').addEventListener('click', (e) => {
|
|
828
860
|
e.preventDefault();
|
|
829
861
|
|
|
@@ -834,7 +866,8 @@ const res = await fetch('/api/posts', {
|
|
|
834
866
|
assistantRadio.dispatchEvent(new Event('change'));
|
|
835
867
|
}
|
|
836
868
|
});
|
|
837
|
-
|
|
869
|
+
|
|
870
|
+
document.querySelector('.nav-to-head-seo').addEventListener('click', (e) => {
|
|
838
871
|
e.preventDefault();
|
|
839
872
|
|
|
840
873
|
const headAndSeoRadio = document.querySelector('input[name="guide-filter"][value="head-seo"]');
|
|
@@ -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);
|
|
@@ -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/src/frontend/llms.njk
CHANGED
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
// Getting root rules from _root.scss
|
|
6
6
|
@use 'root' as root;
|
|
7
7
|
|
|
8
|
+
// Frameworks imports
|
|
8
9
|
@import "../modules/frameworks/bootstrap";
|
|
9
10
|
// @import "../modules/frameworks/bulma";
|
|
10
11
|
// @import "../modules/frameworks/foundation";
|
|
11
12
|
// @import "../modules/frameworks/uikit";
|
|
12
13
|
|
|
13
14
|
|
|
15
|
+
// Modules imports
|
|
14
16
|
@import "typography";
|
|
15
17
|
|
|
16
18
|
@import "header";
|
|
@@ -21,6 +23,10 @@
|
|
|
21
23
|
|
|
22
24
|
@import "buttons";
|
|
23
25
|
|
|
26
|
+
//==========================
|
|
27
|
+
// Global css rules for the entire site
|
|
28
|
+
//==========================
|
|
29
|
+
|
|
24
30
|
*,
|
|
25
31
|
*::before,
|
|
26
32
|
*::after {
|