@vertebr/admin 0.4.0 → 0.5.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/dist/api/catalog.d.ts +59 -0
- package/dist/api/catalog.d.ts.map +1 -0
- package/dist/api/catalog.js +85 -0
- package/dist/api/catalog.js.map +1 -0
- package/dist/api/configs.d.ts +15 -0
- package/dist/api/configs.d.ts.map +1 -0
- package/dist/api/configs.js +43 -0
- package/dist/api/configs.js.map +1 -0
- package/dist/api/emails.d.ts +85 -0
- package/dist/api/emails.d.ts.map +1 -0
- package/dist/api/emails.js +520 -0
- package/dist/api/emails.js.map +1 -0
- package/dist/api/gallery.d.ts +28 -0
- package/dist/api/gallery.d.ts.map +1 -0
- package/dist/api/gallery.js +91 -0
- package/dist/api/gallery.js.map +1 -0
- package/dist/api/metrics.d.ts +22 -0
- package/dist/api/metrics.d.ts.map +1 -0
- package/dist/api/metrics.js +38 -0
- package/dist/api/metrics.js.map +1 -0
- package/dist/api/public.d.ts +22 -0
- package/dist/api/public.d.ts.map +1 -0
- package/dist/api/public.js +69 -0
- package/dist/api/public.js.map +1 -0
- package/dist/api/request.d.ts +26 -0
- package/dist/api/request.d.ts.map +1 -0
- package/dist/api/request.js +38 -0
- package/dist/api/request.js.map +1 -0
- package/dist/api/resources.d.ts +85 -0
- package/dist/api/resources.d.ts.map +1 -0
- package/dist/api/resources.js +263 -0
- package/dist/api/resources.js.map +1 -0
- package/dist/api/routes.d.ts +8 -0
- package/dist/api/routes.d.ts.map +1 -0
- package/dist/api/routes.js +202 -0
- package/dist/api/routes.js.map +1 -0
- package/dist/api/spa.d.ts +2 -0
- package/dist/api/spa.d.ts.map +1 -0
- package/dist/api/spa.js +34 -0
- package/dist/api/spa.js.map +1 -0
- package/dist/connectors/payments/connector.d.ts +30 -0
- package/dist/connectors/payments/connector.d.ts.map +1 -0
- package/dist/connectors/payments/connector.js +226 -0
- package/dist/connectors/payments/connector.js.map +1 -0
- package/dist/connectors/payments/index.d.ts +4 -0
- package/dist/connectors/payments/index.d.ts.map +1 -0
- package/dist/connectors/payments/index.js +10 -0
- package/dist/connectors/payments/index.js.map +1 -0
- package/dist/connectors/payments/stripe.d.ts +99 -0
- package/dist/connectors/payments/stripe.d.ts.map +1 -0
- package/dist/connectors/payments/stripe.js +189 -0
- package/dist/connectors/payments/stripe.js.map +1 -0
- package/dist/connectors/payments/types.d.ts +67 -0
- package/dist/connectors/payments/types.d.ts.map +1 -0
- package/dist/connectors/payments/types.js +16 -0
- package/dist/connectors/payments/types.js.map +1 -0
- package/dist/connectors/stripe.d.ts +19 -0
- package/dist/connectors/stripe.d.ts.map +1 -0
- package/dist/connectors/stripe.js +26 -0
- package/dist/connectors/stripe.js.map +1 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +8 -0
- package/dist/constants.js.map +1 -0
- package/dist/createAdmin.d.ts +3 -0
- package/dist/createAdmin.d.ts.map +1 -0
- package/dist/createAdmin.js +152 -0
- package/dist/createAdmin.js.map +1 -0
- package/dist/database/connector.d.ts +1 -1
- package/dist/database/connector.d.ts.map +1 -1
- package/dist/database/connector.js +42 -6
- package/dist/database/connector.js.map +1 -1
- package/dist/database/types.d.ts +20 -5
- package/dist/database/types.d.ts.map +1 -1
- package/dist/errors.d.ts +17 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +36 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +9 -264
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -957
- package/dist/index.js.map +1 -1
- package/dist/mail.d.ts +23 -0
- package/dist/mail.d.ts.map +1 -0
- package/dist/mail.js +248 -0
- package/dist/mail.js.map +1 -0
- package/dist/schema.d.ts +6 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/services/environment.d.ts +8 -0
- package/dist/services/environment.d.ts.map +1 -0
- package/dist/services/environment.js +78 -0
- package/dist/services/environment.js.map +1 -0
- package/dist/services/registry.d.ts +6 -0
- package/dist/services/registry.d.ts.map +1 -0
- package/dist/services/registry.js +32 -0
- package/dist/services/registry.js.map +1 -0
- package/dist/services/state.d.ts +25 -0
- package/dist/services/state.d.ts.map +1 -0
- package/dist/services/state.js +3 -0
- package/dist/services/state.js.map +1 -0
- package/dist/types/actions.d.ts +15 -0
- package/dist/types/actions.d.ts.map +1 -0
- package/dist/types/actions.js +3 -0
- package/dist/types/actions.js.map +1 -0
- package/dist/types/admin.d.ts +83 -0
- package/dist/types/admin.d.ts.map +1 -0
- package/dist/types/admin.js +3 -0
- package/dist/types/admin.js.map +1 -0
- package/dist/types/auth.d.ts +67 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/auth.js +3 -0
- package/dist/types/auth.js.map +1 -0
- package/dist/types/config.d.ts +39 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +3 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/index.d.ts +15 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/layout.d.ts +29 -0
- package/dist/types/layout.d.ts.map +1 -0
- package/dist/types/layout.js +3 -0
- package/dist/types/layout.js.map +1 -0
- package/dist/types/mail/catalog.d.ts +104 -0
- package/dist/types/mail/catalog.d.ts.map +1 -0
- package/dist/types/mail/catalog.js +3 -0
- package/dist/types/mail/catalog.js.map +1 -0
- package/dist/types/mail/definition.d.ts +68 -0
- package/dist/types/mail/definition.d.ts.map +1 -0
- package/dist/types/mail/definition.js +3 -0
- package/dist/types/mail/definition.js.map +1 -0
- package/dist/types/mail/index.d.ts +4 -0
- package/dist/types/mail/index.d.ts.map +1 -0
- package/dist/types/mail/index.js +3 -0
- package/dist/types/mail/index.js.map +1 -0
- package/dist/types/mail/message.d.ts +45 -0
- package/dist/types/mail/message.d.ts.map +1 -0
- package/dist/types/mail/message.js +3 -0
- package/dist/types/mail/message.js.map +1 -0
- package/dist/types/metrics.d.ts +46 -0
- package/dist/types/metrics.d.ts.map +1 -0
- package/dist/types/metrics.js +3 -0
- package/dist/types/metrics.js.map +1 -0
- package/dist/types/resource.d.ts +41 -0
- package/dist/types/resource.d.ts.map +1 -0
- package/dist/types/resource.js +3 -0
- package/dist/types/resource.js.map +1 -0
- package/dist/utils/auth.d.ts +4 -0
- package/dist/utils/auth.d.ts.map +1 -0
- package/dist/utils/auth.js +29 -0
- package/dist/utils/auth.js.map +1 -0
- package/dist/utils/config.d.ts +4 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +14 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/http.d.ts +7 -0
- package/dist/utils/http.d.ts.map +1 -0
- package/dist/utils/http.js +61 -0
- package/dist/utils/http.js.map +1 -0
- package/dist/utils/metricsRange.d.ts +34 -0
- package/dist/utils/metricsRange.d.ts.map +1 -0
- package/dist/utils/metricsRange.js +121 -0
- package/dist/utils/metricsRange.js.map +1 -0
- package/dist/utils/sidebar.d.ts +2 -0
- package/dist/utils/sidebar.d.ts.map +1 -0
- package/dist/utils/sidebar.js +8 -0
- package/dist/utils/sidebar.js.map +1 -0
- package/dist/utils/staticDir.d.ts +2 -0
- package/dist/utils/staticDir.d.ts.map +1 -0
- package/dist/utils/staticDir.js +16 -0
- package/dist/utils/staticDir.js.map +1 -0
- package/dist/utils/uploads.d.ts +6 -0
- package/dist/utils/uploads.d.ts.map +1 -0
- package/dist/utils/uploads.js +66 -0
- package/dist/utils/uploads.js.map +1 -0
- package/package.json +1 -1
- package/static/assets/index-DKyVvFeT.js +332 -0
- package/static/assets/index-DUwFg1bw.css +1 -0
- package/static/index.html +2 -2
- package/static/assets/index-CZ7lykOe.css +0 -1
- package/static/assets/index-DvZiFDex.js +0 -329
package/dist/index.js
CHANGED
|
@@ -3,964 +3,30 @@
|
|
|
3
3
|
* Vertebr — core admin control plane.
|
|
4
4
|
* Wire resources once; owners control them from the admin UI.
|
|
5
5
|
*/
|
|
6
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
-
};
|
|
9
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.whereEq = exports.postgresJsonbTableDDL = exports.postgresAdapter = exports.mongoAdapter = exports.memoryAdapter = exports.firestoreAdapter = exports.databaseConnector = exports.adapterList = exports.adapterCount = exports.adapterConfigStore = exports.
|
|
11
|
-
|
|
12
|
-
exports.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
var
|
|
17
|
-
Object.defineProperty(exports, "
|
|
18
|
-
Object.defineProperty(exports, "
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
exports.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this.name = "VertebrNotFoundError";
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
exports.VertebrNotFoundError = VertebrNotFoundError;
|
|
37
|
-
class VertebrEnvError extends Error {
|
|
38
|
-
statusCode = 400;
|
|
39
|
-
constructor(message = "Invalid environment") {
|
|
40
|
-
super(message);
|
|
41
|
-
this.name = "VertebrEnvError";
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
exports.VertebrEnvError = VertebrEnvError;
|
|
45
|
-
class VertebrBadRequestError extends Error {
|
|
46
|
-
statusCode = 400;
|
|
47
|
-
constructor(message = "Bad request") {
|
|
48
|
-
super(message);
|
|
49
|
-
this.name = "VertebrBadRequestError";
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
exports.VertebrBadRequestError = VertebrBadRequestError;
|
|
53
|
-
const DEFAULT_UPLOAD_ACCEPT = ["image/*"];
|
|
54
|
-
const DEFAULT_UPLOAD_MAX_BYTES = 5_000_000;
|
|
55
|
-
function publicUploads(resource) {
|
|
56
|
-
if (!resource.uploads)
|
|
57
|
-
return undefined;
|
|
58
|
-
const entries = Object.entries(resource.uploads).map(([field, def]) => [
|
|
59
|
-
field,
|
|
60
|
-
{
|
|
61
|
-
field,
|
|
62
|
-
accept: def.accept?.length ? def.accept : DEFAULT_UPLOAD_ACCEPT,
|
|
63
|
-
maxBytes: def.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES,
|
|
64
|
-
},
|
|
65
|
-
]);
|
|
66
|
-
return entries.length > 0
|
|
67
|
-
? Object.fromEntries(entries)
|
|
68
|
-
: undefined;
|
|
69
|
-
}
|
|
70
|
-
function mimeAllowed(type, accept) {
|
|
71
|
-
if (accept.length === 0)
|
|
72
|
-
return true;
|
|
73
|
-
const normalized = type.toLowerCase() || "application/octet-stream";
|
|
74
|
-
return accept.some((rule) => {
|
|
75
|
-
const pattern = rule.toLowerCase().trim();
|
|
76
|
-
if (!pattern || pattern === "*/*")
|
|
77
|
-
return true;
|
|
78
|
-
if (pattern.endsWith("/*")) {
|
|
79
|
-
return normalized.startsWith(pattern.slice(0, -1));
|
|
80
|
-
}
|
|
81
|
-
return normalized === pattern;
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
function decodeUploadPayload(entry) {
|
|
85
|
-
if (!entry || typeof entry !== "object") {
|
|
86
|
-
throw new VertebrBadRequestError("Invalid upload payload");
|
|
87
|
-
}
|
|
88
|
-
const body = entry;
|
|
89
|
-
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
90
|
-
const type = typeof body.type === "string" && body.type.trim()
|
|
91
|
-
? body.type.trim()
|
|
92
|
-
: "application/octet-stream";
|
|
93
|
-
const data = typeof body.data === "string" ? body.data : "";
|
|
94
|
-
if (!name) {
|
|
95
|
-
throw new VertebrBadRequestError("Upload is missing a file name");
|
|
96
|
-
}
|
|
97
|
-
if (!data) {
|
|
98
|
-
throw new VertebrBadRequestError("Upload is missing file data");
|
|
99
|
-
}
|
|
100
|
-
const base64 = data.includes(",") ? data.slice(data.indexOf(",") + 1) : data;
|
|
101
|
-
let buffer;
|
|
102
|
-
try {
|
|
103
|
-
buffer = Buffer.from(base64, "base64");
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
throw new VertebrBadRequestError("Upload data is not valid base64");
|
|
107
|
-
}
|
|
108
|
-
if (!buffer.length) {
|
|
109
|
-
throw new VertebrBadRequestError("Upload file is empty");
|
|
110
|
-
}
|
|
111
|
-
return { name, type, size: buffer.byteLength, buffer };
|
|
112
|
-
}
|
|
113
|
-
function defaultStaticDir() {
|
|
114
|
-
const candidate = node_path_1.default.join(__dirname, "..", "static");
|
|
115
|
-
if (node_fs_1.default.existsSync(node_path_1.default.join(candidate, "index.html"))) {
|
|
116
|
-
return candidate;
|
|
117
|
-
}
|
|
118
|
-
return undefined;
|
|
119
|
-
}
|
|
120
|
-
function contentTypeFor(filePath) {
|
|
121
|
-
const ext = node_path_1.default.extname(filePath).toLowerCase();
|
|
122
|
-
switch (ext) {
|
|
123
|
-
case ".html":
|
|
124
|
-
return "text/html; charset=utf-8";
|
|
125
|
-
case ".js":
|
|
126
|
-
return "text/javascript; charset=utf-8";
|
|
127
|
-
case ".css":
|
|
128
|
-
return "text/css; charset=utf-8";
|
|
129
|
-
case ".svg":
|
|
130
|
-
return "image/svg+xml";
|
|
131
|
-
case ".json":
|
|
132
|
-
return "application/json";
|
|
133
|
-
case ".png":
|
|
134
|
-
return "image/png";
|
|
135
|
-
case ".woff2":
|
|
136
|
-
return "font/woff2";
|
|
137
|
-
default:
|
|
138
|
-
return "application/octet-stream";
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
function headerValue(headers, name) {
|
|
142
|
-
if (!headers)
|
|
143
|
-
return undefined;
|
|
144
|
-
const raw = headers[name] ?? headers[name.toLowerCase()];
|
|
145
|
-
if (Array.isArray(raw))
|
|
146
|
-
return raw[0];
|
|
147
|
-
return typeof raw === "string" ? raw : undefined;
|
|
148
|
-
}
|
|
149
|
-
function createAdmin(options) {
|
|
150
|
-
const basePath = (options.basePath ?? "/admin").replace(/\/$/, "") || "/admin";
|
|
151
|
-
const apiBase = `${basePath}/api`;
|
|
152
|
-
const resources = [];
|
|
153
|
-
const configs = [];
|
|
154
|
-
let metricsDef = null;
|
|
155
|
-
let galleryDef = null;
|
|
156
|
-
const controlMeta = new Map();
|
|
157
|
-
const staticDir = options.staticDir ?? defaultStaticDir();
|
|
158
|
-
const environments = options.environments && options.environments.length > 0
|
|
159
|
-
? options.environments
|
|
160
|
-
: [{ id: "default", label: "Default", tone: "neutral" }];
|
|
161
|
-
const defaultEnvironment = options.defaultEnvironment ?? environments[0]?.id ?? "default";
|
|
162
|
-
if (!environments.some((env) => env.id === defaultEnvironment)) {
|
|
163
|
-
throw new Error(`defaultEnvironment "${defaultEnvironment}" is not in environments`);
|
|
164
|
-
}
|
|
165
|
-
function resolveDefaults(schema, defaults, env) {
|
|
166
|
-
const fromSchema = {};
|
|
167
|
-
for (const [key, field] of Object.entries(schema)) {
|
|
168
|
-
if (field.default !== undefined) {
|
|
169
|
-
fromSchema[key] = field.default;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
const fromDef = typeof defaults === "function" ? defaults(env) : (defaults ?? {});
|
|
173
|
-
return { ...fromSchema, ...fromDef };
|
|
174
|
-
}
|
|
175
|
-
function resolveEnv(request) {
|
|
176
|
-
const header = headerValue(request.headers, exports.VERTEBR_ENV_HEADER);
|
|
177
|
-
const env = (header ?? defaultEnvironment).trim();
|
|
178
|
-
if (!environments.some((item) => item.id === env)) {
|
|
179
|
-
throw new VertebrEnvError(`Unknown environment "${env}". Expected one of: ${environments
|
|
180
|
-
.map((item) => item.id)
|
|
181
|
-
.join(", ")}`);
|
|
182
|
-
}
|
|
183
|
-
return env;
|
|
184
|
-
}
|
|
185
|
-
function publicAuth(env) {
|
|
186
|
-
return {
|
|
187
|
-
required: Boolean(env.auth?.required),
|
|
188
|
-
...(env.auth?.claimKey ? { claimKey: env.auth.claimKey } : {}),
|
|
189
|
-
...(env.auth?.roles?.length ? { roles: [...env.auth.roles] } : {}),
|
|
190
|
-
...(env.auth?.client ? { client: env.auth.client } : {}),
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
function claimSatisfied(claims, key, expected) {
|
|
194
|
-
const value = claims?.[key];
|
|
195
|
-
if (value == null)
|
|
196
|
-
return false;
|
|
197
|
-
if (Array.isArray(value)) {
|
|
198
|
-
return value.map(String).includes(expected);
|
|
199
|
-
}
|
|
200
|
-
if (typeof value === "string") {
|
|
201
|
-
return (value === expected ||
|
|
202
|
-
value
|
|
203
|
-
.split(",")
|
|
204
|
-
.map((part) => part.trim())
|
|
205
|
-
.includes(expected));
|
|
206
|
-
}
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
async function assertAuth(request, envId) {
|
|
210
|
-
const environment = environments.find((item) => item.id === envId);
|
|
211
|
-
if (!environment) {
|
|
212
|
-
throw new VertebrEnvError(`Unknown environment "${envId}"`);
|
|
213
|
-
}
|
|
214
|
-
const user = options.auth.getUser
|
|
215
|
-
? await options.auth.getUser(request)
|
|
216
|
-
: null;
|
|
217
|
-
const envAuth = environment.auth;
|
|
218
|
-
if (envAuth?.required) {
|
|
219
|
-
if (!user) {
|
|
220
|
-
throw new VertebrAuthError("Sign in required for this environment");
|
|
221
|
-
}
|
|
222
|
-
if (envAuth.claimKey) {
|
|
223
|
-
const expected = envAuth.claimIncludes ?? environment.id;
|
|
224
|
-
if (!claimSatisfied(user.claims, envAuth.claimKey, expected)) {
|
|
225
|
-
throw new VertebrAuthError(`Missing claim ${envAuth.claimKey}=${expected} for environment "${environment.id}"`);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
if (envAuth.roles?.length) {
|
|
229
|
-
const roles = user.roles ?? [];
|
|
230
|
-
const ok = envAuth.roles.some((role) => roles.includes(role));
|
|
231
|
-
if (!ok) {
|
|
232
|
-
throw new VertebrAuthError(`Requires one of roles: ${envAuth.roles.join(", ")}`);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
const allowed = await options.auth.check(request, {
|
|
237
|
-
env: envId,
|
|
238
|
-
user,
|
|
239
|
-
environment,
|
|
240
|
-
});
|
|
241
|
-
if (!allowed) {
|
|
242
|
-
throw new VertebrAuthError();
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
async function withEnvContext(request, run) {
|
|
246
|
-
const env = resolveEnv(request);
|
|
247
|
-
await assertAuth(request, env);
|
|
248
|
-
const ctx = { env, request };
|
|
249
|
-
const execute = () => Promise.resolve(run(ctx));
|
|
250
|
-
if (options.onEnvironment) {
|
|
251
|
-
return options.onEnvironment(env, execute);
|
|
252
|
-
}
|
|
253
|
-
return execute();
|
|
254
|
-
}
|
|
255
|
-
/** Public storefront reads — resolve env, no auth. */
|
|
256
|
-
async function withPublicEnvContext(request, run) {
|
|
257
|
-
const env = resolveEnv(request);
|
|
258
|
-
const ctx = { env, request };
|
|
259
|
-
const execute = () => Promise.resolve(run(ctx));
|
|
260
|
-
if (options.onEnvironment) {
|
|
261
|
-
return options.onEnvironment(env, execute);
|
|
262
|
-
}
|
|
263
|
-
return execute();
|
|
264
|
-
}
|
|
265
|
-
function findResource(id) {
|
|
266
|
-
const resource = resources.find((r) => r.id === id);
|
|
267
|
-
if (!resource) {
|
|
268
|
-
throw new VertebrNotFoundError(`Resource "${id}" not found`);
|
|
269
|
-
}
|
|
270
|
-
return resource;
|
|
271
|
-
}
|
|
272
|
-
function findConfig(id) {
|
|
273
|
-
const config = configs.find((c) => c.id === id);
|
|
274
|
-
if (!config) {
|
|
275
|
-
throw new VertebrNotFoundError(`Config "${id}" not found`);
|
|
276
|
-
}
|
|
277
|
-
return config;
|
|
278
|
-
}
|
|
279
|
-
async function readConfigValue(id, env) {
|
|
280
|
-
const meta = controlMeta.get(id);
|
|
281
|
-
if (meta) {
|
|
282
|
-
const stored = await meta.store.get(id, env);
|
|
283
|
-
return (0, store_1.mergeConfigValue)(resolveDefaults(meta.schema, meta.defaults, env), stored);
|
|
284
|
-
}
|
|
285
|
-
const config = findConfig(id);
|
|
286
|
-
return config.get({ env, request: null });
|
|
287
|
-
}
|
|
288
|
-
const bootstrapHandler = async () => ({
|
|
289
|
-
ok: true,
|
|
290
|
-
brand: "Vertebr",
|
|
291
|
-
defaultEnvironment,
|
|
292
|
-
environments: environments.map((env) => ({
|
|
293
|
-
id: env.id,
|
|
294
|
-
label: env.label,
|
|
295
|
-
tone: env.tone ?? "neutral",
|
|
296
|
-
auth: publicAuth(env),
|
|
297
|
-
})),
|
|
298
|
-
});
|
|
299
|
-
const catalogHandler = async (request) => withEnvContext(request, async (ctx) => ({
|
|
300
|
-
ok: true,
|
|
301
|
-
brand: "Vertebr",
|
|
302
|
-
environment: ctx.env,
|
|
303
|
-
environments: environments.map((env) => ({
|
|
304
|
-
id: env.id,
|
|
305
|
-
label: env.label,
|
|
306
|
-
tone: env.tone ?? "neutral",
|
|
307
|
-
auth: publicAuth(env),
|
|
308
|
-
})),
|
|
309
|
-
defaultEnvironment,
|
|
310
|
-
resources: resources.map((r) => ({
|
|
311
|
-
id: r.id,
|
|
312
|
-
label: r.label,
|
|
313
|
-
schema: r.schema,
|
|
314
|
-
permissions: {
|
|
315
|
-
create: r.permissions?.create ?? Boolean(r.create),
|
|
316
|
-
update: r.permissions?.update ?? Boolean(r.update),
|
|
317
|
-
delete: r.permissions?.delete ?? Boolean(r.remove),
|
|
318
|
-
},
|
|
319
|
-
uploads: publicUploads(r),
|
|
320
|
-
})),
|
|
321
|
-
configs: configs.map((c) => ({
|
|
322
|
-
id: c.id,
|
|
323
|
-
label: c.label,
|
|
324
|
-
schema: c.schema,
|
|
325
|
-
section: c.section === "page" ? "page" : "config",
|
|
326
|
-
})),
|
|
327
|
-
metrics: metricsDef
|
|
328
|
-
? {
|
|
329
|
-
id: metricsDef.id ?? "overview",
|
|
330
|
-
label: metricsDef.label ?? "Overview",
|
|
331
|
-
}
|
|
332
|
-
: null,
|
|
333
|
-
gallery: galleryDef
|
|
334
|
-
? {
|
|
335
|
-
id: galleryDef.id ?? "gallery",
|
|
336
|
-
label: galleryDef.label ?? "Gallery",
|
|
337
|
-
accept: galleryDef.accept?.length
|
|
338
|
-
? galleryDef.accept
|
|
339
|
-
: DEFAULT_UPLOAD_ACCEPT,
|
|
340
|
-
maxBytes: galleryDef.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES,
|
|
341
|
-
canDelete: Boolean(galleryDef.remove),
|
|
342
|
-
}
|
|
343
|
-
: null,
|
|
344
|
-
}));
|
|
345
|
-
const listResourceHandler = async (request) => withEnvContext(request, async (ctx) => {
|
|
346
|
-
const id = request.params?.id;
|
|
347
|
-
if (!id) {
|
|
348
|
-
throw new VertebrNotFoundError("Missing resource id");
|
|
349
|
-
}
|
|
350
|
-
const resource = findResource(id);
|
|
351
|
-
const items = resource.list ? await resource.list(ctx) : [];
|
|
352
|
-
return {
|
|
353
|
-
id: resource.id,
|
|
354
|
-
label: resource.label,
|
|
355
|
-
schema: resource.schema,
|
|
356
|
-
environment: ctx.env,
|
|
357
|
-
permissions: {
|
|
358
|
-
create: resource.permissions?.create ?? Boolean(resource.create),
|
|
359
|
-
update: resource.permissions?.update ?? Boolean(resource.update),
|
|
360
|
-
delete: resource.permissions?.delete ?? Boolean(resource.remove),
|
|
361
|
-
},
|
|
362
|
-
actions: resource.actions
|
|
363
|
-
? Object.fromEntries(Object.entries(resource.actions).map(([key, action]) => [
|
|
364
|
-
key,
|
|
365
|
-
{ label: action.label, input: action.input ?? {} },
|
|
366
|
-
]))
|
|
367
|
-
: {},
|
|
368
|
-
uploads: publicUploads(resource),
|
|
369
|
-
items,
|
|
370
|
-
};
|
|
371
|
-
});
|
|
372
|
-
const runFieldUpload = async (resource, field, file, ctx) => {
|
|
373
|
-
const def = resource.uploads?.[field];
|
|
374
|
-
if (!def) {
|
|
375
|
-
throw new VertebrNotFoundError(`Upload is not configured for field "${field}"`);
|
|
376
|
-
}
|
|
377
|
-
const accept = def.accept?.length ? def.accept : DEFAULT_UPLOAD_ACCEPT;
|
|
378
|
-
const maxBytes = def.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
|
|
379
|
-
if (file.size > maxBytes) {
|
|
380
|
-
throw new VertebrBadRequestError(`File exceeds max size of ${maxBytes} bytes`);
|
|
381
|
-
}
|
|
382
|
-
if (!mimeAllowed(file.type, accept)) {
|
|
383
|
-
throw new VertebrBadRequestError(`File type "${file.type}" is not allowed`);
|
|
384
|
-
}
|
|
385
|
-
const result = await def.upload(file, ctx);
|
|
386
|
-
if (!result?.url || typeof result.url !== "string") {
|
|
387
|
-
throw new VertebrBadRequestError("Upload handler did not return a url");
|
|
388
|
-
}
|
|
389
|
-
return { url: result.url, name: result.name ?? file.name };
|
|
390
|
-
};
|
|
391
|
-
const uploadResourceFileHandler = async (request) => withEnvContext(request, async (ctx) => {
|
|
392
|
-
const id = request.params?.id;
|
|
393
|
-
const field = request.params?.field;
|
|
394
|
-
if (!id || !field) {
|
|
395
|
-
throw new VertebrNotFoundError("Missing resource or field");
|
|
396
|
-
}
|
|
397
|
-
const resource = findResource(id);
|
|
398
|
-
const file = decodeUploadPayload(request.payload);
|
|
399
|
-
const result = await runFieldUpload(resource, field, file, ctx);
|
|
400
|
-
return { ok: true, id, field, environment: ctx.env, ...result };
|
|
401
|
-
});
|
|
402
|
-
const uploadResourceFilesHandler = async (request) => withEnvContext(request, async (ctx) => {
|
|
403
|
-
const id = request.params?.id;
|
|
404
|
-
const field = request.params?.field;
|
|
405
|
-
if (!id || !field) {
|
|
406
|
-
throw new VertebrNotFoundError("Missing resource or field");
|
|
407
|
-
}
|
|
408
|
-
const resource = findResource(id);
|
|
409
|
-
const payload = request.payload;
|
|
410
|
-
const list = payload &&
|
|
411
|
-
typeof payload === "object" &&
|
|
412
|
-
Array.isArray(payload.files)
|
|
413
|
-
? payload.files
|
|
414
|
-
: null;
|
|
415
|
-
if (!list?.length) {
|
|
416
|
-
throw new VertebrBadRequestError("Provide files: [] for bulk upload");
|
|
417
|
-
}
|
|
418
|
-
if (list.length > 40) {
|
|
419
|
-
throw new VertebrBadRequestError("Bulk upload is limited to 40 files");
|
|
420
|
-
}
|
|
421
|
-
const files = [];
|
|
422
|
-
for (const entry of list) {
|
|
423
|
-
const file = decodeUploadPayload(entry);
|
|
424
|
-
files.push(await runFieldUpload(resource, field, file, ctx));
|
|
425
|
-
}
|
|
426
|
-
return { ok: true, id, field, environment: ctx.env, files };
|
|
427
|
-
});
|
|
428
|
-
const updateResourceHandler = async (request) => withEnvContext(request, async (ctx) => {
|
|
429
|
-
const id = request.params?.id;
|
|
430
|
-
const rowId = request.params?.rowId;
|
|
431
|
-
if (!id || !rowId) {
|
|
432
|
-
throw new VertebrNotFoundError("Missing resource or row id");
|
|
433
|
-
}
|
|
434
|
-
const resource = findResource(id);
|
|
435
|
-
if (!resource.update) {
|
|
436
|
-
throw new VertebrNotFoundError(`Resource "${id}" is not updatable`);
|
|
437
|
-
}
|
|
438
|
-
const payload = request.payload;
|
|
439
|
-
const value = payload && typeof payload === "object"
|
|
440
|
-
? payload
|
|
441
|
-
: {};
|
|
442
|
-
const item = await resource.update(rowId, value, ctx);
|
|
443
|
-
return { ok: true, id, rowId, environment: ctx.env, item };
|
|
444
|
-
});
|
|
445
|
-
const createResourceHandler = async (request) => withEnvContext(request, async (ctx) => {
|
|
446
|
-
const id = request.params?.id;
|
|
447
|
-
if (!id) {
|
|
448
|
-
throw new VertebrNotFoundError("Missing resource id");
|
|
449
|
-
}
|
|
450
|
-
const resource = findResource(id);
|
|
451
|
-
if (!resource.create) {
|
|
452
|
-
throw new VertebrNotFoundError(`Resource "${id}" cannot create`);
|
|
453
|
-
}
|
|
454
|
-
const payload = request.payload;
|
|
455
|
-
const value = payload && typeof payload === "object"
|
|
456
|
-
? payload
|
|
457
|
-
: {};
|
|
458
|
-
const item = await resource.create(value, ctx);
|
|
459
|
-
return { ok: true, id, environment: ctx.env, item };
|
|
460
|
-
});
|
|
461
|
-
const deleteResourceHandler = async (request) => withEnvContext(request, async (ctx) => {
|
|
462
|
-
const id = request.params?.id;
|
|
463
|
-
const rowId = request.params?.rowId;
|
|
464
|
-
if (!id || !rowId) {
|
|
465
|
-
throw new VertebrNotFoundError("Missing resource or row id");
|
|
466
|
-
}
|
|
467
|
-
const resource = findResource(id);
|
|
468
|
-
if (!resource.remove) {
|
|
469
|
-
throw new VertebrNotFoundError(`Resource "${id}" cannot delete`);
|
|
470
|
-
}
|
|
471
|
-
await resource.remove(rowId, ctx);
|
|
472
|
-
return { ok: true, id, rowId, environment: ctx.env };
|
|
473
|
-
});
|
|
474
|
-
const resourceActionHandler = async (request) => withEnvContext(request, async (ctx) => {
|
|
475
|
-
const id = request.params?.id;
|
|
476
|
-
const rowId = request.params?.rowId;
|
|
477
|
-
const actionId = request.params?.actionId;
|
|
478
|
-
if (!id || !rowId || !actionId) {
|
|
479
|
-
throw new VertebrNotFoundError("Missing resource, row, or action id");
|
|
480
|
-
}
|
|
481
|
-
const resource = findResource(id);
|
|
482
|
-
const action = resource.actions?.[actionId];
|
|
483
|
-
if (!action) {
|
|
484
|
-
throw new VertebrNotFoundError(`Action "${actionId}" not found`);
|
|
485
|
-
}
|
|
486
|
-
const row = resource.get
|
|
487
|
-
? await resource.get(rowId, ctx)
|
|
488
|
-
: (await (resource.list?.(ctx) ?? [])).find((item) => String(item.id ?? "") === rowId ||
|
|
489
|
-
String(item.slug ?? "") === rowId ||
|
|
490
|
-
String(item.uid ?? "") === rowId ||
|
|
491
|
-
String(item.email ?? "") === rowId);
|
|
492
|
-
if (!row) {
|
|
493
|
-
throw new VertebrNotFoundError(`Row "${rowId}" not found`);
|
|
494
|
-
}
|
|
495
|
-
const payload = request.payload;
|
|
496
|
-
const input = payload && typeof payload === "object"
|
|
497
|
-
? payload
|
|
498
|
-
: undefined;
|
|
499
|
-
const result = await action.run(row, input, ctx);
|
|
500
|
-
return { ok: true, id, rowId, actionId, environment: ctx.env, result };
|
|
501
|
-
});
|
|
502
|
-
const metricsHandler = async (request) => withEnvContext(request, async (ctx) => {
|
|
503
|
-
if (!metricsDef) {
|
|
504
|
-
return {
|
|
505
|
-
ok: true,
|
|
506
|
-
id: "overview",
|
|
507
|
-
label: "Overview",
|
|
508
|
-
environment: ctx.env,
|
|
509
|
-
metrics: [],
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
const metrics = await metricsDef.load(ctx);
|
|
513
|
-
return {
|
|
514
|
-
ok: true,
|
|
515
|
-
id: metricsDef.id ?? "overview",
|
|
516
|
-
label: metricsDef.label ?? "Overview",
|
|
517
|
-
environment: ctx.env,
|
|
518
|
-
metrics,
|
|
519
|
-
};
|
|
520
|
-
});
|
|
521
|
-
const requireGallery = () => {
|
|
522
|
-
if (!galleryDef) {
|
|
523
|
-
throw new VertebrNotFoundError("Gallery is not configured");
|
|
524
|
-
}
|
|
525
|
-
return galleryDef;
|
|
526
|
-
};
|
|
527
|
-
const runGalleryUpload = async (file, ctx) => {
|
|
528
|
-
const gallery = requireGallery();
|
|
529
|
-
const accept = gallery.accept?.length
|
|
530
|
-
? gallery.accept
|
|
531
|
-
: DEFAULT_UPLOAD_ACCEPT;
|
|
532
|
-
const maxBytes = gallery.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES;
|
|
533
|
-
if (file.size > maxBytes) {
|
|
534
|
-
throw new VertebrBadRequestError(`File exceeds max size of ${maxBytes} bytes`);
|
|
535
|
-
}
|
|
536
|
-
if (!mimeAllowed(file.type, accept)) {
|
|
537
|
-
throw new VertebrBadRequestError(`File type "${file.type}" is not allowed`);
|
|
538
|
-
}
|
|
539
|
-
const item = await gallery.upload(file, ctx);
|
|
540
|
-
if (!item?.id || !item?.url) {
|
|
541
|
-
throw new VertebrBadRequestError("Gallery upload must return an item with id and url");
|
|
542
|
-
}
|
|
543
|
-
return item;
|
|
544
|
-
};
|
|
545
|
-
const listGalleryHandler = async (request) => withEnvContext(request, async (ctx) => {
|
|
546
|
-
const gallery = requireGallery();
|
|
547
|
-
const items = await gallery.list(ctx);
|
|
548
|
-
return {
|
|
549
|
-
ok: true,
|
|
550
|
-
id: gallery.id ?? "gallery",
|
|
551
|
-
label: gallery.label ?? "Gallery",
|
|
552
|
-
environment: ctx.env,
|
|
553
|
-
accept: gallery.accept?.length
|
|
554
|
-
? gallery.accept
|
|
555
|
-
: DEFAULT_UPLOAD_ACCEPT,
|
|
556
|
-
maxBytes: gallery.maxBytes ?? DEFAULT_UPLOAD_MAX_BYTES,
|
|
557
|
-
canDelete: Boolean(gallery.remove),
|
|
558
|
-
items,
|
|
559
|
-
};
|
|
560
|
-
});
|
|
561
|
-
const uploadGalleryFileHandler = async (request) => withEnvContext(request, async (ctx) => {
|
|
562
|
-
const file = decodeUploadPayload(request.payload);
|
|
563
|
-
const item = await runGalleryUpload(file, ctx);
|
|
564
|
-
return { ok: true, environment: ctx.env, item };
|
|
565
|
-
});
|
|
566
|
-
const uploadGalleryFilesHandler = async (request) => withEnvContext(request, async (ctx) => {
|
|
567
|
-
const payload = request.payload;
|
|
568
|
-
const list = payload &&
|
|
569
|
-
typeof payload === "object" &&
|
|
570
|
-
Array.isArray(payload.files)
|
|
571
|
-
? payload.files
|
|
572
|
-
: null;
|
|
573
|
-
if (!list?.length) {
|
|
574
|
-
throw new VertebrBadRequestError("Provide files: [] for bulk upload");
|
|
575
|
-
}
|
|
576
|
-
if (list.length > 40) {
|
|
577
|
-
throw new VertebrBadRequestError("Bulk upload is limited to 40 files");
|
|
578
|
-
}
|
|
579
|
-
const items = [];
|
|
580
|
-
for (const entry of list) {
|
|
581
|
-
items.push(await runGalleryUpload(decodeUploadPayload(entry), ctx));
|
|
582
|
-
}
|
|
583
|
-
return { ok: true, environment: ctx.env, items };
|
|
584
|
-
});
|
|
585
|
-
const deleteGalleryItemHandler = async (request) => withEnvContext(request, async (ctx) => {
|
|
586
|
-
const gallery = requireGallery();
|
|
587
|
-
const itemId = request.params?.itemId;
|
|
588
|
-
if (!itemId) {
|
|
589
|
-
throw new VertebrNotFoundError("Missing gallery item id");
|
|
590
|
-
}
|
|
591
|
-
if (!gallery.remove) {
|
|
592
|
-
throw new VertebrNotFoundError("Gallery delete is not configured");
|
|
593
|
-
}
|
|
594
|
-
await gallery.remove(itemId, ctx);
|
|
595
|
-
return { ok: true, id: itemId, environment: ctx.env };
|
|
596
|
-
});
|
|
597
|
-
const getConfigHandler = async (request) => withEnvContext(request, async (ctx) => {
|
|
598
|
-
const id = request.params?.id;
|
|
599
|
-
if (!id) {
|
|
600
|
-
throw new VertebrNotFoundError("Missing config id");
|
|
601
|
-
}
|
|
602
|
-
const config = findConfig(id);
|
|
603
|
-
const value = await config.get(ctx);
|
|
604
|
-
return {
|
|
605
|
-
id: config.id,
|
|
606
|
-
label: config.label,
|
|
607
|
-
schema: config.schema,
|
|
608
|
-
environment: ctx.env,
|
|
609
|
-
value,
|
|
610
|
-
};
|
|
611
|
-
});
|
|
612
|
-
const setConfigHandler = async (request) => withEnvContext(request, async (ctx) => {
|
|
613
|
-
const id = request.params?.id;
|
|
614
|
-
if (!id) {
|
|
615
|
-
throw new VertebrNotFoundError("Missing config id");
|
|
616
|
-
}
|
|
617
|
-
const config = findConfig(id);
|
|
618
|
-
const payload = request.payload;
|
|
619
|
-
const value = payload && typeof payload === "object"
|
|
620
|
-
? payload
|
|
621
|
-
: {};
|
|
622
|
-
await config.set(value, ctx);
|
|
623
|
-
return {
|
|
624
|
-
ok: true,
|
|
625
|
-
id: config.id,
|
|
626
|
-
environment: ctx.env,
|
|
627
|
-
value: await config.get(ctx),
|
|
628
|
-
};
|
|
629
|
-
});
|
|
630
|
-
const publicControlsHandler = async (request) => withPublicEnvContext(request, async (ctx) => {
|
|
631
|
-
const entries = await Promise.all([...controlMeta.entries()]
|
|
632
|
-
.filter(([, meta]) => meta.public)
|
|
633
|
-
.map(async ([id]) => {
|
|
634
|
-
const value = await readConfigValue(id, ctx.env);
|
|
635
|
-
return [id, value];
|
|
636
|
-
}));
|
|
637
|
-
return {
|
|
638
|
-
ok: true,
|
|
639
|
-
environment: ctx.env,
|
|
640
|
-
configs: Object.fromEntries(entries),
|
|
641
|
-
};
|
|
642
|
-
});
|
|
643
|
-
const publicConfigHandler = async (request) => withPublicEnvContext(request, async (ctx) => {
|
|
644
|
-
const id = request.params?.id;
|
|
645
|
-
if (!id) {
|
|
646
|
-
throw new VertebrNotFoundError("Missing config id");
|
|
647
|
-
}
|
|
648
|
-
const meta = controlMeta.get(id);
|
|
649
|
-
if (!meta?.public) {
|
|
650
|
-
throw new VertebrNotFoundError(`Public control "${id}" not found`);
|
|
651
|
-
}
|
|
652
|
-
const config = findConfig(id);
|
|
653
|
-
return {
|
|
654
|
-
ok: true,
|
|
655
|
-
id,
|
|
656
|
-
label: config.label,
|
|
657
|
-
environment: ctx.env,
|
|
658
|
-
value: await readConfigValue(id, ctx.env),
|
|
659
|
-
};
|
|
660
|
-
});
|
|
661
|
-
const publicResourceHandler = async (request) => withPublicEnvContext(request, async (ctx) => {
|
|
662
|
-
const id = request.params?.id;
|
|
663
|
-
if (!id) {
|
|
664
|
-
throw new VertebrNotFoundError("Missing resource id");
|
|
665
|
-
}
|
|
666
|
-
const resource = findResource(id);
|
|
667
|
-
if (!resource.public) {
|
|
668
|
-
throw new VertebrNotFoundError(`Public resource "${id}" not found`);
|
|
669
|
-
}
|
|
670
|
-
const listed = resource.list ? await resource.list(ctx) : [];
|
|
671
|
-
const items = listed.filter((row) => {
|
|
672
|
-
if (!row || typeof row !== "object")
|
|
673
|
-
return false;
|
|
674
|
-
return row.active !== false;
|
|
675
|
-
});
|
|
676
|
-
return {
|
|
677
|
-
ok: true,
|
|
678
|
-
id: resource.id,
|
|
679
|
-
environment: ctx.env,
|
|
680
|
-
items,
|
|
681
|
-
};
|
|
682
|
-
});
|
|
683
|
-
const admin = {
|
|
684
|
-
resource(def) {
|
|
685
|
-
resources.push(def);
|
|
686
|
-
return admin;
|
|
687
|
-
},
|
|
688
|
-
config(def) {
|
|
689
|
-
configs.push({
|
|
690
|
-
...def,
|
|
691
|
-
section: def.section === "page" ? "page" : "config",
|
|
692
|
-
});
|
|
693
|
-
return admin;
|
|
694
|
-
},
|
|
695
|
-
control(def) {
|
|
696
|
-
controlMeta.set(def.id, {
|
|
697
|
-
public: Boolean(def.public),
|
|
698
|
-
store: def.store,
|
|
699
|
-
defaults: def.defaults,
|
|
700
|
-
schema: def.schema,
|
|
701
|
-
});
|
|
702
|
-
configs.push({
|
|
703
|
-
id: def.id,
|
|
704
|
-
label: def.label,
|
|
705
|
-
schema: def.schema,
|
|
706
|
-
section: def.section === "page" ? "page" : "config",
|
|
707
|
-
get: async (ctx) => readConfigValue(def.id, ctx.env),
|
|
708
|
-
set: async (value, ctx) => {
|
|
709
|
-
const next = (0, store_1.mergeConfigValue)(resolveDefaults(def.schema, def.defaults, ctx.env), await def.store.get(def.id, ctx.env), value);
|
|
710
|
-
await def.store.set(def.id, ctx.env, next);
|
|
711
|
-
},
|
|
712
|
-
});
|
|
713
|
-
return admin;
|
|
714
|
-
},
|
|
715
|
-
page(def) {
|
|
716
|
-
return admin.control({ ...def, section: "page" });
|
|
717
|
-
},
|
|
718
|
-
layout(def) {
|
|
719
|
-
const slotIds = def.slots.map((slot) => slot.id);
|
|
720
|
-
const order = def.defaultOrder?.filter((id) => slotIds.includes(id)) ?? slotIds;
|
|
721
|
-
const optionLabels = Object.fromEntries(def.slots.map((slot) => [slot.id, slot.label]));
|
|
722
|
-
const presets = def.presets ?? [];
|
|
723
|
-
const presetIds = presets.map((preset) => preset.id);
|
|
724
|
-
const presetLabels = Object.fromEntries(presets.map((preset) => [preset.id, preset.label]));
|
|
725
|
-
return admin.control({
|
|
726
|
-
id: def.id,
|
|
727
|
-
label: def.label,
|
|
728
|
-
public: def.public,
|
|
729
|
-
store: def.store,
|
|
730
|
-
section: def.section === "config" ? "config" : "page",
|
|
731
|
-
defaults: {
|
|
732
|
-
...(presetIds[0] ? { preset: presetIds[0] } : {}),
|
|
733
|
-
blocks: order,
|
|
734
|
-
},
|
|
735
|
-
schema: {
|
|
736
|
-
...(presetIds.length
|
|
737
|
-
? {
|
|
738
|
-
preset: {
|
|
739
|
-
type: "enum",
|
|
740
|
-
label: "Preset",
|
|
741
|
-
options: presetIds,
|
|
742
|
-
optionLabels: presetLabels,
|
|
743
|
-
default: presetIds[0],
|
|
744
|
-
group: "Layout",
|
|
745
|
-
},
|
|
746
|
-
}
|
|
747
|
-
: {}),
|
|
748
|
-
blocks: {
|
|
749
|
-
type: "enum[]",
|
|
750
|
-
label: "Blocks",
|
|
751
|
-
options: slotIds,
|
|
752
|
-
optionLabels,
|
|
753
|
-
default: order,
|
|
754
|
-
group: "Layout",
|
|
755
|
-
},
|
|
756
|
-
},
|
|
757
|
-
});
|
|
758
|
-
},
|
|
759
|
-
metrics(def) {
|
|
760
|
-
metricsDef = def;
|
|
761
|
-
return admin;
|
|
762
|
-
},
|
|
763
|
-
gallery(def) {
|
|
764
|
-
galleryDef = def;
|
|
765
|
-
return admin;
|
|
766
|
-
},
|
|
767
|
-
async readConfig(id, env) {
|
|
768
|
-
if (!environments.some((item) => item.id === env)) {
|
|
769
|
-
throw new VertebrEnvError(`Unknown environment "${env}"`);
|
|
770
|
-
}
|
|
771
|
-
const run = () => readConfigValue(id, env);
|
|
772
|
-
if (options.onEnvironment) {
|
|
773
|
-
return options.onEnvironment(env, run);
|
|
774
|
-
}
|
|
775
|
-
return run();
|
|
776
|
-
},
|
|
777
|
-
use(connector) {
|
|
778
|
-
connector.register(admin);
|
|
779
|
-
return admin;
|
|
780
|
-
},
|
|
781
|
-
routes() {
|
|
782
|
-
return [
|
|
783
|
-
{
|
|
784
|
-
method: "GET",
|
|
785
|
-
path: `${apiBase}/bootstrap`,
|
|
786
|
-
handler: bootstrapHandler,
|
|
787
|
-
},
|
|
788
|
-
{
|
|
789
|
-
method: "GET",
|
|
790
|
-
path: `${apiBase}/public/controls`,
|
|
791
|
-
handler: publicControlsHandler,
|
|
792
|
-
},
|
|
793
|
-
{
|
|
794
|
-
method: "GET",
|
|
795
|
-
path: `${apiBase}/public/configs/{id}`,
|
|
796
|
-
handler: publicConfigHandler,
|
|
797
|
-
},
|
|
798
|
-
{
|
|
799
|
-
method: "GET",
|
|
800
|
-
path: `${apiBase}/public/resources/{id}`,
|
|
801
|
-
handler: publicResourceHandler,
|
|
802
|
-
},
|
|
803
|
-
{
|
|
804
|
-
method: "GET",
|
|
805
|
-
path: `${apiBase}/metrics`,
|
|
806
|
-
handler: metricsHandler,
|
|
807
|
-
},
|
|
808
|
-
{
|
|
809
|
-
method: "GET",
|
|
810
|
-
path: `${apiBase}/gallery`,
|
|
811
|
-
handler: listGalleryHandler,
|
|
812
|
-
},
|
|
813
|
-
{
|
|
814
|
-
method: "POST",
|
|
815
|
-
path: `${apiBase}/gallery/upload`,
|
|
816
|
-
handler: uploadGalleryFileHandler,
|
|
817
|
-
},
|
|
818
|
-
{
|
|
819
|
-
method: "POST",
|
|
820
|
-
path: `${apiBase}/gallery/upload/bulk`,
|
|
821
|
-
handler: uploadGalleryFilesHandler,
|
|
822
|
-
},
|
|
823
|
-
{
|
|
824
|
-
method: "DELETE",
|
|
825
|
-
path: `${apiBase}/gallery/{itemId}`,
|
|
826
|
-
handler: deleteGalleryItemHandler,
|
|
827
|
-
},
|
|
828
|
-
{ method: "GET", path: apiBase, handler: catalogHandler },
|
|
829
|
-
{
|
|
830
|
-
method: "GET",
|
|
831
|
-
path: `${apiBase}/resources/{id}`,
|
|
832
|
-
handler: listResourceHandler,
|
|
833
|
-
},
|
|
834
|
-
{
|
|
835
|
-
method: "POST",
|
|
836
|
-
path: `${apiBase}/resources/{id}`,
|
|
837
|
-
handler: createResourceHandler,
|
|
838
|
-
},
|
|
839
|
-
{
|
|
840
|
-
method: "PUT",
|
|
841
|
-
path: `${apiBase}/resources/{id}/{rowId}`,
|
|
842
|
-
handler: updateResourceHandler,
|
|
843
|
-
},
|
|
844
|
-
{
|
|
845
|
-
method: "DELETE",
|
|
846
|
-
path: `${apiBase}/resources/{id}/{rowId}`,
|
|
847
|
-
handler: deleteResourceHandler,
|
|
848
|
-
},
|
|
849
|
-
{
|
|
850
|
-
method: "POST",
|
|
851
|
-
path: `${apiBase}/resources/{id}/{rowId}/actions/{actionId}`,
|
|
852
|
-
handler: resourceActionHandler,
|
|
853
|
-
},
|
|
854
|
-
{
|
|
855
|
-
method: "POST",
|
|
856
|
-
path: `${apiBase}/resources/{id}/uploads/{field}`,
|
|
857
|
-
handler: uploadResourceFileHandler,
|
|
858
|
-
},
|
|
859
|
-
{
|
|
860
|
-
method: "POST",
|
|
861
|
-
path: `${apiBase}/resources/{id}/uploads/{field}/bulk`,
|
|
862
|
-
handler: uploadResourceFilesHandler,
|
|
863
|
-
},
|
|
864
|
-
{
|
|
865
|
-
method: "GET",
|
|
866
|
-
path: `${apiBase}/configs/{id}`,
|
|
867
|
-
handler: getConfigHandler,
|
|
868
|
-
},
|
|
869
|
-
{
|
|
870
|
-
method: "PUT",
|
|
871
|
-
path: `${apiBase}/configs/{id}`,
|
|
872
|
-
handler: setConfigHandler,
|
|
873
|
-
},
|
|
874
|
-
];
|
|
875
|
-
},
|
|
876
|
-
hapiRoutes() {
|
|
877
|
-
const apiRoutes = admin.routes().map((route) => ({
|
|
878
|
-
method: route.method,
|
|
879
|
-
path: route.path,
|
|
880
|
-
options: { auth: false },
|
|
881
|
-
handler: async (request, h) => {
|
|
882
|
-
const toolkit = h;
|
|
883
|
-
try {
|
|
884
|
-
return await route.handler(request);
|
|
885
|
-
}
|
|
886
|
-
catch (error) {
|
|
887
|
-
if (error instanceof VertebrAuthError ||
|
|
888
|
-
error instanceof VertebrNotFoundError ||
|
|
889
|
-
error instanceof VertebrEnvError ||
|
|
890
|
-
error instanceof VertebrBadRequestError) {
|
|
891
|
-
return toolkit
|
|
892
|
-
.response({ ok: false, error: error.message })
|
|
893
|
-
.code(error.statusCode);
|
|
894
|
-
}
|
|
895
|
-
throw error;
|
|
896
|
-
}
|
|
897
|
-
},
|
|
898
|
-
}));
|
|
899
|
-
if (!staticDir) {
|
|
900
|
-
return apiRoutes;
|
|
901
|
-
}
|
|
902
|
-
const spaHandler = async (request, h) => {
|
|
903
|
-
const toolkit = h;
|
|
904
|
-
const req = request;
|
|
905
|
-
const reqPath = req.path ?? req.url?.pathname ?? "";
|
|
906
|
-
const relative = reqPath.slice(basePath.length).replace(/^\//, "");
|
|
907
|
-
if (relative === "api" || relative.startsWith("api/")) {
|
|
908
|
-
return toolkit.response({ ok: false, error: "Not found" }).code(404);
|
|
909
|
-
}
|
|
910
|
-
const safeRelative = relative.replace(/\.\./g, "");
|
|
911
|
-
const candidate = node_path_1.default.join(staticDir, safeRelative === "" ? "index.html" : safeRelative);
|
|
912
|
-
const resolved = node_path_1.default.resolve(candidate);
|
|
913
|
-
if (!resolved.startsWith(node_path_1.default.resolve(staticDir))) {
|
|
914
|
-
return toolkit.response({ ok: false, error: "Forbidden" }).code(403);
|
|
915
|
-
}
|
|
916
|
-
let filePath = resolved;
|
|
917
|
-
if (!node_fs_1.default.existsSync(filePath) || node_fs_1.default.statSync(filePath).isDirectory()) {
|
|
918
|
-
filePath = node_path_1.default.join(staticDir, "index.html");
|
|
919
|
-
}
|
|
920
|
-
const body = node_fs_1.default.readFileSync(filePath);
|
|
921
|
-
return toolkit.response(body).type(contentTypeFor(filePath)).code(200);
|
|
922
|
-
};
|
|
923
|
-
const staticRoutes = [
|
|
924
|
-
{
|
|
925
|
-
method: "GET",
|
|
926
|
-
path: basePath,
|
|
927
|
-
options: { auth: false },
|
|
928
|
-
handler: spaHandler,
|
|
929
|
-
},
|
|
930
|
-
{
|
|
931
|
-
method: "GET",
|
|
932
|
-
path: `${basePath}/{path*}`,
|
|
933
|
-
options: { auth: false },
|
|
934
|
-
handler: spaHandler,
|
|
935
|
-
},
|
|
936
|
-
];
|
|
937
|
-
return [...apiRoutes, ...staticRoutes];
|
|
938
|
-
},
|
|
939
|
-
resources: () => [...resources],
|
|
940
|
-
configs: () => [...configs],
|
|
941
|
-
environments: () => [...environments],
|
|
942
|
-
};
|
|
943
|
-
return admin;
|
|
944
|
-
}
|
|
945
|
-
/** Example connector shape — Stripe implementation TBD. */
|
|
946
|
-
function stripeConnector(options) {
|
|
947
|
-
return {
|
|
948
|
-
id: options.id ?? "stripe",
|
|
949
|
-
register(admin) {
|
|
950
|
-
admin.resource({
|
|
951
|
-
id: "stripe-sessions",
|
|
952
|
-
label: "Stripe sessions",
|
|
953
|
-
schema: {
|
|
954
|
-
id: { type: "string", readOnly: true },
|
|
955
|
-
status: { type: "string", readOnly: true },
|
|
956
|
-
amountTotal: { type: "number", readOnly: true },
|
|
957
|
-
},
|
|
958
|
-
list: async () => [],
|
|
959
|
-
permissions: { create: false, update: false, delete: false },
|
|
960
|
-
});
|
|
961
|
-
},
|
|
962
|
-
};
|
|
963
|
-
}
|
|
7
|
+
exports.whereEq = exports.postgresJsonbTableDDL = exports.postgresAdapter = exports.mongoAdapter = exports.memoryAdapter = exports.firestoreAdapter = exports.databaseConnector = exports.adapterList = exports.adapterCount = exports.adapterConfigStore = exports.toMailDiscountOptions = exports.stripePaymentsProvider = exports.paymentsConnector = exports.stripeConnector = exports.createAdmin = exports.VertebrNotFoundError = exports.VertebrEnvError = exports.VertebrBadRequestError = exports.VertebrAuthError = exports.resolveMetricsRange = exports.parseMetricsRange = exports.METRICS_RANGES = exports.VERTEBR_ENV_HEADER = exports.mergeConfigValue = exports.memoryStore = void 0;
|
|
8
|
+
var store_1 = require("./store");
|
|
9
|
+
Object.defineProperty(exports, "memoryStore", { enumerable: true, get: function () { return store_1.memoryStore; } });
|
|
10
|
+
Object.defineProperty(exports, "mergeConfigValue", { enumerable: true, get: function () { return store_1.mergeConfigValue; } });
|
|
11
|
+
var constants_1 = require("./constants");
|
|
12
|
+
Object.defineProperty(exports, "VERTEBR_ENV_HEADER", { enumerable: true, get: function () { return constants_1.VERTEBR_ENV_HEADER; } });
|
|
13
|
+
var metricsRange_1 = require("./utils/metricsRange");
|
|
14
|
+
Object.defineProperty(exports, "METRICS_RANGES", { enumerable: true, get: function () { return metricsRange_1.METRICS_RANGES; } });
|
|
15
|
+
Object.defineProperty(exports, "parseMetricsRange", { enumerable: true, get: function () { return metricsRange_1.parseMetricsRange; } });
|
|
16
|
+
Object.defineProperty(exports, "resolveMetricsRange", { enumerable: true, get: function () { return metricsRange_1.resolveMetricsRange; } });
|
|
17
|
+
var errors_1 = require("./errors");
|
|
18
|
+
Object.defineProperty(exports, "VertebrAuthError", { enumerable: true, get: function () { return errors_1.VertebrAuthError; } });
|
|
19
|
+
Object.defineProperty(exports, "VertebrBadRequestError", { enumerable: true, get: function () { return errors_1.VertebrBadRequestError; } });
|
|
20
|
+
Object.defineProperty(exports, "VertebrEnvError", { enumerable: true, get: function () { return errors_1.VertebrEnvError; } });
|
|
21
|
+
Object.defineProperty(exports, "VertebrNotFoundError", { enumerable: true, get: function () { return errors_1.VertebrNotFoundError; } });
|
|
22
|
+
var createAdmin_1 = require("./createAdmin");
|
|
23
|
+
Object.defineProperty(exports, "createAdmin", { enumerable: true, get: function () { return createAdmin_1.createAdmin; } });
|
|
24
|
+
var stripe_1 = require("./connectors/stripe");
|
|
25
|
+
Object.defineProperty(exports, "stripeConnector", { enumerable: true, get: function () { return stripe_1.stripeConnector; } });
|
|
26
|
+
var payments_1 = require("./connectors/payments");
|
|
27
|
+
Object.defineProperty(exports, "paymentsConnector", { enumerable: true, get: function () { return payments_1.paymentsConnector; } });
|
|
28
|
+
Object.defineProperty(exports, "stripePaymentsProvider", { enumerable: true, get: function () { return payments_1.stripePaymentsProvider; } });
|
|
29
|
+
Object.defineProperty(exports, "toMailDiscountOptions", { enumerable: true, get: function () { return payments_1.toMailDiscountOptions; } });
|
|
964
30
|
var database_1 = require("./database");
|
|
965
31
|
Object.defineProperty(exports, "adapterConfigStore", { enumerable: true, get: function () { return database_1.adapterConfigStore; } });
|
|
966
32
|
Object.defineProperty(exports, "adapterCount", { enumerable: true, get: function () { return database_1.adapterCount; } });
|