chadstart 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +10 -0
- package/.env.example +46 -0
- package/.github/workflows/browser-test.yml +34 -0
- package/.github/workflows/docker-publish.yml +54 -0
- package/.github/workflows/docs.yml +31 -0
- package/.github/workflows/npm-chadstart.yml +27 -0
- package/.github/workflows/npm-sdk.yml +38 -0
- package/.github/workflows/test.yml +85 -0
- package/.weblate +9 -0
- package/Dockerfile +23 -0
- package/README.md +348 -0
- package/admin/index.html +2802 -0
- package/admin/login.html +207 -0
- package/chadstart.example.yml +416 -0
- package/chadstart.schema.json +367 -0
- package/chadstart.yaml +53 -0
- package/cli/cli.js +295 -0
- package/core/api-generator.js +606 -0
- package/core/auth.js +298 -0
- package/core/db.js +384 -0
- package/core/entity-engine.js +166 -0
- package/core/error-reporter.js +132 -0
- package/core/file-storage.js +97 -0
- package/core/functions-engine.js +353 -0
- package/core/openapi.js +171 -0
- package/core/plugin-loader.js +92 -0
- package/core/realtime.js +93 -0
- package/core/schema-validator.js +50 -0
- package/core/seeder.js +231 -0
- package/core/telemetry.js +119 -0
- package/core/upload.js +372 -0
- package/core/workers/php_worker.php +19 -0
- package/core/workers/python_worker.py +33 -0
- package/core/workers/ruby_worker.rb +21 -0
- package/core/yaml-loader.js +64 -0
- package/demo/chadstart.yaml +178 -0
- package/demo/docker-compose.yml +31 -0
- package/demo/functions/greet.go +39 -0
- package/demo/functions/hello.cpp +18 -0
- package/demo/functions/hello.py +13 -0
- package/demo/functions/hello.rb +10 -0
- package/demo/functions/onTodoCreated.js +13 -0
- package/demo/functions/ping.sh +13 -0
- package/demo/functions/stats.js +22 -0
- package/demo/public/index.html +522 -0
- package/docker-compose.yml +17 -0
- package/docs/access-policies.md +155 -0
- package/docs/admin-ui.md +29 -0
- package/docs/angular.md +69 -0
- package/docs/astro.md +71 -0
- package/docs/auth.md +160 -0
- package/docs/cli.md +56 -0
- package/docs/config.md +127 -0
- package/docs/crud.md +627 -0
- package/docs/deploy.md +113 -0
- package/docs/docker.md +59 -0
- package/docs/entities.md +385 -0
- package/docs/functions.md +196 -0
- package/docs/getting-started.md +79 -0
- package/docs/groups.md +85 -0
- package/docs/index.md +5 -0
- package/docs/llm-rules.md +81 -0
- package/docs/middlewares.md +78 -0
- package/docs/overrides/home.html +350 -0
- package/docs/plugins.md +59 -0
- package/docs/react.md +75 -0
- package/docs/realtime.md +43 -0
- package/docs/s3-storage.md +40 -0
- package/docs/security.md +23 -0
- package/docs/stylesheets/extra.css +375 -0
- package/docs/svelte.md +71 -0
- package/docs/telemetry.md +97 -0
- package/docs/upload.md +168 -0
- package/docs/validation.md +115 -0
- package/docs/vue.md +86 -0
- package/docs/webhooks.md +87 -0
- package/index.js +11 -0
- package/locales/en/admin.json +169 -0
- package/mkdocs.yml +82 -0
- package/package.json +65 -0
- package/playwright.config.js +24 -0
- package/public/.gitkeep +0 -0
- package/sdk/README.md +284 -0
- package/sdk/package.json +39 -0
- package/sdk/scripts/build.js +58 -0
- package/sdk/src/index.js +368 -0
- package/sdk/test/sdk.test.cjs +340 -0
- package/sdk/types/index.d.ts +217 -0
- package/server/express-server.js +734 -0
- package/test/access-policies.test.js +96 -0
- package/test/ai.test.js +81 -0
- package/test/api-keys.test.js +361 -0
- package/test/auth.test.js +122 -0
- package/test/browser/admin-ui.spec.js +127 -0
- package/test/browser/global-setup.js +71 -0
- package/test/browser/global-teardown.js +11 -0
- package/test/db.test.js +227 -0
- package/test/entity-engine.test.js +193 -0
- package/test/error-reporter.test.js +140 -0
- package/test/functions-engine.test.js +240 -0
- package/test/groups.test.js +212 -0
- package/test/hot-reload.test.js +153 -0
- package/test/i18n.test.js +173 -0
- package/test/middleware.test.js +76 -0
- package/test/openapi.test.js +67 -0
- package/test/schema-validator.test.js +83 -0
- package/test/sdk.test.js +90 -0
- package/test/seeder.test.js +279 -0
- package/test/settings.test.js +109 -0
- package/test/telemetry.test.js +254 -0
- package/test/test.js +17 -0
- package/test/upload.test.js +265 -0
- package/test/validation.test.js +96 -0
- package/test/yaml-loader.test.js +93 -0
- package/utils/logger.js +24 -0
package/admin/login.html
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" id="html-root">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Sign in — ChadStart Admin</title>
|
|
7
|
+
<script src="/admin/vendor/tailwind.js"></script>
|
|
8
|
+
<style type="text/tailwindcss">
|
|
9
|
+
@theme {
|
|
10
|
+
--color-cs-bg: #121212;
|
|
11
|
+
--color-cs-surface: #1e1e1e;
|
|
12
|
+
--color-cs-border: #2a2a2a;
|
|
13
|
+
--color-cs-primary: #34b1eb;
|
|
14
|
+
--color-cs-accent: #03dac6;
|
|
15
|
+
--color-cs-text: #e1e1e1;
|
|
16
|
+
--color-cs-muted: #888888;
|
|
17
|
+
}
|
|
18
|
+
</style>
|
|
19
|
+
<style>
|
|
20
|
+
/* ── Animated glowing border button ────────────────────────────── */
|
|
21
|
+
@property --glow-a { syntax: '<angle>'; inherits: false; initial-value: 0deg; }
|
|
22
|
+
@keyframes spin-glow { to { --glow-a: 360deg; } }
|
|
23
|
+
.btn-glow {
|
|
24
|
+
border: 2px solid transparent;
|
|
25
|
+
background:
|
|
26
|
+
linear-gradient(#1e1e1e, #1e1e1e) padding-box,
|
|
27
|
+
conic-gradient(from var(--glow-a), #34b1eb 0%, #0e7fc4 40%, transparent 60%, #34b1eb 100%) border-box;
|
|
28
|
+
animation: spin-glow 2s linear infinite;
|
|
29
|
+
color: #34b1eb; font-weight: 500;
|
|
30
|
+
transition: opacity 150ms ease;
|
|
31
|
+
}
|
|
32
|
+
.btn-glow:hover { opacity: 0.8; }
|
|
33
|
+
.btn-glow:disabled { opacity: 0.4; animation: none; }
|
|
34
|
+
</style>
|
|
35
|
+
</head>
|
|
36
|
+
<body class="bg-cs-bg text-cs-text min-h-screen flex items-center justify-center p-4"
|
|
37
|
+
style="font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;">
|
|
38
|
+
|
|
39
|
+
<div class="bg-cs-surface border border-cs-border rounded p-8 w-full max-w-sm" style="box-shadow: 0 2px 16px rgba(52,177,235,.1);">
|
|
40
|
+
<div id="login-project-name" class="font-bold text-cs-text text-xl mb-0.5">Admin</div>
|
|
41
|
+
<div class="text-[11px] text-cs-muted mb-6">Powered by ChadStart.com</div>
|
|
42
|
+
|
|
43
|
+
<div id="login-error" class="text-red-400 text-sm mb-4 hidden"></div>
|
|
44
|
+
|
|
45
|
+
<div id="login-collection-section" class="hidden mb-4">
|
|
46
|
+
<label class="block text-sm text-cs-muted mb-1">Collection</label>
|
|
47
|
+
<input type="text" id="login-collection"
|
|
48
|
+
class="w-full bg-cs-bg border border-cs-border rounded text-cs-text px-3 py-2 text-sm outline-none focus:border-cs-primary"
|
|
49
|
+
style="transition: border-color 150ms ease;"
|
|
50
|
+
placeholder="Admin collection name" autocomplete="off" />
|
|
51
|
+
</div>
|
|
52
|
+
<div class="mb-4 text-right">
|
|
53
|
+
<button type="button" id="toggle-collection-btn" onclick="toggleCollectionField()"
|
|
54
|
+
class="text-xs text-cs-muted hover:text-cs-primary cursor-pointer"
|
|
55
|
+
style="transition: color 150ms ease; background: none; border: none; padding: 0;">
|
|
56
|
+
Change collection
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<label class="block text-sm text-cs-muted mb-1">Email</label>
|
|
61
|
+
<input type="email" id="login-email" placeholder="admin@example.com" autocomplete="email"
|
|
62
|
+
class="w-full bg-cs-bg border border-cs-border rounded text-cs-text px-3 py-2 text-sm mb-4 outline-none focus:border-cs-primary"
|
|
63
|
+
style="transition: border-color 150ms ease;" />
|
|
64
|
+
|
|
65
|
+
<label class="block text-sm text-cs-muted mb-1">Password</label>
|
|
66
|
+
<input type="password" id="login-password" placeholder="••••••••" autocomplete="current-password"
|
|
67
|
+
class="w-full bg-cs-bg border border-cs-border rounded text-cs-text px-3 py-2 text-sm mb-5 outline-none focus:border-cs-primary"
|
|
68
|
+
style="transition: border-color 150ms ease;" />
|
|
69
|
+
|
|
70
|
+
<button id="login-btn" onclick="doLogin()" class="btn-glow w-full py-2 px-4 rounded text-sm cursor-pointer">
|
|
71
|
+
<span>Sign in</span>
|
|
72
|
+
</button>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<script>
|
|
76
|
+
// ── State ──────────────────────────────────────────────────────────────
|
|
77
|
+
let schema = null;
|
|
78
|
+
|
|
79
|
+
function toSlug(name) {
|
|
80
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function escHtml(s) {
|
|
84
|
+
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Bootstrap ──────────────────────────────────────────────────────────
|
|
88
|
+
async function init() {
|
|
89
|
+
// If already authenticated, redirect to admin
|
|
90
|
+
const token = localStorage.getItem('cs_token');
|
|
91
|
+
if (token) {
|
|
92
|
+
// Verify token is still valid before redirect
|
|
93
|
+
try {
|
|
94
|
+
const verifySchema = await fetch('/admin/schema').then(r => r.json());
|
|
95
|
+
const cols = getAdminCollections(verifySchema);
|
|
96
|
+
if (cols.length) {
|
|
97
|
+
const r = await fetch(`/api/auth/${toSlug(cols[0].name)}/me`, {
|
|
98
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
99
|
+
});
|
|
100
|
+
if (r.ok) {
|
|
101
|
+
window.location.replace('/admin');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch { /* fall through to show login */ }
|
|
106
|
+
localStorage.removeItem('cs_token');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Load schema for collection selection
|
|
110
|
+
try {
|
|
111
|
+
schema = await fetch('/admin/schema').then(r => r.json());
|
|
112
|
+
if (schema.name) {
|
|
113
|
+
document.getElementById('login-project-name').textContent = schema.name;
|
|
114
|
+
document.title = `Sign in — ${escHtml(schema.name)}`;
|
|
115
|
+
}
|
|
116
|
+
populateLoginCollections();
|
|
117
|
+
} catch { /* ignore */ }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getAdminCollections(s) {
|
|
121
|
+
const src = s || schema;
|
|
122
|
+
return (src && src.userCollections) || [];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function populateLoginCollections() {
|
|
126
|
+
const cols = getAdminCollections();
|
|
127
|
+
const input = document.getElementById('login-collection');
|
|
128
|
+
const errEl = document.getElementById('login-error');
|
|
129
|
+
const btn = document.getElementById('login-btn');
|
|
130
|
+
if (cols.length) {
|
|
131
|
+
input.value = cols[0].name;
|
|
132
|
+
} else {
|
|
133
|
+
input.value = '';
|
|
134
|
+
errEl.textContent = 'No admin collections found. Please configure an authenticable entity.';
|
|
135
|
+
errEl.classList.remove('hidden');
|
|
136
|
+
btn.disabled = true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function toggleCollectionField() {
|
|
141
|
+
const section = document.getElementById('login-collection-section');
|
|
142
|
+
const btn = document.getElementById('toggle-collection-btn');
|
|
143
|
+
const isHidden = section.classList.contains('hidden');
|
|
144
|
+
section.classList.toggle('hidden', !isHidden);
|
|
145
|
+
btn.textContent = isHidden ? 'Hide collection' : 'Change collection';
|
|
146
|
+
if (isHidden) document.getElementById('login-collection').focus();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function doLogin() {
|
|
150
|
+
const collectionName = document.getElementById('login-collection').value.trim().replace(/[^a-zA-Z0-9 _-]/g, '');
|
|
151
|
+
const email = document.getElementById('login-email').value.trim();
|
|
152
|
+
const password = document.getElementById('login-password').value;
|
|
153
|
+
const errEl = document.getElementById('login-error');
|
|
154
|
+
const btn = document.getElementById('login-btn');
|
|
155
|
+
const btnText = btn.querySelector('span');
|
|
156
|
+
|
|
157
|
+
errEl.classList.add('hidden');
|
|
158
|
+
if (!collectionName || !email || !password) {
|
|
159
|
+
errEl.textContent = 'All fields are required';
|
|
160
|
+
errEl.classList.remove('hidden');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
btn.disabled = true;
|
|
165
|
+
btnText.textContent = 'Signing in…';
|
|
166
|
+
|
|
167
|
+
const cols = getAdminCollections();
|
|
168
|
+
const col = cols.find(c => c.name === collectionName);
|
|
169
|
+
if (!col) {
|
|
170
|
+
showLoginError('Collection not found');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const r = await fetch(`/api/auth/${toSlug(col.name)}/login`, {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
headers: { 'Content-Type': 'application/json' },
|
|
178
|
+
body: JSON.stringify({ email, password }),
|
|
179
|
+
});
|
|
180
|
+
const data = await r.json();
|
|
181
|
+
if (!r.ok) {
|
|
182
|
+
showLoginError(data.error || 'Login failed');
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
localStorage.setItem('cs_token', data.token);
|
|
186
|
+
localStorage.setItem('cs_collection', col.name);
|
|
187
|
+
window.location.replace('/admin');
|
|
188
|
+
} catch {
|
|
189
|
+
showLoginError('Network error');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function showLoginError(msg) {
|
|
193
|
+
errEl.textContent = msg;
|
|
194
|
+
errEl.classList.remove('hidden');
|
|
195
|
+
btn.disabled = false;
|
|
196
|
+
btnText.textContent = 'Sign in';
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
document.addEventListener('keydown', e => {
|
|
201
|
+
if (e.key === 'Enter') doLogin();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
init();
|
|
205
|
+
</script>
|
|
206
|
+
</body>
|
|
207
|
+
</html>
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# chadstart.example.yml
|
|
2
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
3
|
+
# Reference file that lists every available option in a ChadStart YAML config.
|
|
4
|
+
# Copy this file to chadstart.yaml and remove / adjust the sections you need.
|
|
5
|
+
# Secrets (JWT_SECRET, DB passwords, SENTRY_DSN, OTLP auth headers …) must
|
|
6
|
+
# always be stored in .env — never put them in this file.
|
|
7
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
# ── App identity ──────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
# (required) Human-readable name of your application.
|
|
12
|
+
name: My App
|
|
13
|
+
|
|
14
|
+
# TCP port the HTTP server listens on. Overridable by PORT env var.
|
|
15
|
+
# Default: 3000
|
|
16
|
+
port: 3000
|
|
17
|
+
|
|
18
|
+
# Path to the SQLite database file (relative to this config file).
|
|
19
|
+
# Overridable by DB_PATH env var.
|
|
20
|
+
# Default: data/chadstart.db
|
|
21
|
+
database: data/chadstart.db
|
|
22
|
+
|
|
23
|
+
# ── Entities ──────────────────────────────────────────────────────────────────
|
|
24
|
+
# Define every data model here. ChadStart auto-generates REST + SDK CRUD for
|
|
25
|
+
# each entity. Every entity gets id (UUID), createdAt, and updatedAt for free.
|
|
26
|
+
|
|
27
|
+
entities:
|
|
28
|
+
|
|
29
|
+
# ── Authenticable (user-like) entity ────────────────────────────────────────
|
|
30
|
+
User:
|
|
31
|
+
# Makes this entity a user: adds email + password fields and enables
|
|
32
|
+
# login / signup / token-refresh endpoints.
|
|
33
|
+
authenticable: true
|
|
34
|
+
|
|
35
|
+
# Identifier property shown in the admin panel (defaults to first string field).
|
|
36
|
+
mainProp: name
|
|
37
|
+
|
|
38
|
+
# Overrides for the auto-generated singular / plural display names.
|
|
39
|
+
nameSingular: user
|
|
40
|
+
namePlural: users
|
|
41
|
+
|
|
42
|
+
# Number of records created when running `npm run seed`. Default: 50
|
|
43
|
+
seedCount: 10
|
|
44
|
+
|
|
45
|
+
# Custom kebab-case slug used in API paths (default: pluralised entity name).
|
|
46
|
+
slug: users
|
|
47
|
+
|
|
48
|
+
properties:
|
|
49
|
+
# Short syntax — type defaults to "string".
|
|
50
|
+
- name
|
|
51
|
+
|
|
52
|
+
# Long syntax — all available property options shown below.
|
|
53
|
+
- name: bio
|
|
54
|
+
type: text # string | text | richText | number | link | money |
|
|
55
|
+
# date | timestamp | email | boolean | file | image |
|
|
56
|
+
# password | choice | location | group | json |
|
|
57
|
+
# integer | float | real
|
|
58
|
+
hidden: false # Exclude this field from API responses. Default: false
|
|
59
|
+
helpText: A short bio # Hint displayed in the admin UI
|
|
60
|
+
default: '' # Default value when property is omitted on create
|
|
61
|
+
|
|
62
|
+
- name: email
|
|
63
|
+
type: email
|
|
64
|
+
validation:
|
|
65
|
+
required: true
|
|
66
|
+
isEmail: true
|
|
67
|
+
|
|
68
|
+
- name: age
|
|
69
|
+
type: integer
|
|
70
|
+
validation:
|
|
71
|
+
min: 0
|
|
72
|
+
max: 120
|
|
73
|
+
|
|
74
|
+
- name: website
|
|
75
|
+
type: link
|
|
76
|
+
|
|
77
|
+
- name: balance
|
|
78
|
+
type: money
|
|
79
|
+
options:
|
|
80
|
+
currency: USD # ISO 4217 currency code. Default: USD
|
|
81
|
+
|
|
82
|
+
- name: avatar
|
|
83
|
+
type: image
|
|
84
|
+
options:
|
|
85
|
+
sizes:
|
|
86
|
+
thumbnail:
|
|
87
|
+
width: 80
|
|
88
|
+
height: 80
|
|
89
|
+
fit: cover # cover | contain | fill | inside | outside (Sharp)
|
|
90
|
+
medium:
|
|
91
|
+
width: 400
|
|
92
|
+
|
|
93
|
+
- name: resume
|
|
94
|
+
type: file
|
|
95
|
+
|
|
96
|
+
- name: password
|
|
97
|
+
type: password # Value is automatically hashed before storage
|
|
98
|
+
|
|
99
|
+
- name: role
|
|
100
|
+
type: choice
|
|
101
|
+
options:
|
|
102
|
+
values: [admin, editor, viewer]
|
|
103
|
+
sequential: false # true = values follow a logical progression
|
|
104
|
+
|
|
105
|
+
- name: coordinates
|
|
106
|
+
type: location # Stored as { lat, lng }
|
|
107
|
+
|
|
108
|
+
- name: metadata
|
|
109
|
+
type: json
|
|
110
|
+
|
|
111
|
+
- name: publishedAt
|
|
112
|
+
type: timestamp # ISO 8601 datetime
|
|
113
|
+
|
|
114
|
+
- name: birthDate
|
|
115
|
+
type: date
|
|
116
|
+
|
|
117
|
+
- name: isActive
|
|
118
|
+
type: boolean
|
|
119
|
+
default: true
|
|
120
|
+
|
|
121
|
+
# ── Access policies ────────────────────────────────────────────────────────
|
|
122
|
+
# Each CRUD operation can have one or more policy rules evaluated in order.
|
|
123
|
+
# access values: public | restricted | admin | forbidden
|
|
124
|
+
# Emoji shortcuts: 🌐 = public | 🔒 = restricted | 👨🏻💻 = admin | 🚫 = forbidden
|
|
125
|
+
policies:
|
|
126
|
+
create:
|
|
127
|
+
- access: public # Anyone can sign up
|
|
128
|
+
read:
|
|
129
|
+
- access: restricted # Must be authenticated
|
|
130
|
+
allow: User # …and belong to the User entity
|
|
131
|
+
condition: self # …and only read their own record
|
|
132
|
+
update:
|
|
133
|
+
- access: restricted
|
|
134
|
+
allow: User
|
|
135
|
+
condition: self
|
|
136
|
+
delete:
|
|
137
|
+
- access: admin # Only admins
|
|
138
|
+
# signup policy controls who can call the signup endpoint (authenticable only)
|
|
139
|
+
signup:
|
|
140
|
+
- access: public
|
|
141
|
+
|
|
142
|
+
# ── Entity-level validation ────────────────────────────────────────────────
|
|
143
|
+
# Validation rules applied per property on create / update requests.
|
|
144
|
+
# All available validation rules are listed here (use only what you need).
|
|
145
|
+
validation:
|
|
146
|
+
name:
|
|
147
|
+
required: true # Field must be present in the request
|
|
148
|
+
isNotEmpty: true # Value must not be an empty string
|
|
149
|
+
minLength: 2
|
|
150
|
+
maxLength: 100
|
|
151
|
+
email:
|
|
152
|
+
isDefined: true # Value must not be undefined
|
|
153
|
+
isEmail: true
|
|
154
|
+
age:
|
|
155
|
+
isOptional: true
|
|
156
|
+
min: 0
|
|
157
|
+
max: 120
|
|
158
|
+
role:
|
|
159
|
+
isIn: [admin, editor, viewer]
|
|
160
|
+
bio:
|
|
161
|
+
isOptional: true
|
|
162
|
+
maxLength: 500
|
|
163
|
+
# Additional validators (not all will apply to every type):
|
|
164
|
+
# equals: someValue
|
|
165
|
+
# notEquals: someValue
|
|
166
|
+
# isEmpty: false
|
|
167
|
+
# isNotIn: [banned, blocked]
|
|
168
|
+
# contains: '@'
|
|
169
|
+
# notContains: spam
|
|
170
|
+
# isAlpha: true
|
|
171
|
+
# isAlphanumeric: true
|
|
172
|
+
# isAscii: true
|
|
173
|
+
# isJSON: false
|
|
174
|
+
# matches: '^[a-z]+$'
|
|
175
|
+
# isMimeType: true
|
|
176
|
+
|
|
177
|
+
# ── Webhooks (lifecycle hooks) ─────────────────────────────────────────────
|
|
178
|
+
# Send HTTP requests to external services on entity lifecycle events.
|
|
179
|
+
hooks:
|
|
180
|
+
afterCreate:
|
|
181
|
+
- url: https://hooks.example.com/user-created
|
|
182
|
+
method: POST # GET | POST | PUT | PATCH | DELETE. Default: POST
|
|
183
|
+
headers:
|
|
184
|
+
X-Api-Key: my-key
|
|
185
|
+
afterUpdate:
|
|
186
|
+
- url: https://hooks.example.com/user-updated
|
|
187
|
+
method: POST
|
|
188
|
+
beforeDelete:
|
|
189
|
+
- url: https://hooks.example.com/before-user-deleted
|
|
190
|
+
method: POST
|
|
191
|
+
# Other available events: beforeCreate | beforeUpdate | afterDelete
|
|
192
|
+
|
|
193
|
+
# ── Middlewares ────────────────────────────────────────────────────────────
|
|
194
|
+
# Run a custom function at specific lifecycle points.
|
|
195
|
+
# Handler files live in the /functions (or CHADSTART_FUNCTIONS_FOLDER) folder.
|
|
196
|
+
middlewares:
|
|
197
|
+
beforeCreate:
|
|
198
|
+
- function: hashApiKey.js
|
|
199
|
+
afterCreate:
|
|
200
|
+
- function: sendWelcomeEmail.js
|
|
201
|
+
# Other available events: beforeUpdate | afterUpdate | beforeDelete | afterDelete
|
|
202
|
+
|
|
203
|
+
# ── Standard collection entity ────────────────────────────────────────────
|
|
204
|
+
Post:
|
|
205
|
+
mainProp: title
|
|
206
|
+
properties:
|
|
207
|
+
- title
|
|
208
|
+
- { name: content, type: richText }
|
|
209
|
+
- { name: publishedAt, type: timestamp }
|
|
210
|
+
- { name: coverImage, type: image, options: { sizes: { large: { width: 1200 } } } }
|
|
211
|
+
|
|
212
|
+
# ── Relations ───────────────────────────────────────────────────────────
|
|
213
|
+
# belongsTo → many-to-one (adds a foreign key on this entity)
|
|
214
|
+
# belongsToMany → many-to-many (uses a join table)
|
|
215
|
+
|
|
216
|
+
# Short syntax: just the related entity name.
|
|
217
|
+
belongsTo:
|
|
218
|
+
- User
|
|
219
|
+
|
|
220
|
+
# Long syntax: rename the relation and/or add extra options.
|
|
221
|
+
# belongsTo:
|
|
222
|
+
# - name: author
|
|
223
|
+
# entity: User
|
|
224
|
+
# helpText: The author of this post
|
|
225
|
+
# eager: false # true = automatically load this relation in all queries
|
|
226
|
+
|
|
227
|
+
belongsToMany:
|
|
228
|
+
- Tag
|
|
229
|
+
|
|
230
|
+
policies:
|
|
231
|
+
create:
|
|
232
|
+
- { access: restricted, allow: User }
|
|
233
|
+
read:
|
|
234
|
+
- access: public
|
|
235
|
+
update:
|
|
236
|
+
- { access: restricted, allow: User, condition: self }
|
|
237
|
+
delete:
|
|
238
|
+
- access: admin
|
|
239
|
+
|
|
240
|
+
Tag:
|
|
241
|
+
properties:
|
|
242
|
+
- name
|
|
243
|
+
policies:
|
|
244
|
+
create:
|
|
245
|
+
- access: admin
|
|
246
|
+
read:
|
|
247
|
+
- access: public
|
|
248
|
+
update:
|
|
249
|
+
- access: admin
|
|
250
|
+
delete:
|
|
251
|
+
- access: admin
|
|
252
|
+
|
|
253
|
+
# ── Single entity ──────────────────────────────────────────────────────────
|
|
254
|
+
# A single entity has exactly one record — Create and Delete are disabled.
|
|
255
|
+
# Useful for site settings, homepage content, etc.
|
|
256
|
+
SiteSettings:
|
|
257
|
+
single: true
|
|
258
|
+
slug: site-settings
|
|
259
|
+
properties:
|
|
260
|
+
- { name: siteName, type: string }
|
|
261
|
+
- { name: tagline, type: text }
|
|
262
|
+
- { name: logo, type: image }
|
|
263
|
+
validation:
|
|
264
|
+
siteName: { required: true }
|
|
265
|
+
|
|
266
|
+
# ── Reusable property groups ──────────────────────────────────────────────────
|
|
267
|
+
# Groups are sets of properties you can embed in multiple entities using
|
|
268
|
+
# type: group. They are stored as JSON in the parent entity's row.
|
|
269
|
+
|
|
270
|
+
groups:
|
|
271
|
+
Address:
|
|
272
|
+
properties:
|
|
273
|
+
- { name: street, type: string }
|
|
274
|
+
- { name: city, type: string }
|
|
275
|
+
- { name: zipCode, type: string }
|
|
276
|
+
- { name: country, type: string }
|
|
277
|
+
validation:
|
|
278
|
+
city: { isNotEmpty: true }
|
|
279
|
+
|
|
280
|
+
# Usage in an entity property (multiple items by default):
|
|
281
|
+
# - name: addresses
|
|
282
|
+
# type: group
|
|
283
|
+
# options:
|
|
284
|
+
# group: Address # Name of the group defined above
|
|
285
|
+
# multiple: true # false = single nested object; Default: true
|
|
286
|
+
|
|
287
|
+
# ── Custom functions ──────────────────────────────────────────────────────────
|
|
288
|
+
# Add your own functions — each can have multiple triggers (HTTP, cron, event).
|
|
289
|
+
# Runtime is optional; defaults to 'js'. Files live in /functions (or CHADSTART_FUNCTIONS_FOLDER).
|
|
290
|
+
#
|
|
291
|
+
# Supported runtimes: js | bash | python | go | c++ | ruby | php
|
|
292
|
+
# Supported triggers: http | cron | event
|
|
293
|
+
#
|
|
294
|
+
# Predefined cron aliases: @yearly @annually @monthly @weekly @daily @midnight @hourly
|
|
295
|
+
# HTTP policies: public (default) | restricted | admin | forbidden
|
|
296
|
+
|
|
297
|
+
functions:
|
|
298
|
+
hello:
|
|
299
|
+
runtime: js # js (default) | bash | python | go | c++ | ruby | php
|
|
300
|
+
function: hello.js # path relative to CHADSTART_FUNCTIONS_FOLDER (default: functions/)
|
|
301
|
+
triggers:
|
|
302
|
+
- type: http
|
|
303
|
+
method: GET
|
|
304
|
+
path: /hello
|
|
305
|
+
# policies omitted → public (anyone can call)
|
|
306
|
+
- type: event
|
|
307
|
+
name: user.created # Emitted by middleware/other functions via eventBus
|
|
308
|
+
- type: cron
|
|
309
|
+
schedule: "*/10 * * * *" # Every 10 minutes
|
|
310
|
+
- type: cron
|
|
311
|
+
schedule: "@daily" # Predefined alias (resolves to "0 0 * * *")
|
|
312
|
+
|
|
313
|
+
sendPasswordReset:
|
|
314
|
+
function: sendPasswordReset.js
|
|
315
|
+
description: Sends a password-reset email. Open to anyone.
|
|
316
|
+
triggers:
|
|
317
|
+
- type: http
|
|
318
|
+
method: POST
|
|
319
|
+
path: /auth/password-reset
|
|
320
|
+
policies:
|
|
321
|
+
- access: public
|
|
322
|
+
|
|
323
|
+
adminStats:
|
|
324
|
+
function: adminStats.js
|
|
325
|
+
description: Usage statistics. Requires any valid auth token.
|
|
326
|
+
triggers:
|
|
327
|
+
- type: http
|
|
328
|
+
method: GET
|
|
329
|
+
path: /admin/stats
|
|
330
|
+
policies:
|
|
331
|
+
- access: admin
|
|
332
|
+
|
|
333
|
+
myProfile:
|
|
334
|
+
function: myProfile.js
|
|
335
|
+
description: User-specific data. Only Customer tokens may access it.
|
|
336
|
+
triggers:
|
|
337
|
+
- type: http
|
|
338
|
+
method: GET
|
|
339
|
+
path: /me
|
|
340
|
+
policies:
|
|
341
|
+
- access: restricted
|
|
342
|
+
allow: Customer
|
|
343
|
+
|
|
344
|
+
# ── File buckets ──────────────────────────────────────────────────────────────
|
|
345
|
+
# Named storage locations for uploaded files.
|
|
346
|
+
|
|
347
|
+
files:
|
|
348
|
+
uploads:
|
|
349
|
+
path: ./uploads # Local directory (relative to this config file)
|
|
350
|
+
public: true # Serve files at /files/uploads/*. Default: false
|
|
351
|
+
|
|
352
|
+
privateDocuments:
|
|
353
|
+
path: ./private-docs
|
|
354
|
+
public: false
|
|
355
|
+
|
|
356
|
+
# ── Static public folder ──────────────────────────────────────────────────────
|
|
357
|
+
# Serve a folder of static assets (HTML, CSS, images, …) from the root path.
|
|
358
|
+
|
|
359
|
+
public:
|
|
360
|
+
folder: ./public
|
|
361
|
+
|
|
362
|
+
# ── Plugins ───────────────────────────────────────────────────────────────────
|
|
363
|
+
# Extend the server with additional Express routes and logic.
|
|
364
|
+
# ⚠ Remote plugins execute arbitrary code — only load plugins from trusted sources.
|
|
365
|
+
|
|
366
|
+
plugins:
|
|
367
|
+
- repo: https://github.com/org/chadstart-plugin-example # Remote GitHub repo
|
|
368
|
+
- path: ./my-local-plugin # Local directory
|
|
369
|
+
name: my-local-plugin # Optional display name
|
|
370
|
+
|
|
371
|
+
# ── Admin UI & Admin Entity ───────────────────────────────────────────────────
|
|
372
|
+
# Control the built-in Admin UI and Admin entity (superuser access).
|
|
373
|
+
#
|
|
374
|
+
# By default:
|
|
375
|
+
# - An Admin entity is auto-created with email + password (authenticable).
|
|
376
|
+
# - The Admin UI is available at /admin (login page at /login).
|
|
377
|
+
# - The admin UI requires admin-level access.
|
|
378
|
+
#
|
|
379
|
+
# If you define entities.Admin in your YAML, it will be merged with the default.
|
|
380
|
+
|
|
381
|
+
admin:
|
|
382
|
+
enable_app: true # Serve the Admin UI at /admin. Default: true
|
|
383
|
+
enable_entity: true # Create the built-in Admin entity. Default: true
|
|
384
|
+
policies:
|
|
385
|
+
- access: admin # Who can access /admin. Default: access: admin
|
|
386
|
+
|
|
387
|
+
# ── Rate limiting ─────────────────────────────────────────────────────────────
|
|
388
|
+
# Protect your API from abuse.
|
|
389
|
+
|
|
390
|
+
rateLimits:
|
|
391
|
+
- name: default
|
|
392
|
+
limit: 100 # Max requests allowed in the time window
|
|
393
|
+
ttl: 60000 # Time window in milliseconds (60 s here)
|
|
394
|
+
- name: strict
|
|
395
|
+
limit: 10
|
|
396
|
+
ttl: 60000
|
|
397
|
+
|
|
398
|
+
# ── OpenTelemetry tracing ──────────────────────────────────────────────────────
|
|
399
|
+
# Non-secret values can be set here; secret values (auth headers / API keys)
|
|
400
|
+
# must be set via environment variables only — see .env.example.
|
|
401
|
+
|
|
402
|
+
telemetry:
|
|
403
|
+
enabled: false # Also configurable via OTEL_ENABLED env var
|
|
404
|
+
serviceName: my-app # Overridable via OTEL_SERVICE_NAME env var
|
|
405
|
+
endpoint: http://localhost:4318 # Overridable via OTEL_EXPORTER_OTLP_ENDPOINT env var
|
|
406
|
+
# Auth headers (e.g., "authorization=Bearer <token>") must be set via
|
|
407
|
+
# OTEL_EXPORTER_OTLP_HEADERS env var — never place secrets in this file.
|
|
408
|
+
|
|
409
|
+
# ── Error reporting (Sentry / Bugsink) ────────────────────────────────────────
|
|
410
|
+
# Set SENTRY_DSN in your .env to enable. The DSN is a secret — never put it here.
|
|
411
|
+
# Bugsink (https://www.bugsink.com) is a self-hosted Sentry-compatible alternative.
|
|
412
|
+
|
|
413
|
+
sentry:
|
|
414
|
+
environment: production # Label sent to Sentry. Defaults to NODE_ENV.
|
|
415
|
+
tracesSampleRate: 1.0 # Fraction of transactions to sample (0.0–1.0)
|
|
416
|
+
debug: false # Enable Sentry SDK debug logging
|