@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.cjs
CHANGED
|
@@ -1799,8 +1799,11 @@ __export(index_exports, {
|
|
|
1799
1799
|
DuplicateKeyError: () => DuplicateKeyError,
|
|
1800
1800
|
DyrectedAIError: () => DyrectedAIError,
|
|
1801
1801
|
DyrectedError: () => DyrectedError,
|
|
1802
|
+
EMAIL_TEMPLATES_COLLECTION_CONFIG: () => EMAIL_TEMPLATES_COLLECTION_CONFIG,
|
|
1803
|
+
EMAIL_TEMPLATES_COLLECTION_SLUG: () => EMAIL_TEMPLATES_COLLECTION_SLUG,
|
|
1802
1804
|
FieldConditionBuilder: () => FieldConditionBuilder,
|
|
1803
1805
|
ForbiddenError: () => ForbiddenError,
|
|
1806
|
+
INTERNAL_SYSTEM_COLLECTIONS: () => INTERNAL_SYSTEM_COLLECTIONS,
|
|
1804
1807
|
LIFECYCLE_EVENTS_COLLECTION: () => LIFECYCLE_EVENTS_COLLECTION,
|
|
1805
1808
|
LIFECYCLE_EVENT_NAMES: () => LIFECYCLE_EVENT_NAMES,
|
|
1806
1809
|
MatchBuilder: () => MatchBuilder,
|
|
@@ -1818,6 +1821,7 @@ __export(index_exports, {
|
|
|
1818
1821
|
WORKFLOW_HISTORY_COLLECTION: () => WORKFLOW_HISTORY_COLLECTION,
|
|
1819
1822
|
abs: () => abs,
|
|
1820
1823
|
addDays: () => addDays,
|
|
1824
|
+
alertBox: () => alertBox,
|
|
1821
1825
|
asCollection: () => asCollection,
|
|
1822
1826
|
assertValidAdminConditionsInConfig: () => assertValidAdminConditionsInConfig,
|
|
1823
1827
|
assertValidDeclarativeAccessInConfig: () => assertValidDeclarativeAccessInConfig,
|
|
@@ -1847,6 +1851,7 @@ __export(index_exports, {
|
|
|
1847
1851
|
createTaskRunner: () => createTaskRunner,
|
|
1848
1852
|
createTrashPurgeTask: () => createTrashPurgeTask,
|
|
1849
1853
|
createWorkflowDocument: () => createWorkflowDocument,
|
|
1854
|
+
ctaButton: () => ctaButton,
|
|
1850
1855
|
defaultValue: () => defaultValue,
|
|
1851
1856
|
defineAction: () => defineAction,
|
|
1852
1857
|
defineArrayField: () => defineArrayField,
|
|
@@ -1893,6 +1898,7 @@ __export(index_exports, {
|
|
|
1893
1898
|
defineUrlField: () => defineUrlField,
|
|
1894
1899
|
defineView: () => defineView,
|
|
1895
1900
|
defineWorkspace: () => defineWorkspace,
|
|
1901
|
+
detailBox: () => detailBox,
|
|
1896
1902
|
diffDays: () => diffDays,
|
|
1897
1903
|
dispatchLifecycleEvent: () => dispatchLifecycleEvent,
|
|
1898
1904
|
dispatchPendingLifecycleEvents: () => dispatchPendingLifecycleEvents,
|
|
@@ -1908,10 +1914,14 @@ __export(index_exports, {
|
|
|
1908
1914
|
displayTab: () => displayTab,
|
|
1909
1915
|
displayTabs: () => displayTabs,
|
|
1910
1916
|
displayText: () => displayText,
|
|
1917
|
+
divider: () => divider,
|
|
1918
|
+
emailTokens: () => emailTokens,
|
|
1911
1919
|
endsWith: () => endsWith,
|
|
1920
|
+
escapeHtml: () => escapeHtml,
|
|
1912
1921
|
evaluateDetailComputed: () => evaluateDetailComputed,
|
|
1913
1922
|
evaluateJexl: () => evaluateJexl,
|
|
1914
1923
|
evaluateJexlSync: () => evaluateJexlSync,
|
|
1924
|
+
eventList: () => eventList,
|
|
1915
1925
|
executeFieldAfterRead: () => executeFieldAfterRead,
|
|
1916
1926
|
executeFieldBeforeChange: () => executeFieldBeforeChange,
|
|
1917
1927
|
expr: () => expr,
|
|
@@ -1928,8 +1938,10 @@ __export(index_exports, {
|
|
|
1928
1938
|
getAdminAuthCollection: () => getAdminAuthCollection,
|
|
1929
1939
|
getAdminRoleForCollection: () => getAdminRoleForCollection,
|
|
1930
1940
|
getAllowedSitesForUser: () => getAllowedSitesForUser,
|
|
1941
|
+
getDefaultEmailTemplate: () => getDefaultEmailTemplate,
|
|
1931
1942
|
getPublicAdminAuthConfig: () => getPublicAdminAuthConfig,
|
|
1932
1943
|
getTrashEntryId: () => getTrashEntryId,
|
|
1944
|
+
heading: () => heading,
|
|
1933
1945
|
includes: () => includes,
|
|
1934
1946
|
initializeWorkflowDocument: () => initializeWorkflowDocument,
|
|
1935
1947
|
isAICollection: () => isAICollection,
|
|
@@ -1942,6 +1954,7 @@ __export(index_exports, {
|
|
|
1942
1954
|
isUserAdmin: () => isUserAdmin,
|
|
1943
1955
|
join: () => join,
|
|
1944
1956
|
last: () => last,
|
|
1957
|
+
layout: () => layout,
|
|
1945
1958
|
length: () => length,
|
|
1946
1959
|
lower: () => lower,
|
|
1947
1960
|
maskEmail: () => maskEmail,
|
|
@@ -1957,6 +1970,7 @@ __export(index_exports, {
|
|
|
1957
1970
|
normalizeGroupKey: () => normalizeGroupKey,
|
|
1958
1971
|
now: () => now,
|
|
1959
1972
|
numericFieldNames: () => numericFieldNames,
|
|
1973
|
+
paragraph: () => paragraph,
|
|
1960
1974
|
parseCron: () => parseCron,
|
|
1961
1975
|
parseMongoWhere: () => parseMongoWhere,
|
|
1962
1976
|
parseSort: () => parseSort,
|
|
@@ -1973,21 +1987,27 @@ __export(index_exports, {
|
|
|
1973
1987
|
resolveAdminAuthCollection: () => resolveAdminAuthCollection,
|
|
1974
1988
|
resolveAllCollectionViews: () => resolveAllCollectionViews,
|
|
1975
1989
|
resolveAuthorizedSiteId: () => resolveAuthorizedSiteId,
|
|
1990
|
+
resolveBlocks: () => resolveBlocks,
|
|
1976
1991
|
resolveDocumentTitle: () => resolveDocumentTitle,
|
|
1977
1992
|
resolveIndexName: () => resolveIndexName,
|
|
1978
1993
|
resolvePrefix: () => resolvePrefix,
|
|
1979
1994
|
resolveTrashConfig: () => resolveTrashConfig,
|
|
1980
1995
|
restoreDocument: () => restoreDocument,
|
|
1981
1996
|
round: () => round,
|
|
1997
|
+
row: () => row,
|
|
1982
1998
|
runCollectionHooks: () => runCollectionHooks,
|
|
1999
|
+
safeLinkDetailBox: () => safeLinkDetailBox,
|
|
1983
2000
|
sanitizeDocForAI: () => sanitizeDocForAI,
|
|
1984
2001
|
saveWorkflowDraft: () => saveWorkflowDraft,
|
|
2002
|
+
sectionLabel: () => sectionLabel,
|
|
1985
2003
|
simplePublishingWorkflow: () => simplePublishingWorkflow,
|
|
1986
2004
|
slugify: () => slugify,
|
|
1987
2005
|
softDeleteDocument: () => softDeleteDocument,
|
|
2006
|
+
spacer: () => spacer,
|
|
1988
2007
|
sqrt: () => sqrt,
|
|
1989
2008
|
startsWith: () => startsWith,
|
|
1990
2009
|
sum: () => sum,
|
|
2010
|
+
table: () => table,
|
|
1991
2011
|
today: () => today,
|
|
1992
2012
|
transitionWorkflow: () => transitionWorkflow,
|
|
1993
2013
|
trim: () => trim,
|
|
@@ -3243,6 +3263,14 @@ async function executeFieldAfterRead(fields, doc, user, db) {
|
|
|
3243
3263
|
|
|
3244
3264
|
// src/trash.ts
|
|
3245
3265
|
var TRASH_COLLECTION = "__trash";
|
|
3266
|
+
var INTERNAL_SYSTEM_COLLECTIONS = /* @__PURE__ */ new Set([
|
|
3267
|
+
"__audit",
|
|
3268
|
+
"__sessions",
|
|
3269
|
+
"__task_locks",
|
|
3270
|
+
"__trash",
|
|
3271
|
+
"__workflow_history",
|
|
3272
|
+
"__lifecycle_events"
|
|
3273
|
+
]);
|
|
3246
3274
|
var TRASH_COLLECTION_CONFIG = {
|
|
3247
3275
|
slug: TRASH_COLLECTION,
|
|
3248
3276
|
labels: { singular: "Trash entry", plural: "Trash entries" },
|
|
@@ -3334,7 +3362,7 @@ function assertValidTrashInConfig(config, source = "config") {
|
|
|
3334
3362
|
if (col.slug === "trash") {
|
|
3335
3363
|
throw new Error(`Collection slug "trash" is reserved by Dyrected for the trash system.`);
|
|
3336
3364
|
}
|
|
3337
|
-
const isSystem = col.slug
|
|
3365
|
+
const isSystem = INTERNAL_SYSTEM_COLLECTIONS.has(col.slug);
|
|
3338
3366
|
if (isSystem && col.trash) {
|
|
3339
3367
|
throw new Error(`System collection "${col.slug}" cannot enable trash.`);
|
|
3340
3368
|
}
|
|
@@ -3713,7 +3741,7 @@ function createTaskRunner(config) {
|
|
|
3713
3741
|
throw error;
|
|
3714
3742
|
}
|
|
3715
3743
|
}
|
|
3716
|
-
async function execute(task,
|
|
3744
|
+
async function execute(task, row2, scheduledFor) {
|
|
3717
3745
|
const started = Date.now();
|
|
3718
3746
|
const lockMs = (task.lockTimeout ?? DEFAULT_LOCK_TIMEOUT_SECONDS) * 1e3;
|
|
3719
3747
|
const token = `${instanceId}:${started}`;
|
|
@@ -3721,7 +3749,7 @@ function createTaskRunner(config) {
|
|
|
3721
3749
|
id: { equals: task.name },
|
|
3722
3750
|
lockedUntil: { less_than: started }
|
|
3723
3751
|
};
|
|
3724
|
-
if (scheduledFor) where.lastScheduledFor = { equals:
|
|
3752
|
+
if (scheduledFor) where.lastScheduledFor = { equals: row2.lastScheduledFor };
|
|
3725
3753
|
const claimed = await db.update({
|
|
3726
3754
|
collection: TASK_LOCKS_COLLECTION,
|
|
3727
3755
|
where,
|
|
@@ -3782,21 +3810,21 @@ function createTaskRunner(config) {
|
|
|
3782
3810
|
async runTask(name) {
|
|
3783
3811
|
const task = tasks.get(name);
|
|
3784
3812
|
if (!task) throw new Error(`Unknown task "${name}".`);
|
|
3785
|
-
const
|
|
3786
|
-
return track(execute(task,
|
|
3813
|
+
const row2 = await ensureLockRow(name, /* @__PURE__ */ new Date());
|
|
3814
|
+
return track(execute(task, row2, void 0));
|
|
3787
3815
|
},
|
|
3788
3816
|
async runDue(now2 = /* @__PURE__ */ new Date()) {
|
|
3789
3817
|
const results = [];
|
|
3790
3818
|
for (const task of tasks.values()) {
|
|
3791
3819
|
if (!task.schedule) continue;
|
|
3792
|
-
const
|
|
3793
|
-
const reference = new Date(String(
|
|
3820
|
+
const row2 = await ensureLockRow(task.name, now2);
|
|
3821
|
+
const reference = new Date(String(row2.lastScheduledFor));
|
|
3794
3822
|
const due = Number.isNaN(reference.getTime()) ? true : nextCronRun(task.schedule, reference).getTime() <= now2.getTime();
|
|
3795
3823
|
if (!due) {
|
|
3796
3824
|
results.push({ name: task.name, status: "skipped", reason: "not-due", durationMs: 0 });
|
|
3797
3825
|
continue;
|
|
3798
3826
|
}
|
|
3799
|
-
results.push(await track(execute(task,
|
|
3827
|
+
results.push(await track(execute(task, row2, floorToMinute(now2))));
|
|
3800
3828
|
}
|
|
3801
3829
|
return results;
|
|
3802
3830
|
},
|
|
@@ -4311,13 +4339,34 @@ function getAdminRoleForCollection(collection) {
|
|
|
4311
4339
|
}
|
|
4312
4340
|
return "admin";
|
|
4313
4341
|
}
|
|
4342
|
+
var STANDARD_ADMIN_ROLES = /* @__PURE__ */ new Set([
|
|
4343
|
+
"admin",
|
|
4344
|
+
"super_admin",
|
|
4345
|
+
"superadmin",
|
|
4346
|
+
"super admin",
|
|
4347
|
+
"super-admin",
|
|
4348
|
+
"owner"
|
|
4349
|
+
]);
|
|
4314
4350
|
function isUserAdmin(user, collection) {
|
|
4315
4351
|
if (!user) return false;
|
|
4316
|
-
const
|
|
4317
|
-
if (
|
|
4352
|
+
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] : [];
|
|
4353
|
+
if (rawRoles.length === 0) return false;
|
|
4354
|
+
const roles = rawRoles.filter((r) => typeof r === "string");
|
|
4318
4355
|
const configuredRole = getAdminRoleForCollection(collection);
|
|
4319
|
-
|
|
4320
|
-
|
|
4356
|
+
for (const r of roles) {
|
|
4357
|
+
if (r === configuredRole || r.toLowerCase() === configuredRole.toLowerCase()) {
|
|
4358
|
+
return true;
|
|
4359
|
+
}
|
|
4360
|
+
const normalized = r.trim().toLowerCase();
|
|
4361
|
+
if (STANDARD_ADMIN_ROLES.has(normalized)) {
|
|
4362
|
+
return true;
|
|
4363
|
+
}
|
|
4364
|
+
const stripped = normalized.replace(/[\s_-]+/g, "");
|
|
4365
|
+
if (stripped === "admin" || stripped === "superadmin" || stripped === "owner") {
|
|
4366
|
+
return true;
|
|
4367
|
+
}
|
|
4368
|
+
}
|
|
4369
|
+
return false;
|
|
4321
4370
|
}
|
|
4322
4371
|
|
|
4323
4372
|
// src/utils/block-references.ts
|
|
@@ -4540,6 +4589,245 @@ function generateDocumentId(collectionOrSlug) {
|
|
|
4540
4589
|
return generateId({ prefix, type });
|
|
4541
4590
|
}
|
|
4542
4591
|
|
|
4592
|
+
// src/services/email-template.ts
|
|
4593
|
+
var emailTokens = {
|
|
4594
|
+
colors: {
|
|
4595
|
+
canvas: "#f6f7f2",
|
|
4596
|
+
surface: "#ffffff",
|
|
4597
|
+
text: "#171717",
|
|
4598
|
+
muted: "#62665b",
|
|
4599
|
+
subtle: "#8a8f82",
|
|
4600
|
+
border: "#dde0d7",
|
|
4601
|
+
accent: "#b6ff2e",
|
|
4602
|
+
code: "#f1f3ec",
|
|
4603
|
+
codeBorder: "#e2e5dc",
|
|
4604
|
+
dangerSurface: "#fff2f0",
|
|
4605
|
+
dangerBorder: "#ffc9c2",
|
|
4606
|
+
dangerText: "#9f251b"
|
|
4607
|
+
},
|
|
4608
|
+
font: "Arial, 'Helvetica Neue', Helvetica, sans-serif",
|
|
4609
|
+
mono: "'Courier New', Courier, monospace",
|
|
4610
|
+
radius: { card: "12px", control: "6px" },
|
|
4611
|
+
width: "600px"
|
|
4612
|
+
};
|
|
4613
|
+
function escapeHtml(value) {
|
|
4614
|
+
return value.replace(
|
|
4615
|
+
/[&<>'"]/g,
|
|
4616
|
+
(character) => ({
|
|
4617
|
+
"&": "&",
|
|
4618
|
+
"<": "<",
|
|
4619
|
+
">": ">",
|
|
4620
|
+
"'": "'",
|
|
4621
|
+
'"': """
|
|
4622
|
+
})[character]
|
|
4623
|
+
);
|
|
4624
|
+
}
|
|
4625
|
+
function safeHttpUrl(value) {
|
|
4626
|
+
if (value.startsWith("{{") && value.endsWith("}}")) {
|
|
4627
|
+
return value;
|
|
4628
|
+
}
|
|
4629
|
+
try {
|
|
4630
|
+
const url = new URL(value);
|
|
4631
|
+
return url.protocol === "https:" || url.protocol === "http:" ? escapeHtml(url.toString()) : void 0;
|
|
4632
|
+
} catch {
|
|
4633
|
+
return void 0;
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4636
|
+
function heading(content) {
|
|
4637
|
+
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>`;
|
|
4638
|
+
}
|
|
4639
|
+
function paragraph(content, margin = "0 0 16px", size = "15px") {
|
|
4640
|
+
return `<p style="margin:${margin};font-family:${emailTokens.font};font-size:${size};line-height:1.6;color:${emailTokens.colors.muted}">${escapeHtml(content)}</p>`;
|
|
4641
|
+
}
|
|
4642
|
+
function sectionLabel(content) {
|
|
4643
|
+
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>`;
|
|
4644
|
+
}
|
|
4645
|
+
function divider() {
|
|
4646
|
+
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>`;
|
|
4647
|
+
}
|
|
4648
|
+
function spacer(height = 16) {
|
|
4649
|
+
return table(row(" ", `height:${height}px;font-size:0;line-height:0`));
|
|
4650
|
+
}
|
|
4651
|
+
function row(content, cellStyle = "") {
|
|
4652
|
+
return `<tr><td style="${cellStyle}">${content}</td></tr>`;
|
|
4653
|
+
}
|
|
4654
|
+
function table(content, style = "width:100%") {
|
|
4655
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="${style}">${content}</table>`;
|
|
4656
|
+
}
|
|
4657
|
+
function detailBox(content, monospace = false) {
|
|
4658
|
+
const font = monospace ? emailTokens.mono : emailTokens.font;
|
|
4659
|
+
return table(
|
|
4660
|
+
row(
|
|
4661
|
+
escapeHtml(content),
|
|
4662
|
+
`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`
|
|
4663
|
+
),
|
|
4664
|
+
`width:100%;background:${emailTokens.colors.code};border:1px solid ${emailTokens.colors.codeBorder};border-radius:${emailTokens.radius.control}`
|
|
4665
|
+
);
|
|
4666
|
+
}
|
|
4667
|
+
function eventList(events) {
|
|
4668
|
+
return table(
|
|
4669
|
+
events.map(
|
|
4670
|
+
({ label, value }) => row(
|
|
4671
|
+
`${sectionLabel(label)}${paragraph(value, "0")}`,
|
|
4672
|
+
`padding:12px 0;border-bottom:1px solid ${emailTokens.colors.border}`
|
|
4673
|
+
)
|
|
4674
|
+
).join("")
|
|
4675
|
+
);
|
|
4676
|
+
}
|
|
4677
|
+
function ctaButton(label, href) {
|
|
4678
|
+
const safeHref = safeHttpUrl(href);
|
|
4679
|
+
if (!safeHref) return "";
|
|
4680
|
+
return table(
|
|
4681
|
+
row(
|
|
4682
|
+
`<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>`,
|
|
4683
|
+
"padding:8px 0 24px"
|
|
4684
|
+
),
|
|
4685
|
+
"width:auto"
|
|
4686
|
+
);
|
|
4687
|
+
}
|
|
4688
|
+
function safeLinkDetailBox(href) {
|
|
4689
|
+
const safeHref = safeHttpUrl(href);
|
|
4690
|
+
if (!safeHref) return "";
|
|
4691
|
+
return detailBox(safeHref, true);
|
|
4692
|
+
}
|
|
4693
|
+
function alertBox(content) {
|
|
4694
|
+
return table(
|
|
4695
|
+
row(
|
|
4696
|
+
escapeHtml(content),
|
|
4697
|
+
`padding:13px 16px;font-family:${emailTokens.font};font-size:13px;line-height:1.5;color:${emailTokens.colors.dangerText}`
|
|
4698
|
+
),
|
|
4699
|
+
`width:100%;background:${emailTokens.colors.dangerSurface};border:1px solid ${emailTokens.colors.dangerBorder};border-radius:${emailTokens.radius.control}`
|
|
4700
|
+
);
|
|
4701
|
+
}
|
|
4702
|
+
function resolveBlocks(input) {
|
|
4703
|
+
if (Array.isArray(input)) {
|
|
4704
|
+
const parts = [];
|
|
4705
|
+
for (const item of input) {
|
|
4706
|
+
if (Array.isArray(item)) {
|
|
4707
|
+
for (const sub of item) {
|
|
4708
|
+
if (typeof sub === "string" && sub.length > 0) parts.push(sub);
|
|
4709
|
+
}
|
|
4710
|
+
} else if (typeof item === "string" && item.length > 0) {
|
|
4711
|
+
parts.push(item);
|
|
4712
|
+
}
|
|
4713
|
+
}
|
|
4714
|
+
return parts.join("\n");
|
|
4715
|
+
}
|
|
4716
|
+
return typeof input === "string" ? input : "";
|
|
4717
|
+
}
|
|
4718
|
+
function layout({ preheader, title, content, footer, siteName }) {
|
|
4719
|
+
const resolvedContent = resolveBlocks(content);
|
|
4720
|
+
const resolvedFooter = resolveBlocks(footer);
|
|
4721
|
+
return `<!doctype html>
|
|
4722
|
+
<html lang="en" dir="ltr" xmlns="http://www.w3.org/1999/xhtml">
|
|
4723
|
+
<head>
|
|
4724
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
|
4725
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
4726
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
4727
|
+
<meta name="x-apple-disable-message-reformatting">
|
|
4728
|
+
<meta name="format-detection" content="telephone=no,address=no,email=no,date=no,url=no">
|
|
4729
|
+
<meta name="color-scheme" content="light">
|
|
4730
|
+
<meta name="supported-color-schemes" content="light">
|
|
4731
|
+
<title>${escapeHtml(title)}</title>
|
|
4732
|
+
</head>
|
|
4733
|
+
<body style="margin:0;padding:0;background-color:${emailTokens.colors.canvas};word-spacing:normal;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%">
|
|
4734
|
+
<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">
|
|
4735
|
+
${escapeHtml(preheader)}​͏‌   ͏‌
|
|
4736
|
+
</div>
|
|
4737
|
+
<center lang="en" style="width:100%;background-color:${emailTokens.colors.canvas}">
|
|
4738
|
+
${table(
|
|
4739
|
+
row(
|
|
4740
|
+
table(
|
|
4741
|
+
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(
|
|
4742
|
+
`${divider()}${paragraph(resolvedFooter, "20px 0 6px")}${paragraph("Privacy: this message contains account-related information; please avoid forwarding it.", "10px 0px 0px 0px", "12px")}`,
|
|
4743
|
+
"padding:0 32px 28px"
|
|
4744
|
+
),
|
|
4745
|
+
`width:100%;max-width:${emailTokens.width};background:${emailTokens.colors.surface};border:1px solid ${emailTokens.colors.border};border-radius:${emailTokens.radius.card};overflow:hidden`
|
|
4746
|
+
),
|
|
4747
|
+
"padding:32px 12px"
|
|
4748
|
+
),
|
|
4749
|
+
`width:100%;background:${emailTokens.colors.canvas}`
|
|
4750
|
+
)}
|
|
4751
|
+
</center>
|
|
4752
|
+
</body>
|
|
4753
|
+
</html>`;
|
|
4754
|
+
}
|
|
4755
|
+
function getDefaultEmailTemplate(purpose, options) {
|
|
4756
|
+
const siteName = options?.siteName ?? "Dyrected";
|
|
4757
|
+
switch (purpose) {
|
|
4758
|
+
case "invite":
|
|
4759
|
+
return {
|
|
4760
|
+
subject: `You've been invited to ${siteName}`,
|
|
4761
|
+
rawHtml: layout({
|
|
4762
|
+
siteName,
|
|
4763
|
+
preheader: `You've been invited to join ${siteName}.`,
|
|
4764
|
+
title: "You've been invited",
|
|
4765
|
+
content: [
|
|
4766
|
+
paragraph("Use the invitation link below to create your account. The link expires in 7 days."),
|
|
4767
|
+
ctaButton("Accept invitation", "{{url}}"),
|
|
4768
|
+
sectionLabel("Invitation link"),
|
|
4769
|
+
safeLinkDetailBox("{{url}}"),
|
|
4770
|
+
paragraph("If the button does not work, copy and paste the link above into your browser.", "12px 0 0")
|
|
4771
|
+
],
|
|
4772
|
+
footer: "If you weren't expecting this invitation, you can safely ignore this email."
|
|
4773
|
+
})
|
|
4774
|
+
};
|
|
4775
|
+
case "resetPassword":
|
|
4776
|
+
return {
|
|
4777
|
+
subject: "Reset your password",
|
|
4778
|
+
rawHtml: layout({
|
|
4779
|
+
siteName,
|
|
4780
|
+
preheader: `Reset your ${siteName} password.`,
|
|
4781
|
+
title: "Reset your password",
|
|
4782
|
+
content: [
|
|
4783
|
+
paragraph("We received a request to reset your password. The reset link expires in 1 hour."),
|
|
4784
|
+
ctaButton("Reset password", "{{url}}"),
|
|
4785
|
+
sectionLabel("Reset link"),
|
|
4786
|
+
safeLinkDetailBox("{{url}}"),
|
|
4787
|
+
paragraph("If the button does not work, copy and paste the link above into your browser.", "12px 0 0")
|
|
4788
|
+
],
|
|
4789
|
+
footer: "If you didn't request a password reset, you can safely ignore this email."
|
|
4790
|
+
})
|
|
4791
|
+
};
|
|
4792
|
+
case "welcome":
|
|
4793
|
+
return {
|
|
4794
|
+
subject: `Welcome to ${siteName} \u2014 your account is ready`,
|
|
4795
|
+
rawHtml: layout({
|
|
4796
|
+
siteName,
|
|
4797
|
+
preheader: `Your ${siteName} account is ready.`,
|
|
4798
|
+
title: "Welcome \u2014 your account is ready",
|
|
4799
|
+
content: [paragraph("Your account has been created. You can now log in with:"), detailBox("{{email}}")],
|
|
4800
|
+
footer: "If you didn't create this account, you can safely ignore this email."
|
|
4801
|
+
})
|
|
4802
|
+
};
|
|
4803
|
+
case "passwordChanged":
|
|
4804
|
+
return {
|
|
4805
|
+
subject: "Your password has been changed",
|
|
4806
|
+
rawHtml: layout({
|
|
4807
|
+
siteName,
|
|
4808
|
+
preheader: `Your ${siteName} password was changed.`,
|
|
4809
|
+
title: "Password changed",
|
|
4810
|
+
content: [
|
|
4811
|
+
paragraph("The password for your account has been changed."),
|
|
4812
|
+
alertBox("If you did not make this change, please contact support or reset your password immediately.")
|
|
4813
|
+
],
|
|
4814
|
+
footer: "Security notice: this confirms a recent credential change."
|
|
4815
|
+
})
|
|
4816
|
+
};
|
|
4817
|
+
default:
|
|
4818
|
+
return {
|
|
4819
|
+
subject: `Notification from ${siteName}`,
|
|
4820
|
+
rawHtml: layout({
|
|
4821
|
+
siteName,
|
|
4822
|
+
preheader: `Account notification from ${siteName}.`,
|
|
4823
|
+
title: "Account Notification",
|
|
4824
|
+
content: [paragraph(`This is an account notification from ${siteName}.`)],
|
|
4825
|
+
footer: "If you have any questions, please reach out to our team."
|
|
4826
|
+
})
|
|
4827
|
+
};
|
|
4828
|
+
}
|
|
4829
|
+
}
|
|
4830
|
+
|
|
4543
4831
|
// src/utils/config.ts
|
|
4544
4832
|
var AUDIT_COLLECTION_SLUG = "__audit";
|
|
4545
4833
|
var SYSTEM_FIELDS = [
|
|
@@ -4681,6 +4969,114 @@ var AUTH_SESSIONS_COLLECTION_CONFIG = {
|
|
|
4681
4969
|
},
|
|
4682
4970
|
admin: { hidden: true }
|
|
4683
4971
|
};
|
|
4972
|
+
var EMAIL_TEMPLATES_COLLECTION_SLUG = "__email_templates";
|
|
4973
|
+
var EMAIL_TEMPLATES_COLLECTION_CONFIG = {
|
|
4974
|
+
slug: EMAIL_TEMPLATES_COLLECTION_SLUG,
|
|
4975
|
+
labels: { singular: "Email Template", plural: "Email Templates" },
|
|
4976
|
+
detail: false,
|
|
4977
|
+
fields: [
|
|
4978
|
+
{
|
|
4979
|
+
name: "collectionSlug",
|
|
4980
|
+
type: "select",
|
|
4981
|
+
label: "Applies To",
|
|
4982
|
+
required: true,
|
|
4983
|
+
defaultValue: "*",
|
|
4984
|
+
options: [
|
|
4985
|
+
{ label: "Global (All Users & Collections)", value: "*" }
|
|
4986
|
+
],
|
|
4987
|
+
admin: {
|
|
4988
|
+
description: "Choose which user collection this template applies to, or 'Global' for all users."
|
|
4989
|
+
}
|
|
4990
|
+
},
|
|
4991
|
+
{
|
|
4992
|
+
name: "purpose",
|
|
4993
|
+
type: "select",
|
|
4994
|
+
label: "Purpose",
|
|
4995
|
+
required: true,
|
|
4996
|
+
options: [
|
|
4997
|
+
{ label: "Invite", value: "invite" },
|
|
4998
|
+
{ label: "Password Reset", value: "resetPassword" },
|
|
4999
|
+
{ label: "Welcome", value: "welcome" },
|
|
5000
|
+
{ label: "Password Changed", value: "passwordChanged" }
|
|
5001
|
+
],
|
|
5002
|
+
admin: { width: "50%" }
|
|
5003
|
+
},
|
|
5004
|
+
{
|
|
5005
|
+
name: "format",
|
|
5006
|
+
type: "select",
|
|
5007
|
+
label: "Format",
|
|
5008
|
+
required: true,
|
|
5009
|
+
defaultValue: "html",
|
|
5010
|
+
options: [
|
|
5011
|
+
{ label: "HTML", value: "html" },
|
|
5012
|
+
{ label: "External Template", value: "external_template" }
|
|
5013
|
+
],
|
|
5014
|
+
admin: { width: "50%" }
|
|
5015
|
+
},
|
|
5016
|
+
{
|
|
5017
|
+
name: "subject",
|
|
5018
|
+
type: "text",
|
|
5019
|
+
label: "Subject",
|
|
5020
|
+
admin: {
|
|
5021
|
+
description: "Subject line (optional when using external provider template)"
|
|
5022
|
+
}
|
|
5023
|
+
},
|
|
5024
|
+
{
|
|
5025
|
+
name: "rawHtml",
|
|
5026
|
+
type: "textarea",
|
|
5027
|
+
label: "HTML Content",
|
|
5028
|
+
admin: {
|
|
5029
|
+
description: "Raw HTML email content with {{variable}} tags"
|
|
5030
|
+
}
|
|
5031
|
+
},
|
|
5032
|
+
{
|
|
5033
|
+
name: "externalTemplateId",
|
|
5034
|
+
type: "text",
|
|
5035
|
+
label: "External Template ID",
|
|
5036
|
+
admin: {
|
|
5037
|
+
description: "Template ID or alias from Seamailer, Postmark, SendGrid, etc."
|
|
5038
|
+
}
|
|
5039
|
+
},
|
|
5040
|
+
{
|
|
5041
|
+
name: "active",
|
|
5042
|
+
type: "boolean",
|
|
5043
|
+
label: "Active",
|
|
5044
|
+
defaultValue: true,
|
|
5045
|
+
admin: {
|
|
5046
|
+
description: "When active, this template overrides code config. When disabled, falls back to code."
|
|
5047
|
+
}
|
|
5048
|
+
}
|
|
5049
|
+
],
|
|
5050
|
+
admin: {
|
|
5051
|
+
group: "Settings",
|
|
5052
|
+
icon: "Mail",
|
|
5053
|
+
groupBy: "collectionSlug",
|
|
5054
|
+
defaultColumns: ["purpose", "collectionSlug", "format", "active"]
|
|
5055
|
+
},
|
|
5056
|
+
hooks: {
|
|
5057
|
+
beforeChange: [
|
|
5058
|
+
async ({ data, req }) => {
|
|
5059
|
+
if (data && data.format === "html") {
|
|
5060
|
+
const siteName = req?.config?.admin?.branding?.logoText || "Dyrected";
|
|
5061
|
+
const defaults = getDefaultEmailTemplate(data.purpose || "invite", { siteName });
|
|
5062
|
+
if (!data.subject) {
|
|
5063
|
+
data.subject = defaults.subject;
|
|
5064
|
+
}
|
|
5065
|
+
if (!data.rawHtml) {
|
|
5066
|
+
data.rawHtml = defaults.rawHtml;
|
|
5067
|
+
}
|
|
5068
|
+
}
|
|
5069
|
+
return data;
|
|
5070
|
+
}
|
|
5071
|
+
]
|
|
5072
|
+
},
|
|
5073
|
+
access: {
|
|
5074
|
+
read: ({ user }) => isUserAdmin(user),
|
|
5075
|
+
create: ({ user }) => isUserAdmin(user),
|
|
5076
|
+
update: ({ user }) => isUserAdmin(user),
|
|
5077
|
+
delete: ({ user }) => isUserAdmin(user)
|
|
5078
|
+
}
|
|
5079
|
+
};
|
|
4684
5080
|
function normalizeConfig(config) {
|
|
4685
5081
|
const schemaAwareConfig = normalizeSchemaFragment(config);
|
|
4686
5082
|
assertValidDeclarativeHooksInConfig(schemaAwareConfig, "config");
|
|
@@ -4695,10 +5091,13 @@ function normalizeConfig(config) {
|
|
|
4695
5091
|
const needsTasks = (config.tasks?.length ?? 0) > 0;
|
|
4696
5092
|
const needsAuthSessions = collections.some((col) => !!col.auth);
|
|
4697
5093
|
const needsTrash = schemaAwareConfig.trash?.enabled === true || collections.some((col) => resolveTrashConfig(col, schemaAwareConfig).enabled);
|
|
4698
|
-
const
|
|
5094
|
+
const adminAuthCollection = getAdminAuthCollection({
|
|
4699
5095
|
collections,
|
|
4700
5096
|
adminAuth: schemaAwareConfig.adminAuth
|
|
4701
|
-
})
|
|
5097
|
+
});
|
|
5098
|
+
const adminAuthCollectionSlug = adminAuthCollection?.slug;
|
|
5099
|
+
const adminOnly = ({ user }) => isUserAdmin(user, adminAuthCollection);
|
|
5100
|
+
const adminOtherThanSelf = ({ user, id }) => isUserAdmin(user, adminAuthCollection) && user?.id !== id;
|
|
4702
5101
|
const normalizedCollections = collections.map((col) => {
|
|
4703
5102
|
let fields = col.fields || [];
|
|
4704
5103
|
const existingFieldNames = new Set(fields.map((f) => f.name));
|
|
@@ -4747,7 +5146,7 @@ function normalizeConfig(config) {
|
|
|
4747
5146
|
{ value: "viewer", label: "Viewer" }
|
|
4748
5147
|
],
|
|
4749
5148
|
access: {
|
|
4750
|
-
update:
|
|
5149
|
+
update: adminOnly
|
|
4751
5150
|
}
|
|
4752
5151
|
}
|
|
4753
5152
|
];
|
|
@@ -4765,7 +5164,7 @@ function normalizeConfig(config) {
|
|
|
4765
5164
|
{ value: "pending", label: "Pending" }
|
|
4766
5165
|
],
|
|
4767
5166
|
access: {
|
|
4768
|
-
update:
|
|
5167
|
+
update: adminOtherThanSelf
|
|
4769
5168
|
},
|
|
4770
5169
|
admin: {
|
|
4771
5170
|
condition: `!(data.roles && "admin" in data.roles) && data.role != "admin"`
|
|
@@ -4834,7 +5233,7 @@ function normalizeConfig(config) {
|
|
|
4834
5233
|
access: {
|
|
4835
5234
|
...field.access || {},
|
|
4836
5235
|
// Must be an admin; cannot edit own roles (no self-elevation).
|
|
4837
|
-
update:
|
|
5236
|
+
update: adminOtherThanSelf
|
|
4838
5237
|
}
|
|
4839
5238
|
};
|
|
4840
5239
|
}
|
|
@@ -4843,7 +5242,7 @@ function normalizeConfig(config) {
|
|
|
4843
5242
|
...field,
|
|
4844
5243
|
access: {
|
|
4845
5244
|
...field.access || {},
|
|
4846
|
-
update:
|
|
5245
|
+
update: adminOtherThanSelf
|
|
4847
5246
|
}
|
|
4848
5247
|
};
|
|
4849
5248
|
}
|
|
@@ -4935,6 +5334,52 @@ function normalizeConfig(config) {
|
|
|
4935
5334
|
if (needsTrash && !normalizedCollections.some((col) => col.slug === TRASH_COLLECTION)) {
|
|
4936
5335
|
systemCollections.push(TRASH_COLLECTION_CONFIG);
|
|
4937
5336
|
}
|
|
5337
|
+
const needsEmailTemplates = schemaAwareConfig.email?.adminEditable === true;
|
|
5338
|
+
if (needsEmailTemplates && !normalizedCollections.some((col) => col.slug === EMAIL_TEMPLATES_COLLECTION_SLUG)) {
|
|
5339
|
+
const adminCol = getAdminAuthCollection(schemaAwareConfig);
|
|
5340
|
+
const customAccess = schemaAwareConfig.email?.access;
|
|
5341
|
+
const resolveOpAccess = (op) => {
|
|
5342
|
+
if (typeof customAccess === "function") return customAccess;
|
|
5343
|
+
if (customAccess && typeof customAccess === "object" && customAccess[op]) return customAccess[op];
|
|
5344
|
+
return ({ user }) => isUserAdmin(user, adminCol);
|
|
5345
|
+
};
|
|
5346
|
+
const customPurposes = schemaAwareConfig.email?.purposes ?? [];
|
|
5347
|
+
const purposeOptions = [
|
|
5348
|
+
{ label: "Invite", value: "invite" },
|
|
5349
|
+
{ label: "Password Reset", value: "resetPassword" },
|
|
5350
|
+
{ label: "Welcome", value: "welcome" },
|
|
5351
|
+
{ label: "Password Changed", value: "passwordChanged" },
|
|
5352
|
+
...customPurposes
|
|
5353
|
+
];
|
|
5354
|
+
const targetableCollections = normalizedCollections.filter(
|
|
5355
|
+
(c) => !c.slug.startsWith("__")
|
|
5356
|
+
);
|
|
5357
|
+
const audienceOptions = [
|
|
5358
|
+
{ label: "Global (All Collections)", value: "*" },
|
|
5359
|
+
...targetableCollections.map((c) => ({
|
|
5360
|
+
label: `${c.labels?.singular || c.labels?.plural || c.slug} (${c.slug})`,
|
|
5361
|
+
value: c.slug
|
|
5362
|
+
}))
|
|
5363
|
+
];
|
|
5364
|
+
systemCollections.push({
|
|
5365
|
+
...EMAIL_TEMPLATES_COLLECTION_CONFIG,
|
|
5366
|
+
fields: EMAIL_TEMPLATES_COLLECTION_CONFIG.fields.map((f) => {
|
|
5367
|
+
if (f.name === "collectionSlug") {
|
|
5368
|
+
return { ...f, options: audienceOptions };
|
|
5369
|
+
}
|
|
5370
|
+
if (f.name === "purpose") {
|
|
5371
|
+
return { ...f, options: purposeOptions };
|
|
5372
|
+
}
|
|
5373
|
+
return f;
|
|
5374
|
+
}),
|
|
5375
|
+
access: {
|
|
5376
|
+
read: resolveOpAccess("read"),
|
|
5377
|
+
create: resolveOpAccess("create"),
|
|
5378
|
+
update: resolveOpAccess("update"),
|
|
5379
|
+
delete: resolveOpAccess("delete")
|
|
5380
|
+
}
|
|
5381
|
+
});
|
|
5382
|
+
}
|
|
4938
5383
|
return {
|
|
4939
5384
|
...schemaAwareConfig,
|
|
4940
5385
|
collections: [...normalizedCollections, ...systemCollections],
|
|
@@ -5054,6 +5499,10 @@ function parseSqlWhere(where, getJsonField, placeholder = "?", likeOperator = pl
|
|
|
5054
5499
|
}
|
|
5055
5500
|
return `${c} = ${next()}`;
|
|
5056
5501
|
case "not_equals":
|
|
5502
|
+
case "not":
|
|
5503
|
+
case "ne":
|
|
5504
|
+
case "not_equal":
|
|
5505
|
+
case "notEquals":
|
|
5057
5506
|
if (operand === null) {
|
|
5058
5507
|
return `${c} IS NOT NULL`;
|
|
5059
5508
|
}
|
|
@@ -5069,9 +5518,33 @@ function parseSqlWhere(where, getJsonField, placeholder = "?", likeOperator = pl
|
|
|
5069
5518
|
} else {
|
|
5070
5519
|
params.push(operand);
|
|
5071
5520
|
}
|
|
5072
|
-
|
|
5073
|
-
|
|
5521
|
+
return `${c} != ${next()}`;
|
|
5522
|
+
}
|
|
5523
|
+
if (Array.isArray(operand)) {
|
|
5524
|
+
if (operand.length === 0) return "1=1";
|
|
5525
|
+
const placeholders = operand.map((v) => {
|
|
5526
|
+
if (typeof v === "boolean") {
|
|
5527
|
+
if (placeholder === "?") {
|
|
5528
|
+
params.push(v ? 1 : 0);
|
|
5529
|
+
} else if (isJsonExtract) {
|
|
5530
|
+
params.push(String(v));
|
|
5531
|
+
} else {
|
|
5532
|
+
params.push(v);
|
|
5533
|
+
}
|
|
5534
|
+
} else {
|
|
5535
|
+
params.push(v);
|
|
5536
|
+
}
|
|
5537
|
+
return next();
|
|
5538
|
+
});
|
|
5539
|
+
return `${c} NOT IN (${placeholders.join(", ")})`;
|
|
5074
5540
|
}
|
|
5541
|
+
if (typeof operand === "object") {
|
|
5542
|
+
const innerEntries = Object.entries(operand);
|
|
5543
|
+
if (innerEntries.length === 0) return "1=1";
|
|
5544
|
+
const innerSql = innerEntries.map(([innerOp, innerOperand]) => buildSingleOp(c, innerOp, innerOperand)).join(" AND ");
|
|
5545
|
+
return `NOT (${innerSql})`;
|
|
5546
|
+
}
|
|
5547
|
+
params.push(operand);
|
|
5075
5548
|
return `${c} != ${next()}`;
|
|
5076
5549
|
case "in": {
|
|
5077
5550
|
const vals = Array.isArray(operand) ? operand : [operand];
|
|
@@ -5142,6 +5615,15 @@ function parseSqlWhere(where, getJsonField, placeholder = "?", likeOperator = pl
|
|
|
5142
5615
|
} else if (upperField === "AND" && Array.isArray(value)) {
|
|
5143
5616
|
const sub = value.map((v) => `(${buildClause(v)})`).join(" AND ");
|
|
5144
5617
|
parts.push(`(${sub})`);
|
|
5618
|
+
} else if (upperField === "NOT") {
|
|
5619
|
+
if (Array.isArray(value)) {
|
|
5620
|
+
const sub = value.map((v) => `(${buildClause(v)})`).join(" AND ");
|
|
5621
|
+
parts.push(`(NOT (${sub}))`);
|
|
5622
|
+
} 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)) {
|
|
5623
|
+
parts.push(`(NOT (${buildClause(value)}))`);
|
|
5624
|
+
} else {
|
|
5625
|
+
parts.push(buildOperator(field, value));
|
|
5626
|
+
}
|
|
5145
5627
|
} else {
|
|
5146
5628
|
parts.push(buildOperator(field, value));
|
|
5147
5629
|
}
|
|
@@ -5172,7 +5654,28 @@ function parseMongoWhere(where) {
|
|
|
5172
5654
|
case "equals":
|
|
5173
5655
|
return { [field]: { $eq: operand } };
|
|
5174
5656
|
case "not_equals":
|
|
5657
|
+
case "not":
|
|
5658
|
+
case "ne":
|
|
5659
|
+
case "not_equal":
|
|
5660
|
+
case "notEquals": {
|
|
5661
|
+
if (Array.isArray(operand)) {
|
|
5662
|
+
return { [field]: { $nin: operand } };
|
|
5663
|
+
}
|
|
5664
|
+
if (typeof operand === "object" && operand !== null) {
|
|
5665
|
+
const innerEntries = Object.entries(operand);
|
|
5666
|
+
if (innerEntries.length === 1) {
|
|
5667
|
+
const [innerOp, innerOperand] = innerEntries[0];
|
|
5668
|
+
const inner = buildSingleOp(field, innerOp, innerOperand)[field];
|
|
5669
|
+
return { [field]: { $not: inner } };
|
|
5670
|
+
}
|
|
5671
|
+
const merged = {};
|
|
5672
|
+
for (const [innerOp, innerOperand] of innerEntries) {
|
|
5673
|
+
Object.assign(merged, buildSingleOp(field, innerOp, innerOperand)[field]);
|
|
5674
|
+
}
|
|
5675
|
+
return { [field]: { $not: merged } };
|
|
5676
|
+
}
|
|
5175
5677
|
return { [field]: { $ne: operand } };
|
|
5678
|
+
}
|
|
5176
5679
|
case "in":
|
|
5177
5680
|
return { [field]: { $in: Array.isArray(operand) ? operand : [operand] } };
|
|
5178
5681
|
case "not_in":
|
|
@@ -5210,6 +5713,14 @@ function parseMongoWhere(where) {
|
|
|
5210
5713
|
conditions.push({ $or: value.map(buildClause) });
|
|
5211
5714
|
} else if (upperField === "AND" && Array.isArray(value)) {
|
|
5212
5715
|
conditions.push({ $and: value.map(buildClause) });
|
|
5716
|
+
} else if (upperField === "NOT") {
|
|
5717
|
+
if (Array.isArray(value)) {
|
|
5718
|
+
conditions.push({ $nor: value.map(buildClause) });
|
|
5719
|
+
} 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)) {
|
|
5720
|
+
conditions.push({ $nor: [buildClause(value)] });
|
|
5721
|
+
} else {
|
|
5722
|
+
conditions.push(buildOperator(field, value));
|
|
5723
|
+
}
|
|
5213
5724
|
} else {
|
|
5214
5725
|
conditions.push(buildOperator(field, value));
|
|
5215
5726
|
}
|
|
@@ -5231,7 +5742,7 @@ function coerceBooleanWhere(where, fields) {
|
|
|
5231
5742
|
if (!node || typeof node !== "object") return node;
|
|
5232
5743
|
const out = {};
|
|
5233
5744
|
for (const [key, value] of Object.entries(node)) {
|
|
5234
|
-
if (key.toUpperCase() === "AND" || key.toUpperCase() === "OR") {
|
|
5745
|
+
if (key.toUpperCase() === "AND" || key.toUpperCase() === "OR" || key.toUpperCase() === "NOT") {
|
|
5235
5746
|
out[key] = walk(value);
|
|
5236
5747
|
} else if (booleanFields.has(key)) {
|
|
5237
5748
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
@@ -6262,6 +6773,31 @@ function generateOpenApi(config) {
|
|
|
6262
6773
|
spec.paths[`${path}/accept-invite`] = {
|
|
6263
6774
|
post: publicAuthPost("Accept an invitation")
|
|
6264
6775
|
};
|
|
6776
|
+
spec.paths[`${path}/tokens/verify`] = {
|
|
6777
|
+
get: {
|
|
6778
|
+
tags: [collectionTag],
|
|
6779
|
+
summary: `Verify an invite or password reset token`,
|
|
6780
|
+
parameters: [
|
|
6781
|
+
{
|
|
6782
|
+
name: "token",
|
|
6783
|
+
in: "query",
|
|
6784
|
+
required: true,
|
|
6785
|
+
schema: { type: "string" }
|
|
6786
|
+
},
|
|
6787
|
+
{
|
|
6788
|
+
name: "purpose",
|
|
6789
|
+
in: "query",
|
|
6790
|
+
required: false,
|
|
6791
|
+
schema: { type: "string", enum: ["invite", "reset"] }
|
|
6792
|
+
}
|
|
6793
|
+
],
|
|
6794
|
+
responses: {
|
|
6795
|
+
200: {
|
|
6796
|
+
description: "Token verification result"
|
|
6797
|
+
}
|
|
6798
|
+
}
|
|
6799
|
+
}
|
|
6800
|
+
};
|
|
6265
6801
|
spec.paths[`${path}/{id}/change-password`] = {
|
|
6266
6802
|
post: {
|
|
6267
6803
|
tags: [collectionTag],
|
|
@@ -7459,8 +7995,8 @@ function sanitizeDocForAI(options) {
|
|
|
7459
7995
|
// src/utils/index-name.ts
|
|
7460
7996
|
var import_node_crypto2 = require("crypto");
|
|
7461
7997
|
var MAX_INDEX_NAME_LENGTH = 60;
|
|
7462
|
-
function resolveIndexName(prefix,
|
|
7463
|
-
const full = `${prefix}_${
|
|
7998
|
+
function resolveIndexName(prefix, table2, fields) {
|
|
7999
|
+
const full = `${prefix}_${table2}_${fields.join("_")}`;
|
|
7464
8000
|
if (full.length <= MAX_INDEX_NAME_LENGTH) return full;
|
|
7465
8001
|
const hash = (0, import_node_crypto2.createHash)("sha1").update(full).digest("hex").slice(0, 10);
|
|
7466
8002
|
const tail = (fields[fields.length - 1] ?? "").slice(0, 30);
|
|
@@ -7932,7 +8468,7 @@ function compileNavigation(config) {
|
|
|
7932
8468
|
const mentionedTrash = explicitNav.some((i) => i.trash || i.slug === "trash");
|
|
7933
8469
|
if (!mentionedTrash) {
|
|
7934
8470
|
const hasTrash = collections.some(
|
|
7935
|
-
(col) => !col.slug
|
|
8471
|
+
(col) => !INTERNAL_SYSTEM_COLLECTIONS.has(col.slug) && resolveTrashConfig(col, config).enabled
|
|
7936
8472
|
);
|
|
7937
8473
|
if (hasTrash) {
|
|
7938
8474
|
registerItem(void 0, {
|
|
@@ -8263,8 +8799,11 @@ function defineTab(args) {
|
|
|
8263
8799
|
DuplicateKeyError,
|
|
8264
8800
|
DyrectedAIError,
|
|
8265
8801
|
DyrectedError,
|
|
8802
|
+
EMAIL_TEMPLATES_COLLECTION_CONFIG,
|
|
8803
|
+
EMAIL_TEMPLATES_COLLECTION_SLUG,
|
|
8266
8804
|
FieldConditionBuilder,
|
|
8267
8805
|
ForbiddenError,
|
|
8806
|
+
INTERNAL_SYSTEM_COLLECTIONS,
|
|
8268
8807
|
LIFECYCLE_EVENTS_COLLECTION,
|
|
8269
8808
|
LIFECYCLE_EVENT_NAMES,
|
|
8270
8809
|
MatchBuilder,
|
|
@@ -8282,6 +8821,7 @@ function defineTab(args) {
|
|
|
8282
8821
|
WORKFLOW_HISTORY_COLLECTION,
|
|
8283
8822
|
abs,
|
|
8284
8823
|
addDays,
|
|
8824
|
+
alertBox,
|
|
8285
8825
|
asCollection,
|
|
8286
8826
|
assertValidAdminConditionsInConfig,
|
|
8287
8827
|
assertValidDeclarativeAccessInConfig,
|
|
@@ -8311,6 +8851,7 @@ function defineTab(args) {
|
|
|
8311
8851
|
createTaskRunner,
|
|
8312
8852
|
createTrashPurgeTask,
|
|
8313
8853
|
createWorkflowDocument,
|
|
8854
|
+
ctaButton,
|
|
8314
8855
|
defaultValue,
|
|
8315
8856
|
defineAction,
|
|
8316
8857
|
defineArrayField,
|
|
@@ -8357,6 +8898,7 @@ function defineTab(args) {
|
|
|
8357
8898
|
defineUrlField,
|
|
8358
8899
|
defineView,
|
|
8359
8900
|
defineWorkspace,
|
|
8901
|
+
detailBox,
|
|
8360
8902
|
diffDays,
|
|
8361
8903
|
dispatchLifecycleEvent,
|
|
8362
8904
|
dispatchPendingLifecycleEvents,
|
|
@@ -8372,10 +8914,14 @@ function defineTab(args) {
|
|
|
8372
8914
|
displayTab,
|
|
8373
8915
|
displayTabs,
|
|
8374
8916
|
displayText,
|
|
8917
|
+
divider,
|
|
8918
|
+
emailTokens,
|
|
8375
8919
|
endsWith,
|
|
8920
|
+
escapeHtml,
|
|
8376
8921
|
evaluateDetailComputed,
|
|
8377
8922
|
evaluateJexl,
|
|
8378
8923
|
evaluateJexlSync,
|
|
8924
|
+
eventList,
|
|
8379
8925
|
executeFieldAfterRead,
|
|
8380
8926
|
executeFieldBeforeChange,
|
|
8381
8927
|
expr,
|
|
@@ -8392,8 +8938,10 @@ function defineTab(args) {
|
|
|
8392
8938
|
getAdminAuthCollection,
|
|
8393
8939
|
getAdminRoleForCollection,
|
|
8394
8940
|
getAllowedSitesForUser,
|
|
8941
|
+
getDefaultEmailTemplate,
|
|
8395
8942
|
getPublicAdminAuthConfig,
|
|
8396
8943
|
getTrashEntryId,
|
|
8944
|
+
heading,
|
|
8397
8945
|
includes,
|
|
8398
8946
|
initializeWorkflowDocument,
|
|
8399
8947
|
isAICollection,
|
|
@@ -8406,6 +8954,7 @@ function defineTab(args) {
|
|
|
8406
8954
|
isUserAdmin,
|
|
8407
8955
|
join,
|
|
8408
8956
|
last,
|
|
8957
|
+
layout,
|
|
8409
8958
|
length,
|
|
8410
8959
|
lower,
|
|
8411
8960
|
maskEmail,
|
|
@@ -8421,6 +8970,7 @@ function defineTab(args) {
|
|
|
8421
8970
|
normalizeGroupKey,
|
|
8422
8971
|
now,
|
|
8423
8972
|
numericFieldNames,
|
|
8973
|
+
paragraph,
|
|
8424
8974
|
parseCron,
|
|
8425
8975
|
parseMongoWhere,
|
|
8426
8976
|
parseSort,
|
|
@@ -8437,21 +8987,27 @@ function defineTab(args) {
|
|
|
8437
8987
|
resolveAdminAuthCollection,
|
|
8438
8988
|
resolveAllCollectionViews,
|
|
8439
8989
|
resolveAuthorizedSiteId,
|
|
8990
|
+
resolveBlocks,
|
|
8440
8991
|
resolveDocumentTitle,
|
|
8441
8992
|
resolveIndexName,
|
|
8442
8993
|
resolvePrefix,
|
|
8443
8994
|
resolveTrashConfig,
|
|
8444
8995
|
restoreDocument,
|
|
8445
8996
|
round,
|
|
8997
|
+
row,
|
|
8446
8998
|
runCollectionHooks,
|
|
8999
|
+
safeLinkDetailBox,
|
|
8447
9000
|
sanitizeDocForAI,
|
|
8448
9001
|
saveWorkflowDraft,
|
|
9002
|
+
sectionLabel,
|
|
8449
9003
|
simplePublishingWorkflow,
|
|
8450
9004
|
slugify,
|
|
8451
9005
|
softDeleteDocument,
|
|
9006
|
+
spacer,
|
|
8452
9007
|
sqrt,
|
|
8453
9008
|
startsWith,
|
|
8454
9009
|
sum,
|
|
9010
|
+
table,
|
|
8455
9011
|
today,
|
|
8456
9012
|
transitionWorkflow,
|
|
8457
9013
|
trim,
|