@dyrected/core 2.19.0 → 2.20.1
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/{app-CXF4MdJc.d.cts → app-YGoigpOH.d.cts} +182 -29
- package/dist/{app-CXF4MdJc.d.ts → app-YGoigpOH.d.ts} +182 -29
- package/dist/index.cjs +580 -24
- package/dist/index.d.cts +73 -5
- package/dist/index.d.ts +73 -5
- package/dist/index.js +560 -24
- package/dist/server.cjs +1173 -137
- package/dist/server.d.cts +79 -29
- package/dist/server.d.ts +79 -29
- package/dist/server.js +1164 -137
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -3011,6 +3011,14 @@ async function executeFieldAfterRead(fields, doc, user, db) {
|
|
|
3011
3011
|
|
|
3012
3012
|
// src/trash.ts
|
|
3013
3013
|
var TRASH_COLLECTION = "__trash";
|
|
3014
|
+
var INTERNAL_SYSTEM_COLLECTIONS = /* @__PURE__ */ new Set([
|
|
3015
|
+
"__audit",
|
|
3016
|
+
"__sessions",
|
|
3017
|
+
"__task_locks",
|
|
3018
|
+
"__trash",
|
|
3019
|
+
"__workflow_history",
|
|
3020
|
+
"__lifecycle_events"
|
|
3021
|
+
]);
|
|
3014
3022
|
var TRASH_COLLECTION_CONFIG = {
|
|
3015
3023
|
slug: TRASH_COLLECTION,
|
|
3016
3024
|
labels: { singular: "Trash entry", plural: "Trash entries" },
|
|
@@ -3102,7 +3110,7 @@ function assertValidTrashInConfig(config, source = "config") {
|
|
|
3102
3110
|
if (col.slug === "trash") {
|
|
3103
3111
|
throw new Error(`Collection slug "trash" is reserved by Dyrected for the trash system.`);
|
|
3104
3112
|
}
|
|
3105
|
-
const isSystem = col.slug
|
|
3113
|
+
const isSystem = INTERNAL_SYSTEM_COLLECTIONS.has(col.slug);
|
|
3106
3114
|
if (isSystem && col.trash) {
|
|
3107
3115
|
throw new Error(`System collection "${col.slug}" cannot enable trash.`);
|
|
3108
3116
|
}
|
|
@@ -3481,7 +3489,7 @@ function createTaskRunner(config) {
|
|
|
3481
3489
|
throw error;
|
|
3482
3490
|
}
|
|
3483
3491
|
}
|
|
3484
|
-
async function execute(task,
|
|
3492
|
+
async function execute(task, row2, scheduledFor) {
|
|
3485
3493
|
const started = Date.now();
|
|
3486
3494
|
const lockMs = (task.lockTimeout ?? DEFAULT_LOCK_TIMEOUT_SECONDS) * 1e3;
|
|
3487
3495
|
const token = `${instanceId}:${started}`;
|
|
@@ -3489,7 +3497,7 @@ function createTaskRunner(config) {
|
|
|
3489
3497
|
id: { equals: task.name },
|
|
3490
3498
|
lockedUntil: { less_than: started }
|
|
3491
3499
|
};
|
|
3492
|
-
if (scheduledFor) where.lastScheduledFor = { equals:
|
|
3500
|
+
if (scheduledFor) where.lastScheduledFor = { equals: row2.lastScheduledFor };
|
|
3493
3501
|
const claimed = await db.update({
|
|
3494
3502
|
collection: TASK_LOCKS_COLLECTION,
|
|
3495
3503
|
where,
|
|
@@ -3550,21 +3558,21 @@ function createTaskRunner(config) {
|
|
|
3550
3558
|
async runTask(name) {
|
|
3551
3559
|
const task = tasks.get(name);
|
|
3552
3560
|
if (!task) throw new Error(`Unknown task "${name}".`);
|
|
3553
|
-
const
|
|
3554
|
-
return track(execute(task,
|
|
3561
|
+
const row2 = await ensureLockRow(name, /* @__PURE__ */ new Date());
|
|
3562
|
+
return track(execute(task, row2, void 0));
|
|
3555
3563
|
},
|
|
3556
3564
|
async runDue(now2 = /* @__PURE__ */ new Date()) {
|
|
3557
3565
|
const results = [];
|
|
3558
3566
|
for (const task of tasks.values()) {
|
|
3559
3567
|
if (!task.schedule) continue;
|
|
3560
|
-
const
|
|
3561
|
-
const reference = new Date(String(
|
|
3568
|
+
const row2 = await ensureLockRow(task.name, now2);
|
|
3569
|
+
const reference = new Date(String(row2.lastScheduledFor));
|
|
3562
3570
|
const due = Number.isNaN(reference.getTime()) ? true : nextCronRun(task.schedule, reference).getTime() <= now2.getTime();
|
|
3563
3571
|
if (!due) {
|
|
3564
3572
|
results.push({ name: task.name, status: "skipped", reason: "not-due", durationMs: 0 });
|
|
3565
3573
|
continue;
|
|
3566
3574
|
}
|
|
3567
|
-
results.push(await track(execute(task,
|
|
3575
|
+
results.push(await track(execute(task, row2, floorToMinute(now2))));
|
|
3568
3576
|
}
|
|
3569
3577
|
return results;
|
|
3570
3578
|
},
|
|
@@ -4079,13 +4087,34 @@ function getAdminRoleForCollection(collection) {
|
|
|
4079
4087
|
}
|
|
4080
4088
|
return "admin";
|
|
4081
4089
|
}
|
|
4090
|
+
var STANDARD_ADMIN_ROLES = /* @__PURE__ */ new Set([
|
|
4091
|
+
"admin",
|
|
4092
|
+
"super_admin",
|
|
4093
|
+
"superadmin",
|
|
4094
|
+
"super admin",
|
|
4095
|
+
"super-admin",
|
|
4096
|
+
"owner"
|
|
4097
|
+
]);
|
|
4082
4098
|
function isUserAdmin(user, collection) {
|
|
4083
4099
|
if (!user) return false;
|
|
4084
|
-
const
|
|
4085
|
-
if (
|
|
4100
|
+
const rawRoles = Array.isArray(user.roles) ? user.roles : typeof user.roles === "string" ? [user.roles] : Array.isArray(user.role) ? user.role : typeof user.role === "string" ? [user.role] : [];
|
|
4101
|
+
if (rawRoles.length === 0) return false;
|
|
4102
|
+
const roles = rawRoles.filter((r) => typeof r === "string");
|
|
4086
4103
|
const configuredRole = getAdminRoleForCollection(collection);
|
|
4087
|
-
|
|
4088
|
-
|
|
4104
|
+
for (const r of roles) {
|
|
4105
|
+
if (r === configuredRole || r.toLowerCase() === configuredRole.toLowerCase()) {
|
|
4106
|
+
return true;
|
|
4107
|
+
}
|
|
4108
|
+
const normalized = r.trim().toLowerCase();
|
|
4109
|
+
if (STANDARD_ADMIN_ROLES.has(normalized)) {
|
|
4110
|
+
return true;
|
|
4111
|
+
}
|
|
4112
|
+
const stripped = normalized.replace(/[\s_-]+/g, "");
|
|
4113
|
+
if (stripped === "admin" || stripped === "superadmin" || stripped === "owner") {
|
|
4114
|
+
return true;
|
|
4115
|
+
}
|
|
4116
|
+
}
|
|
4117
|
+
return false;
|
|
4089
4118
|
}
|
|
4090
4119
|
|
|
4091
4120
|
// src/utils/block-references.ts
|
|
@@ -4308,6 +4337,245 @@ function generateDocumentId(collectionOrSlug) {
|
|
|
4308
4337
|
return generateId({ prefix, type });
|
|
4309
4338
|
}
|
|
4310
4339
|
|
|
4340
|
+
// src/services/email-template.ts
|
|
4341
|
+
var emailTokens = {
|
|
4342
|
+
colors: {
|
|
4343
|
+
canvas: "#f6f7f2",
|
|
4344
|
+
surface: "#ffffff",
|
|
4345
|
+
text: "#171717",
|
|
4346
|
+
muted: "#62665b",
|
|
4347
|
+
subtle: "#8a8f82",
|
|
4348
|
+
border: "#dde0d7",
|
|
4349
|
+
accent: "#b6ff2e",
|
|
4350
|
+
code: "#f1f3ec",
|
|
4351
|
+
codeBorder: "#e2e5dc",
|
|
4352
|
+
dangerSurface: "#fff2f0",
|
|
4353
|
+
dangerBorder: "#ffc9c2",
|
|
4354
|
+
dangerText: "#9f251b"
|
|
4355
|
+
},
|
|
4356
|
+
font: "Arial, 'Helvetica Neue', Helvetica, sans-serif",
|
|
4357
|
+
mono: "'Courier New', Courier, monospace",
|
|
4358
|
+
radius: { card: "12px", control: "6px" },
|
|
4359
|
+
width: "600px"
|
|
4360
|
+
};
|
|
4361
|
+
function escapeHtml(value) {
|
|
4362
|
+
return value.replace(
|
|
4363
|
+
/[&<>'"]/g,
|
|
4364
|
+
(character) => ({
|
|
4365
|
+
"&": "&",
|
|
4366
|
+
"<": "<",
|
|
4367
|
+
">": ">",
|
|
4368
|
+
"'": "'",
|
|
4369
|
+
'"': """
|
|
4370
|
+
})[character]
|
|
4371
|
+
);
|
|
4372
|
+
}
|
|
4373
|
+
function safeHttpUrl(value) {
|
|
4374
|
+
if (value.startsWith("{{") && value.endsWith("}}")) {
|
|
4375
|
+
return value;
|
|
4376
|
+
}
|
|
4377
|
+
try {
|
|
4378
|
+
const url = new URL(value);
|
|
4379
|
+
return url.protocol === "https:" || url.protocol === "http:" ? escapeHtml(url.toString()) : void 0;
|
|
4380
|
+
} catch {
|
|
4381
|
+
return void 0;
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
4384
|
+
function heading(content) {
|
|
4385
|
+
return `<h1 style="margin:0;font-family:${emailTokens.font};font-size:24px;line-height:1.25;font-weight:700;color:${emailTokens.colors.text}">${escapeHtml(content)}</h1>`;
|
|
4386
|
+
}
|
|
4387
|
+
function paragraph(content, margin = "0 0 16px", size = "15px") {
|
|
4388
|
+
return `<p style="margin:${margin};font-family:${emailTokens.font};font-size:${size};line-height:1.6;color:${emailTokens.colors.muted}">${escapeHtml(content)}</p>`;
|
|
4389
|
+
}
|
|
4390
|
+
function sectionLabel(content) {
|
|
4391
|
+
return `<p style="margin:0 0 8px;font-family:${emailTokens.font};font-size:11px;line-height:1.4;font-weight:700;letter-spacing:0.1em;text-transform:uppercase;color:${emailTokens.colors.subtle}">${escapeHtml(content)}</p>`;
|
|
4392
|
+
}
|
|
4393
|
+
function divider() {
|
|
4394
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%"><tr><td style="height:1px;background:${emailTokens.colors.border};font-size:0;line-height:0"> </td></tr></table>`;
|
|
4395
|
+
}
|
|
4396
|
+
function spacer(height = 16) {
|
|
4397
|
+
return table(row(" ", `height:${height}px;font-size:0;line-height:0`));
|
|
4398
|
+
}
|
|
4399
|
+
function row(content, cellStyle = "") {
|
|
4400
|
+
return `<tr><td style="${cellStyle}">${content}</td></tr>`;
|
|
4401
|
+
}
|
|
4402
|
+
function table(content, style = "width:100%") {
|
|
4403
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="${style}">${content}</table>`;
|
|
4404
|
+
}
|
|
4405
|
+
function detailBox(content, monospace = false) {
|
|
4406
|
+
const font = monospace ? emailTokens.mono : emailTokens.font;
|
|
4407
|
+
return table(
|
|
4408
|
+
row(
|
|
4409
|
+
escapeHtml(content),
|
|
4410
|
+
`padding:14px 16px;font-family:${font};font-size:13px;line-height:1.5;font-weight:${monospace ? "400" : "700"};color:${emailTokens.colors.text};word-break:break-all`
|
|
4411
|
+
),
|
|
4412
|
+
`width:100%;background:${emailTokens.colors.code};border:1px solid ${emailTokens.colors.codeBorder};border-radius:${emailTokens.radius.control}`
|
|
4413
|
+
);
|
|
4414
|
+
}
|
|
4415
|
+
function eventList(events) {
|
|
4416
|
+
return table(
|
|
4417
|
+
events.map(
|
|
4418
|
+
({ label, value }) => row(
|
|
4419
|
+
`${sectionLabel(label)}${paragraph(value, "0")}`,
|
|
4420
|
+
`padding:12px 0;border-bottom:1px solid ${emailTokens.colors.border}`
|
|
4421
|
+
)
|
|
4422
|
+
).join("")
|
|
4423
|
+
);
|
|
4424
|
+
}
|
|
4425
|
+
function ctaButton(label, href) {
|
|
4426
|
+
const safeHref = safeHttpUrl(href);
|
|
4427
|
+
if (!safeHref) return "";
|
|
4428
|
+
return table(
|
|
4429
|
+
row(
|
|
4430
|
+
`<a href="${safeHref}" style="display:inline-block;padding:13px 22px;border-radius:${emailTokens.radius.control};background:${emailTokens.colors.accent};font-family:${emailTokens.font};font-size:14px;line-height:1.2;font-weight:700;color:${emailTokens.colors.text};text-decoration:none">${escapeHtml(label)}</a>`,
|
|
4431
|
+
"padding:8px 0 24px"
|
|
4432
|
+
),
|
|
4433
|
+
"width:auto"
|
|
4434
|
+
);
|
|
4435
|
+
}
|
|
4436
|
+
function safeLinkDetailBox(href) {
|
|
4437
|
+
const safeHref = safeHttpUrl(href);
|
|
4438
|
+
if (!safeHref) return "";
|
|
4439
|
+
return detailBox(safeHref, true);
|
|
4440
|
+
}
|
|
4441
|
+
function alertBox(content) {
|
|
4442
|
+
return table(
|
|
4443
|
+
row(
|
|
4444
|
+
escapeHtml(content),
|
|
4445
|
+
`padding:13px 16px;font-family:${emailTokens.font};font-size:13px;line-height:1.5;color:${emailTokens.colors.dangerText}`
|
|
4446
|
+
),
|
|
4447
|
+
`width:100%;background:${emailTokens.colors.dangerSurface};border:1px solid ${emailTokens.colors.dangerBorder};border-radius:${emailTokens.radius.control}`
|
|
4448
|
+
);
|
|
4449
|
+
}
|
|
4450
|
+
function resolveBlocks(input) {
|
|
4451
|
+
if (Array.isArray(input)) {
|
|
4452
|
+
const parts = [];
|
|
4453
|
+
for (const item of input) {
|
|
4454
|
+
if (Array.isArray(item)) {
|
|
4455
|
+
for (const sub of item) {
|
|
4456
|
+
if (typeof sub === "string" && sub.length > 0) parts.push(sub);
|
|
4457
|
+
}
|
|
4458
|
+
} else if (typeof item === "string" && item.length > 0) {
|
|
4459
|
+
parts.push(item);
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
return parts.join("\n");
|
|
4463
|
+
}
|
|
4464
|
+
return typeof input === "string" ? input : "";
|
|
4465
|
+
}
|
|
4466
|
+
function layout({ preheader, title, content, footer, siteName }) {
|
|
4467
|
+
const resolvedContent = resolveBlocks(content);
|
|
4468
|
+
const resolvedFooter = resolveBlocks(footer);
|
|
4469
|
+
return `<!doctype html>
|
|
4470
|
+
<html lang="en" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
|
|
4471
|
+
<head>
|
|
4472
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
4473
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4474
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
4475
|
+
<meta name="x-apple-disable-message-reformatting">
|
|
4476
|
+
<meta name="format-detection" content="telephone=no,address=no,email=no,date=no,url=no">
|
|
4477
|
+
<meta name="color-scheme" content="light">
|
|
4478
|
+
<meta name="supported-color-schemes" content="light">
|
|
4479
|
+
<title>${escapeHtml(title)}</title>
|
|
4480
|
+
</head>
|
|
4481
|
+
<body style="margin:0;padding:0;background-color:${emailTokens.colors.canvas};word-spacing:normal;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%">
|
|
4482
|
+
<div style="display:none;max-height:0;max-width:0;overflow:hidden;opacity:0;mso-hide:all;color:transparent;font-size:1px;line-height:1px">
|
|
4483
|
+
${escapeHtml(preheader)}​͏‌   ͏‌
|
|
4484
|
+
</div>
|
|
4485
|
+
<center lang="en" style="width:100%;background-color:${emailTokens.colors.canvas}">
|
|
4486
|
+
${table(
|
|
4487
|
+
row(
|
|
4488
|
+
table(
|
|
4489
|
+
row(" ", `height:5px;background:${emailTokens.colors.accent};font-size:0;line-height:0`) + row(`${sectionLabel(siteName ?? "Dyrected")}${heading(title)}`, "padding:30px 32px 24px") + row(resolvedContent, "padding:0 32px 32px") + row(
|
|
4490
|
+
`${divider()}${paragraph(resolvedFooter, "20px 0 6px")}${paragraph("Privacy: this message contains account-related information; please avoid forwarding it.", "10px 0px 0px 0px", "12px")}`,
|
|
4491
|
+
"padding:0 32px 28px"
|
|
4492
|
+
),
|
|
4493
|
+
`width:100%;max-width:${emailTokens.width};background:${emailTokens.colors.surface};border:1px solid ${emailTokens.colors.border};border-radius:${emailTokens.radius.card};overflow:hidden`
|
|
4494
|
+
),
|
|
4495
|
+
"padding:32px 12px"
|
|
4496
|
+
),
|
|
4497
|
+
`width:100%;background:${emailTokens.colors.canvas}`
|
|
4498
|
+
)}
|
|
4499
|
+
</center>
|
|
4500
|
+
</body>
|
|
4501
|
+
</html>`;
|
|
4502
|
+
}
|
|
4503
|
+
function getDefaultEmailTemplate(purpose, options) {
|
|
4504
|
+
const siteName = options?.siteName ?? "Dyrected";
|
|
4505
|
+
switch (purpose) {
|
|
4506
|
+
case "invite":
|
|
4507
|
+
return {
|
|
4508
|
+
subject: `You've been invited to ${siteName}`,
|
|
4509
|
+
rawHtml: layout({
|
|
4510
|
+
siteName,
|
|
4511
|
+
preheader: `You've been invited to join ${siteName}.`,
|
|
4512
|
+
title: "You've been invited",
|
|
4513
|
+
content: [
|
|
4514
|
+
paragraph("Use the invitation link below to create your account. The link expires in 7 days."),
|
|
4515
|
+
ctaButton("Accept invitation", "{{url}}"),
|
|
4516
|
+
sectionLabel("Invitation link"),
|
|
4517
|
+
safeLinkDetailBox("{{url}}"),
|
|
4518
|
+
paragraph("If the button does not work, copy and paste the link above into your browser.", "12px 0 0")
|
|
4519
|
+
],
|
|
4520
|
+
footer: "If you weren't expecting this invitation, you can safely ignore this email."
|
|
4521
|
+
})
|
|
4522
|
+
};
|
|
4523
|
+
case "resetPassword":
|
|
4524
|
+
return {
|
|
4525
|
+
subject: "Reset your password",
|
|
4526
|
+
rawHtml: layout({
|
|
4527
|
+
siteName,
|
|
4528
|
+
preheader: `Reset your ${siteName} password.`,
|
|
4529
|
+
title: "Reset your password",
|
|
4530
|
+
content: [
|
|
4531
|
+
paragraph("We received a request to reset your password. The reset link expires in 1 hour."),
|
|
4532
|
+
ctaButton("Reset password", "{{url}}"),
|
|
4533
|
+
sectionLabel("Reset link"),
|
|
4534
|
+
safeLinkDetailBox("{{url}}"),
|
|
4535
|
+
paragraph("If the button does not work, copy and paste the link above into your browser.", "12px 0 0")
|
|
4536
|
+
],
|
|
4537
|
+
footer: "If you didn't request a password reset, you can safely ignore this email."
|
|
4538
|
+
})
|
|
4539
|
+
};
|
|
4540
|
+
case "welcome":
|
|
4541
|
+
return {
|
|
4542
|
+
subject: `Welcome to ${siteName} \u2014 your account is ready`,
|
|
4543
|
+
rawHtml: layout({
|
|
4544
|
+
siteName,
|
|
4545
|
+
preheader: `Your ${siteName} account is ready.`,
|
|
4546
|
+
title: "Welcome \u2014 your account is ready",
|
|
4547
|
+
content: [paragraph("Your account has been created. You can now log in with:"), detailBox("{{email}}")],
|
|
4548
|
+
footer: "If you didn't create this account, you can safely ignore this email."
|
|
4549
|
+
})
|
|
4550
|
+
};
|
|
4551
|
+
case "passwordChanged":
|
|
4552
|
+
return {
|
|
4553
|
+
subject: "Your password has been changed",
|
|
4554
|
+
rawHtml: layout({
|
|
4555
|
+
siteName,
|
|
4556
|
+
preheader: `Your ${siteName} password was changed.`,
|
|
4557
|
+
title: "Password changed",
|
|
4558
|
+
content: [
|
|
4559
|
+
paragraph("The password for your account has been changed."),
|
|
4560
|
+
alertBox("If you did not make this change, please contact support or reset your password immediately.")
|
|
4561
|
+
],
|
|
4562
|
+
footer: "Security notice: this confirms a recent credential change."
|
|
4563
|
+
})
|
|
4564
|
+
};
|
|
4565
|
+
default:
|
|
4566
|
+
return {
|
|
4567
|
+
subject: `Notification from ${siteName}`,
|
|
4568
|
+
rawHtml: layout({
|
|
4569
|
+
siteName,
|
|
4570
|
+
preheader: `Account notification from ${siteName}.`,
|
|
4571
|
+
title: "Account Notification",
|
|
4572
|
+
content: [paragraph(`This is an account notification from ${siteName}.`)],
|
|
4573
|
+
footer: "If you have any questions, please reach out to our team."
|
|
4574
|
+
})
|
|
4575
|
+
};
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
|
|
4311
4579
|
// src/utils/config.ts
|
|
4312
4580
|
var AUDIT_COLLECTION_SLUG = "__audit";
|
|
4313
4581
|
var SYSTEM_FIELDS = [
|
|
@@ -4449,6 +4717,114 @@ var AUTH_SESSIONS_COLLECTION_CONFIG = {
|
|
|
4449
4717
|
},
|
|
4450
4718
|
admin: { hidden: true }
|
|
4451
4719
|
};
|
|
4720
|
+
var EMAIL_TEMPLATES_COLLECTION_SLUG = "__email_templates";
|
|
4721
|
+
var EMAIL_TEMPLATES_COLLECTION_CONFIG = {
|
|
4722
|
+
slug: EMAIL_TEMPLATES_COLLECTION_SLUG,
|
|
4723
|
+
labels: { singular: "Email Template", plural: "Email Templates" },
|
|
4724
|
+
detail: false,
|
|
4725
|
+
fields: [
|
|
4726
|
+
{
|
|
4727
|
+
name: "collectionSlug",
|
|
4728
|
+
type: "select",
|
|
4729
|
+
label: "Applies To",
|
|
4730
|
+
required: true,
|
|
4731
|
+
defaultValue: "*",
|
|
4732
|
+
options: [
|
|
4733
|
+
{ label: "Global (All Users & Collections)", value: "*" }
|
|
4734
|
+
],
|
|
4735
|
+
admin: {
|
|
4736
|
+
description: "Choose which user collection this template applies to, or 'Global' for all users."
|
|
4737
|
+
}
|
|
4738
|
+
},
|
|
4739
|
+
{
|
|
4740
|
+
name: "purpose",
|
|
4741
|
+
type: "select",
|
|
4742
|
+
label: "Purpose",
|
|
4743
|
+
required: true,
|
|
4744
|
+
options: [
|
|
4745
|
+
{ label: "Invite", value: "invite" },
|
|
4746
|
+
{ label: "Password Reset", value: "resetPassword" },
|
|
4747
|
+
{ label: "Welcome", value: "welcome" },
|
|
4748
|
+
{ label: "Password Changed", value: "passwordChanged" }
|
|
4749
|
+
],
|
|
4750
|
+
admin: { width: "50%" }
|
|
4751
|
+
},
|
|
4752
|
+
{
|
|
4753
|
+
name: "format",
|
|
4754
|
+
type: "select",
|
|
4755
|
+
label: "Format",
|
|
4756
|
+
required: true,
|
|
4757
|
+
defaultValue: "html",
|
|
4758
|
+
options: [
|
|
4759
|
+
{ label: "HTML", value: "html" },
|
|
4760
|
+
{ label: "External Template", value: "external_template" }
|
|
4761
|
+
],
|
|
4762
|
+
admin: { width: "50%" }
|
|
4763
|
+
},
|
|
4764
|
+
{
|
|
4765
|
+
name: "subject",
|
|
4766
|
+
type: "text",
|
|
4767
|
+
label: "Subject",
|
|
4768
|
+
admin: {
|
|
4769
|
+
description: "Subject line (optional when using external provider template)"
|
|
4770
|
+
}
|
|
4771
|
+
},
|
|
4772
|
+
{
|
|
4773
|
+
name: "rawHtml",
|
|
4774
|
+
type: "textarea",
|
|
4775
|
+
label: "HTML Content",
|
|
4776
|
+
admin: {
|
|
4777
|
+
description: "Raw HTML email content with {{variable}} tags"
|
|
4778
|
+
}
|
|
4779
|
+
},
|
|
4780
|
+
{
|
|
4781
|
+
name: "externalTemplateId",
|
|
4782
|
+
type: "text",
|
|
4783
|
+
label: "External Template ID",
|
|
4784
|
+
admin: {
|
|
4785
|
+
description: "Template ID or alias from Seamailer, Postmark, SendGrid, etc."
|
|
4786
|
+
}
|
|
4787
|
+
},
|
|
4788
|
+
{
|
|
4789
|
+
name: "active",
|
|
4790
|
+
type: "boolean",
|
|
4791
|
+
label: "Active",
|
|
4792
|
+
defaultValue: true,
|
|
4793
|
+
admin: {
|
|
4794
|
+
description: "When active, this template overrides code config. When disabled, falls back to code."
|
|
4795
|
+
}
|
|
4796
|
+
}
|
|
4797
|
+
],
|
|
4798
|
+
admin: {
|
|
4799
|
+
group: "Settings",
|
|
4800
|
+
icon: "Mail",
|
|
4801
|
+
groupBy: "collectionSlug",
|
|
4802
|
+
defaultColumns: ["purpose", "collectionSlug", "format", "active"]
|
|
4803
|
+
},
|
|
4804
|
+
hooks: {
|
|
4805
|
+
beforeChange: [
|
|
4806
|
+
async ({ data, req }) => {
|
|
4807
|
+
if (data && data.format === "html") {
|
|
4808
|
+
const siteName = req?.config?.admin?.branding?.logoText || "Dyrected";
|
|
4809
|
+
const defaults = getDefaultEmailTemplate(data.purpose || "invite", { siteName });
|
|
4810
|
+
if (!data.subject) {
|
|
4811
|
+
data.subject = defaults.subject;
|
|
4812
|
+
}
|
|
4813
|
+
if (!data.rawHtml) {
|
|
4814
|
+
data.rawHtml = defaults.rawHtml;
|
|
4815
|
+
}
|
|
4816
|
+
}
|
|
4817
|
+
return data;
|
|
4818
|
+
}
|
|
4819
|
+
]
|
|
4820
|
+
},
|
|
4821
|
+
access: {
|
|
4822
|
+
read: ({ user }) => isUserAdmin(user),
|
|
4823
|
+
create: ({ user }) => isUserAdmin(user),
|
|
4824
|
+
update: ({ user }) => isUserAdmin(user),
|
|
4825
|
+
delete: ({ user }) => isUserAdmin(user)
|
|
4826
|
+
}
|
|
4827
|
+
};
|
|
4452
4828
|
function normalizeConfig(config) {
|
|
4453
4829
|
const schemaAwareConfig = normalizeSchemaFragment(config);
|
|
4454
4830
|
assertValidDeclarativeHooksInConfig(schemaAwareConfig, "config");
|
|
@@ -4463,10 +4839,13 @@ function normalizeConfig(config) {
|
|
|
4463
4839
|
const needsTasks = (config.tasks?.length ?? 0) > 0;
|
|
4464
4840
|
const needsAuthSessions = collections.some((col) => !!col.auth);
|
|
4465
4841
|
const needsTrash = schemaAwareConfig.trash?.enabled === true || collections.some((col) => resolveTrashConfig(col, schemaAwareConfig).enabled);
|
|
4466
|
-
const
|
|
4842
|
+
const adminAuthCollection = getAdminAuthCollection({
|
|
4467
4843
|
collections,
|
|
4468
4844
|
adminAuth: schemaAwareConfig.adminAuth
|
|
4469
|
-
})
|
|
4845
|
+
});
|
|
4846
|
+
const adminAuthCollectionSlug = adminAuthCollection?.slug;
|
|
4847
|
+
const adminOnly = ({ user }) => isUserAdmin(user, adminAuthCollection);
|
|
4848
|
+
const adminOtherThanSelf = ({ user, id }) => isUserAdmin(user, adminAuthCollection) && user?.id !== id;
|
|
4470
4849
|
const normalizedCollections = collections.map((col) => {
|
|
4471
4850
|
let fields = col.fields || [];
|
|
4472
4851
|
const existingFieldNames = new Set(fields.map((f) => f.name));
|
|
@@ -4515,7 +4894,7 @@ function normalizeConfig(config) {
|
|
|
4515
4894
|
{ value: "viewer", label: "Viewer" }
|
|
4516
4895
|
],
|
|
4517
4896
|
access: {
|
|
4518
|
-
update:
|
|
4897
|
+
update: adminOnly
|
|
4519
4898
|
}
|
|
4520
4899
|
}
|
|
4521
4900
|
];
|
|
@@ -4533,7 +4912,7 @@ function normalizeConfig(config) {
|
|
|
4533
4912
|
{ value: "pending", label: "Pending" }
|
|
4534
4913
|
],
|
|
4535
4914
|
access: {
|
|
4536
|
-
update:
|
|
4915
|
+
update: adminOtherThanSelf
|
|
4537
4916
|
},
|
|
4538
4917
|
admin: {
|
|
4539
4918
|
condition: `!(data.roles && "admin" in data.roles) && data.role != "admin"`
|
|
@@ -4602,7 +4981,7 @@ function normalizeConfig(config) {
|
|
|
4602
4981
|
access: {
|
|
4603
4982
|
...field.access || {},
|
|
4604
4983
|
// Must be an admin; cannot edit own roles (no self-elevation).
|
|
4605
|
-
update:
|
|
4984
|
+
update: adminOtherThanSelf
|
|
4606
4985
|
}
|
|
4607
4986
|
};
|
|
4608
4987
|
}
|
|
@@ -4611,7 +4990,7 @@ function normalizeConfig(config) {
|
|
|
4611
4990
|
...field,
|
|
4612
4991
|
access: {
|
|
4613
4992
|
...field.access || {},
|
|
4614
|
-
update:
|
|
4993
|
+
update: adminOtherThanSelf
|
|
4615
4994
|
}
|
|
4616
4995
|
};
|
|
4617
4996
|
}
|
|
@@ -4703,6 +5082,52 @@ function normalizeConfig(config) {
|
|
|
4703
5082
|
if (needsTrash && !normalizedCollections.some((col) => col.slug === TRASH_COLLECTION)) {
|
|
4704
5083
|
systemCollections.push(TRASH_COLLECTION_CONFIG);
|
|
4705
5084
|
}
|
|
5085
|
+
const needsEmailTemplates = schemaAwareConfig.email?.adminEditable === true;
|
|
5086
|
+
if (needsEmailTemplates && !normalizedCollections.some((col) => col.slug === EMAIL_TEMPLATES_COLLECTION_SLUG)) {
|
|
5087
|
+
const adminCol = getAdminAuthCollection(schemaAwareConfig);
|
|
5088
|
+
const customAccess = schemaAwareConfig.email?.access;
|
|
5089
|
+
const resolveOpAccess = (op) => {
|
|
5090
|
+
if (typeof customAccess === "function") return customAccess;
|
|
5091
|
+
if (customAccess && typeof customAccess === "object" && customAccess[op]) return customAccess[op];
|
|
5092
|
+
return ({ user }) => isUserAdmin(user, adminCol);
|
|
5093
|
+
};
|
|
5094
|
+
const customPurposes = schemaAwareConfig.email?.purposes ?? [];
|
|
5095
|
+
const purposeOptions = [
|
|
5096
|
+
{ label: "Invite", value: "invite" },
|
|
5097
|
+
{ label: "Password Reset", value: "resetPassword" },
|
|
5098
|
+
{ label: "Welcome", value: "welcome" },
|
|
5099
|
+
{ label: "Password Changed", value: "passwordChanged" },
|
|
5100
|
+
...customPurposes
|
|
5101
|
+
];
|
|
5102
|
+
const targetableCollections = normalizedCollections.filter(
|
|
5103
|
+
(c) => !c.slug.startsWith("__")
|
|
5104
|
+
);
|
|
5105
|
+
const audienceOptions = [
|
|
5106
|
+
{ label: "Global (All Collections)", value: "*" },
|
|
5107
|
+
...targetableCollections.map((c) => ({
|
|
5108
|
+
label: `${c.labels?.singular || c.labels?.plural || c.slug} (${c.slug})`,
|
|
5109
|
+
value: c.slug
|
|
5110
|
+
}))
|
|
5111
|
+
];
|
|
5112
|
+
systemCollections.push({
|
|
5113
|
+
...EMAIL_TEMPLATES_COLLECTION_CONFIG,
|
|
5114
|
+
fields: EMAIL_TEMPLATES_COLLECTION_CONFIG.fields.map((f) => {
|
|
5115
|
+
if (f.name === "collectionSlug") {
|
|
5116
|
+
return { ...f, options: audienceOptions };
|
|
5117
|
+
}
|
|
5118
|
+
if (f.name === "purpose") {
|
|
5119
|
+
return { ...f, options: purposeOptions };
|
|
5120
|
+
}
|
|
5121
|
+
return f;
|
|
5122
|
+
}),
|
|
5123
|
+
access: {
|
|
5124
|
+
read: resolveOpAccess("read"),
|
|
5125
|
+
create: resolveOpAccess("create"),
|
|
5126
|
+
update: resolveOpAccess("update"),
|
|
5127
|
+
delete: resolveOpAccess("delete")
|
|
5128
|
+
}
|
|
5129
|
+
});
|
|
5130
|
+
}
|
|
4706
5131
|
return {
|
|
4707
5132
|
...schemaAwareConfig,
|
|
4708
5133
|
collections: [...normalizedCollections, ...systemCollections],
|
|
@@ -4822,6 +5247,10 @@ function parseSqlWhere(where, getJsonField, placeholder = "?", likeOperator = pl
|
|
|
4822
5247
|
}
|
|
4823
5248
|
return `${c} = ${next()}`;
|
|
4824
5249
|
case "not_equals":
|
|
5250
|
+
case "not":
|
|
5251
|
+
case "ne":
|
|
5252
|
+
case "not_equal":
|
|
5253
|
+
case "notEquals":
|
|
4825
5254
|
if (operand === null) {
|
|
4826
5255
|
return `${c} IS NOT NULL`;
|
|
4827
5256
|
}
|
|
@@ -4837,9 +5266,33 @@ function parseSqlWhere(where, getJsonField, placeholder = "?", likeOperator = pl
|
|
|
4837
5266
|
} else {
|
|
4838
5267
|
params.push(operand);
|
|
4839
5268
|
}
|
|
4840
|
-
|
|
4841
|
-
|
|
5269
|
+
return `${c} != ${next()}`;
|
|
5270
|
+
}
|
|
5271
|
+
if (Array.isArray(operand)) {
|
|
5272
|
+
if (operand.length === 0) return "1=1";
|
|
5273
|
+
const placeholders = operand.map((v) => {
|
|
5274
|
+
if (typeof v === "boolean") {
|
|
5275
|
+
if (placeholder === "?") {
|
|
5276
|
+
params.push(v ? 1 : 0);
|
|
5277
|
+
} else if (isJsonExtract) {
|
|
5278
|
+
params.push(String(v));
|
|
5279
|
+
} else {
|
|
5280
|
+
params.push(v);
|
|
5281
|
+
}
|
|
5282
|
+
} else {
|
|
5283
|
+
params.push(v);
|
|
5284
|
+
}
|
|
5285
|
+
return next();
|
|
5286
|
+
});
|
|
5287
|
+
return `${c} NOT IN (${placeholders.join(", ")})`;
|
|
4842
5288
|
}
|
|
5289
|
+
if (typeof operand === "object") {
|
|
5290
|
+
const innerEntries = Object.entries(operand);
|
|
5291
|
+
if (innerEntries.length === 0) return "1=1";
|
|
5292
|
+
const innerSql = innerEntries.map(([innerOp, innerOperand]) => buildSingleOp(c, innerOp, innerOperand)).join(" AND ");
|
|
5293
|
+
return `NOT (${innerSql})`;
|
|
5294
|
+
}
|
|
5295
|
+
params.push(operand);
|
|
4843
5296
|
return `${c} != ${next()}`;
|
|
4844
5297
|
case "in": {
|
|
4845
5298
|
const vals = Array.isArray(operand) ? operand : [operand];
|
|
@@ -4910,6 +5363,15 @@ function parseSqlWhere(where, getJsonField, placeholder = "?", likeOperator = pl
|
|
|
4910
5363
|
} else if (upperField === "AND" && Array.isArray(value)) {
|
|
4911
5364
|
const sub = value.map((v) => `(${buildClause(v)})`).join(" AND ");
|
|
4912
5365
|
parts.push(`(${sub})`);
|
|
5366
|
+
} else if (upperField === "NOT") {
|
|
5367
|
+
if (Array.isArray(value)) {
|
|
5368
|
+
const sub = value.map((v) => `(${buildClause(v)})`).join(" AND ");
|
|
5369
|
+
parts.push(`(NOT (${sub}))`);
|
|
5370
|
+
} else if (typeof value === "object" && value !== null && !("equals" in value || "not_equals" in value || "not" in value || "ne" in value || "not_equal" in value || "notEquals" in value || "in" in value || "not_in" in value || "gt" in value || "gte" in value || "lt" in value || "lte" in value || "contains" in value || "like" in value || "starts_with" in value || "exists" in value)) {
|
|
5371
|
+
parts.push(`(NOT (${buildClause(value)}))`);
|
|
5372
|
+
} else {
|
|
5373
|
+
parts.push(buildOperator(field, value));
|
|
5374
|
+
}
|
|
4913
5375
|
} else {
|
|
4914
5376
|
parts.push(buildOperator(field, value));
|
|
4915
5377
|
}
|
|
@@ -4940,7 +5402,28 @@ function parseMongoWhere(where) {
|
|
|
4940
5402
|
case "equals":
|
|
4941
5403
|
return { [field]: { $eq: operand } };
|
|
4942
5404
|
case "not_equals":
|
|
5405
|
+
case "not":
|
|
5406
|
+
case "ne":
|
|
5407
|
+
case "not_equal":
|
|
5408
|
+
case "notEquals": {
|
|
5409
|
+
if (Array.isArray(operand)) {
|
|
5410
|
+
return { [field]: { $nin: operand } };
|
|
5411
|
+
}
|
|
5412
|
+
if (typeof operand === "object" && operand !== null) {
|
|
5413
|
+
const innerEntries = Object.entries(operand);
|
|
5414
|
+
if (innerEntries.length === 1) {
|
|
5415
|
+
const [innerOp, innerOperand] = innerEntries[0];
|
|
5416
|
+
const inner = buildSingleOp(field, innerOp, innerOperand)[field];
|
|
5417
|
+
return { [field]: { $not: inner } };
|
|
5418
|
+
}
|
|
5419
|
+
const merged = {};
|
|
5420
|
+
for (const [innerOp, innerOperand] of innerEntries) {
|
|
5421
|
+
Object.assign(merged, buildSingleOp(field, innerOp, innerOperand)[field]);
|
|
5422
|
+
}
|
|
5423
|
+
return { [field]: { $not: merged } };
|
|
5424
|
+
}
|
|
4943
5425
|
return { [field]: { $ne: operand } };
|
|
5426
|
+
}
|
|
4944
5427
|
case "in":
|
|
4945
5428
|
return { [field]: { $in: Array.isArray(operand) ? operand : [operand] } };
|
|
4946
5429
|
case "not_in":
|
|
@@ -4978,6 +5461,14 @@ function parseMongoWhere(where) {
|
|
|
4978
5461
|
conditions.push({ $or: value.map(buildClause) });
|
|
4979
5462
|
} else if (upperField === "AND" && Array.isArray(value)) {
|
|
4980
5463
|
conditions.push({ $and: value.map(buildClause) });
|
|
5464
|
+
} else if (upperField === "NOT") {
|
|
5465
|
+
if (Array.isArray(value)) {
|
|
5466
|
+
conditions.push({ $nor: value.map(buildClause) });
|
|
5467
|
+
} else if (typeof value === "object" && value !== null && !("equals" in value || "not_equals" in value || "not" in value || "ne" in value || "not_equal" in value || "notEquals" in value || "in" in value || "not_in" in value || "gt" in value || "gte" in value || "lt" in value || "lte" in value || "contains" in value || "like" in value || "starts_with" in value || "exists" in value)) {
|
|
5468
|
+
conditions.push({ $nor: [buildClause(value)] });
|
|
5469
|
+
} else {
|
|
5470
|
+
conditions.push(buildOperator(field, value));
|
|
5471
|
+
}
|
|
4981
5472
|
} else {
|
|
4982
5473
|
conditions.push(buildOperator(field, value));
|
|
4983
5474
|
}
|
|
@@ -4999,7 +5490,7 @@ function coerceBooleanWhere(where, fields) {
|
|
|
4999
5490
|
if (!node || typeof node !== "object") return node;
|
|
5000
5491
|
const out = {};
|
|
5001
5492
|
for (const [key, value] of Object.entries(node)) {
|
|
5002
|
-
if (key.toUpperCase() === "AND" || key.toUpperCase() === "OR") {
|
|
5493
|
+
if (key.toUpperCase() === "AND" || key.toUpperCase() === "OR" || key.toUpperCase() === "NOT") {
|
|
5003
5494
|
out[key] = walk(value);
|
|
5004
5495
|
} else if (booleanFields.has(key)) {
|
|
5005
5496
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
@@ -6030,6 +6521,31 @@ function generateOpenApi(config) {
|
|
|
6030
6521
|
spec.paths[`${path}/accept-invite`] = {
|
|
6031
6522
|
post: publicAuthPost("Accept an invitation")
|
|
6032
6523
|
};
|
|
6524
|
+
spec.paths[`${path}/tokens/verify`] = {
|
|
6525
|
+
get: {
|
|
6526
|
+
tags: [collectionTag],
|
|
6527
|
+
summary: `Verify an invite or password reset token`,
|
|
6528
|
+
parameters: [
|
|
6529
|
+
{
|
|
6530
|
+
name: "token",
|
|
6531
|
+
in: "query",
|
|
6532
|
+
required: true,
|
|
6533
|
+
schema: { type: "string" }
|
|
6534
|
+
},
|
|
6535
|
+
{
|
|
6536
|
+
name: "purpose",
|
|
6537
|
+
in: "query",
|
|
6538
|
+
required: false,
|
|
6539
|
+
schema: { type: "string", enum: ["invite", "reset"] }
|
|
6540
|
+
}
|
|
6541
|
+
],
|
|
6542
|
+
responses: {
|
|
6543
|
+
200: {
|
|
6544
|
+
description: "Token verification result"
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
}
|
|
6548
|
+
};
|
|
6033
6549
|
spec.paths[`${path}/{id}/change-password`] = {
|
|
6034
6550
|
post: {
|
|
6035
6551
|
tags: [collectionTag],
|
|
@@ -7227,8 +7743,8 @@ function sanitizeDocForAI(options) {
|
|
|
7227
7743
|
// src/utils/index-name.ts
|
|
7228
7744
|
import { createHash } from "crypto";
|
|
7229
7745
|
var MAX_INDEX_NAME_LENGTH = 60;
|
|
7230
|
-
function resolveIndexName(prefix,
|
|
7231
|
-
const full = `${prefix}_${
|
|
7746
|
+
function resolveIndexName(prefix, table2, fields) {
|
|
7747
|
+
const full = `${prefix}_${table2}_${fields.join("_")}`;
|
|
7232
7748
|
if (full.length <= MAX_INDEX_NAME_LENGTH) return full;
|
|
7233
7749
|
const hash = createHash("sha1").update(full).digest("hex").slice(0, 10);
|
|
7234
7750
|
const tail = (fields[fields.length - 1] ?? "").slice(0, 30);
|
|
@@ -7700,7 +8216,7 @@ function compileNavigation(config) {
|
|
|
7700
8216
|
const mentionedTrash = explicitNav.some((i) => i.trash || i.slug === "trash");
|
|
7701
8217
|
if (!mentionedTrash) {
|
|
7702
8218
|
const hasTrash = collections.some(
|
|
7703
|
-
(col) => !col.slug
|
|
8219
|
+
(col) => !INTERNAL_SYSTEM_COLLECTIONS.has(col.slug) && resolveTrashConfig(col, config).enabled
|
|
7704
8220
|
);
|
|
7705
8221
|
if (hasTrash) {
|
|
7706
8222
|
registerItem(void 0, {
|
|
@@ -8030,8 +8546,11 @@ export {
|
|
|
8030
8546
|
DuplicateKeyError,
|
|
8031
8547
|
DyrectedAIError,
|
|
8032
8548
|
DyrectedError,
|
|
8549
|
+
EMAIL_TEMPLATES_COLLECTION_CONFIG,
|
|
8550
|
+
EMAIL_TEMPLATES_COLLECTION_SLUG,
|
|
8033
8551
|
FieldConditionBuilder,
|
|
8034
8552
|
ForbiddenError,
|
|
8553
|
+
INTERNAL_SYSTEM_COLLECTIONS,
|
|
8035
8554
|
LIFECYCLE_EVENTS_COLLECTION,
|
|
8036
8555
|
LIFECYCLE_EVENT_NAMES,
|
|
8037
8556
|
MatchBuilder,
|
|
@@ -8049,6 +8568,7 @@ export {
|
|
|
8049
8568
|
WORKFLOW_HISTORY_COLLECTION,
|
|
8050
8569
|
abs,
|
|
8051
8570
|
addDays,
|
|
8571
|
+
alertBox,
|
|
8052
8572
|
asCollection,
|
|
8053
8573
|
assertValidAdminConditionsInConfig,
|
|
8054
8574
|
assertValidDeclarativeAccessInConfig,
|
|
@@ -8078,6 +8598,7 @@ export {
|
|
|
8078
8598
|
createTaskRunner,
|
|
8079
8599
|
createTrashPurgeTask,
|
|
8080
8600
|
createWorkflowDocument,
|
|
8601
|
+
ctaButton,
|
|
8081
8602
|
defaultValue,
|
|
8082
8603
|
defineAction,
|
|
8083
8604
|
defineArrayField,
|
|
@@ -8124,6 +8645,7 @@ export {
|
|
|
8124
8645
|
defineUrlField,
|
|
8125
8646
|
defineView,
|
|
8126
8647
|
defineWorkspace,
|
|
8648
|
+
detailBox,
|
|
8127
8649
|
diffDays,
|
|
8128
8650
|
dispatchLifecycleEvent,
|
|
8129
8651
|
dispatchPendingLifecycleEvents,
|
|
@@ -8139,10 +8661,14 @@ export {
|
|
|
8139
8661
|
displayTab,
|
|
8140
8662
|
displayTabs,
|
|
8141
8663
|
displayText,
|
|
8664
|
+
divider,
|
|
8665
|
+
emailTokens,
|
|
8142
8666
|
endsWith,
|
|
8667
|
+
escapeHtml,
|
|
8143
8668
|
evaluateDetailComputed,
|
|
8144
8669
|
evaluateJexl,
|
|
8145
8670
|
evaluateJexlSync,
|
|
8671
|
+
eventList,
|
|
8146
8672
|
executeFieldAfterRead,
|
|
8147
8673
|
executeFieldBeforeChange,
|
|
8148
8674
|
expr,
|
|
@@ -8159,8 +8685,10 @@ export {
|
|
|
8159
8685
|
getAdminAuthCollection,
|
|
8160
8686
|
getAdminRoleForCollection,
|
|
8161
8687
|
getAllowedSitesForUser,
|
|
8688
|
+
getDefaultEmailTemplate,
|
|
8162
8689
|
getPublicAdminAuthConfig,
|
|
8163
8690
|
getTrashEntryId,
|
|
8691
|
+
heading,
|
|
8164
8692
|
includes,
|
|
8165
8693
|
initializeWorkflowDocument,
|
|
8166
8694
|
isAICollection,
|
|
@@ -8173,6 +8701,7 @@ export {
|
|
|
8173
8701
|
isUserAdmin,
|
|
8174
8702
|
join,
|
|
8175
8703
|
last,
|
|
8704
|
+
layout,
|
|
8176
8705
|
length,
|
|
8177
8706
|
lower,
|
|
8178
8707
|
maskEmail,
|
|
@@ -8188,6 +8717,7 @@ export {
|
|
|
8188
8717
|
normalizeGroupKey,
|
|
8189
8718
|
now,
|
|
8190
8719
|
numericFieldNames,
|
|
8720
|
+
paragraph,
|
|
8191
8721
|
parseCron,
|
|
8192
8722
|
parseMongoWhere,
|
|
8193
8723
|
parseSort,
|
|
@@ -8204,21 +8734,27 @@ export {
|
|
|
8204
8734
|
resolveAdminAuthCollection,
|
|
8205
8735
|
resolveAllCollectionViews,
|
|
8206
8736
|
resolveAuthorizedSiteId,
|
|
8737
|
+
resolveBlocks,
|
|
8207
8738
|
resolveDocumentTitle,
|
|
8208
8739
|
resolveIndexName,
|
|
8209
8740
|
resolvePrefix,
|
|
8210
8741
|
resolveTrashConfig,
|
|
8211
8742
|
restoreDocument,
|
|
8212
8743
|
round,
|
|
8744
|
+
row,
|
|
8213
8745
|
runCollectionHooks,
|
|
8746
|
+
safeLinkDetailBox,
|
|
8214
8747
|
sanitizeDocForAI,
|
|
8215
8748
|
saveWorkflowDraft,
|
|
8749
|
+
sectionLabel,
|
|
8216
8750
|
simplePublishingWorkflow,
|
|
8217
8751
|
slugify,
|
|
8218
8752
|
softDeleteDocument,
|
|
8753
|
+
spacer,
|
|
8219
8754
|
sqrt,
|
|
8220
8755
|
startsWith,
|
|
8221
8756
|
sum,
|
|
8757
|
+
table,
|
|
8222
8758
|
today,
|
|
8223
8759
|
transitionWorkflow,
|
|
8224
8760
|
trim,
|