@tetherto/dev-websites-core 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cms/index.d.mts +2 -2
- package/dist/cms/index.mjs +2 -2
- package/dist/cms.mjs +514 -4
- package/dist/cms.mjs.map +1 -1
- package/dist/index.d.mts +113 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index5.d.mts +2 -2
- package/package.json +1 -1
package/dist/cms/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { $ as createAccessHelpers, A as downloadCtaBlock, B as seoFields, C as codeBlockLanguages, D as sectionHeadingBlock, E as leafBlocks, F as richTextBlock, G as STOCK_COLLECTION_SLUGS, H as BuildCmsConfigOptions, I as coerceBlogPostCategoriesOnRead, J as GlobalOverride, K as StockCollectionSlug, L as coerceBlogPostCategoriesOnWrite, M as imageBlock, N as codeTabsBlock, O as accordionBlock, P as codeBlock, Q as AccessOptions, R as setPublishedAt, S as showcaseContentBlocks, T as languageOptions, U as buildCmsConfig, V as draftsWithAutosave, W as Overrides, X as applyGlobalOverride, Y as applyCollectionOverride, Z as AccessHelpers, _ as requireEnvAtRuntime, a as createChangelog, b as blogContentBlocks, c as createPages, d as createAuthors, f as createMedia, g as createS3MediaPlugin, h as CreateS3MediaPluginOptions, i as createEventTags, j as videoBlock, k as stepsBlock, l as createBlogPosts, m as createUsers, n as createSiteSettings, o as createChangelogTags, p as createApiTokens, q as CollectionOverride, r as createEvents, s as createLegalPages, t as createAnnouncementBanner, u as createCategories, v as getRichTextEditorConfig, w as defaultCodeLanguage, x as pageContentBlocks, y as richTextEditor, z as slugify } from "../index.mjs";
|
|
1
|
+
import { $ as createAccessHelpers, A as downloadCtaBlock, B as seoFields, C as codeBlockLanguages, D as sectionHeadingBlock, E as leafBlocks, F as richTextBlock, G as STOCK_COLLECTION_SLUGS, H as BuildCmsConfigOptions, I as coerceBlogPostCategoriesOnRead, J as GlobalOverride, K as StockCollectionSlug, L as coerceBlogPostCategoriesOnWrite, M as imageBlock, N as codeTabsBlock, O as accordionBlock, P as codeBlock, Q as AccessOptions, R as setPublishedAt, S as showcaseContentBlocks, T as languageOptions, U as buildCmsConfig, V as draftsWithAutosave, W as Overrides, X as applyGlobalOverride, Y as applyCollectionOverride, Z as AccessHelpers, _ as requireEnvAtRuntime, _t as writeAccess, a as createChangelog, at as RoleRule, b as blogContentBlocks, c as createPages, ct as collectionRoleRule, d as createAuthors, dt as hiddenUnless, et as ADMIN_ROLE, f as createMedia, ft as isCmsAdmin, g as createS3MediaPlugin, gt as withRoles, h as CreateS3MediaPluginOptions, ht as userHasAnyRole, i as createEventTags, it as RoleOption, j as videoBlock, k as stepsBlock, l as createBlogPosts, lt as effectiveRoles, m as createUsers, mt as resolveRoleOptions, n as createSiteSettings, nt as DEFAULT_GLOBAL_ROLES, o as createChangelogTags, ot as RolesOptions, p as createApiTokens, pt as publishedOrRoleRead, q as CollectionOverride, r as createEvents, rt as DEFAULT_ROLES, s as createLegalPages, st as adminRoleAccess, t as createAnnouncementBanner, tt as DEFAULT_COLLECTION_ROLES, u as createCategories, ut as globalRoleRule, v as getRichTextEditorConfig, w as defaultCodeLanguage, x as pageContentBlocks, y as richTextEditor, z as slugify } from "../index.mjs";
|
|
2
2
|
import { n as readPayloadMediaAsDataUrl, r as readPayloadMediaBuffer, t as PayloadMedia } from "../readMediaBuffer.mjs";
|
|
3
|
-
export { type AccessHelpers, type AccessOptions, type BuildCmsConfigOptions, type CollectionOverride, type CreateS3MediaPluginOptions, type GlobalOverride, type Overrides, type PayloadMedia, STOCK_COLLECTION_SLUGS, type StockCollectionSlug, accordionBlock, applyCollectionOverride, applyGlobalOverride, blogContentBlocks, buildCmsConfig, codeBlock, codeBlockLanguages, codeTabsBlock, coerceBlogPostCategoriesOnRead, coerceBlogPostCategoriesOnWrite, createAccessHelpers, createAnnouncementBanner, createApiTokens, createAuthors, createBlogPosts, createCategories, createChangelog, createChangelogTags, createEventTags, createEvents, createLegalPages, createMedia, createPages, createS3MediaPlugin, createSiteSettings, createUsers, defaultCodeLanguage, downloadCtaBlock, draftsWithAutosave, getRichTextEditorConfig, imageBlock, languageOptions, leafBlocks, pageContentBlocks, readPayloadMediaAsDataUrl, readPayloadMediaBuffer, requireEnvAtRuntime, richTextBlock, richTextEditor, sectionHeadingBlock, seoFields, setPublishedAt, showcaseContentBlocks, slugify, stepsBlock, videoBlock };
|
|
3
|
+
export { ADMIN_ROLE, type AccessHelpers, type AccessOptions, type BuildCmsConfigOptions, type CollectionOverride, type CreateS3MediaPluginOptions, DEFAULT_COLLECTION_ROLES, DEFAULT_GLOBAL_ROLES, DEFAULT_ROLES, type GlobalOverride, type Overrides, type PayloadMedia, type RoleOption, type RoleRule, type RolesOptions, STOCK_COLLECTION_SLUGS, type StockCollectionSlug, accordionBlock, adminRoleAccess, applyCollectionOverride, applyGlobalOverride, blogContentBlocks, buildCmsConfig, codeBlock, codeBlockLanguages, codeTabsBlock, coerceBlogPostCategoriesOnRead, coerceBlogPostCategoriesOnWrite, collectionRoleRule, createAccessHelpers, createAnnouncementBanner, createApiTokens, createAuthors, createBlogPosts, createCategories, createChangelog, createChangelogTags, createEventTags, createEvents, createLegalPages, createMedia, createPages, createS3MediaPlugin, createSiteSettings, createUsers, defaultCodeLanguage, downloadCtaBlock, draftsWithAutosave, effectiveRoles, getRichTextEditorConfig, globalRoleRule, hiddenUnless, imageBlock, isCmsAdmin, languageOptions, leafBlocks, pageContentBlocks, publishedOrRoleRead, readPayloadMediaAsDataUrl, readPayloadMediaBuffer, requireEnvAtRuntime, resolveRoleOptions, richTextBlock, richTextEditor, sectionHeadingBlock, seoFields, setPublishedAt, showcaseContentBlocks, slugify, stepsBlock, userHasAnyRole, videoBlock, withRoles, writeAccess };
|
package/dist/cms/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { A as richTextBlock, B as languageOptions, C as blogContentBlocks, D as accordionBlock, E as sectionHeadingBlock, F as downloadCtaBlock, G as
|
|
1
|
+
import { $ as hiddenUnless, A as richTextBlock, B as languageOptions, C as blogContentBlocks, D as accordionBlock, E as sectionHeadingBlock, F as downloadCtaBlock, G as ADMIN_ROLE, H as seoFields, I as codeTabsBlock, J as DEFAULT_ROLES, K as DEFAULT_COLLECTION_ROLES, L as codeBlock, M as richTextEditor, N as videoBlock, O as leafBlocks, P as imageBlock, Q as globalRoleRule, R as codeBlockLanguages, S as coerceBlogPostCategoriesOnWrite, T as showcaseContentBlocks, U as slugify, V as createAuthors, W as createApiTokens, X as collectionRoleRule, Y as adminRoleAccess, Z as effectiveRoles, _ as createCategories, a as STOCK_COLLECTION_SLUGS, at as writeAccess, b as setPublishedAt, c as createSiteSettings, d as createPages, et as isCmsAdmin, f as createMedia, g as createChangelog, h as createChangelogTags, i as buildCmsConfig, it as withRoles, j as getRichTextEditorConfig, k as stepsBlock, l as createUsers, m as createEventTags, n as createS3MediaPlugin, nt as resolveRoleOptions, o as applyCollectionOverride, ot as createAccessHelpers, p as createEvents, q as DEFAULT_GLOBAL_ROLES, r as requireEnvAtRuntime, rt as userHasAnyRole, s as applyGlobalOverride, t as createAnnouncementBanner, tt as publishedOrRoleRead, u as createLegalPages, v as createBlogPosts, w as pageContentBlocks, x as coerceBlogPostCategoriesOnRead, y as draftsWithAutosave, z as defaultCodeLanguage } from "../cms.mjs";
|
|
2
2
|
import { n as readPayloadMediaBuffer, t as readPayloadMediaAsDataUrl } from "../readMediaBuffer.mjs";
|
|
3
|
-
export { STOCK_COLLECTION_SLUGS, accordionBlock, applyCollectionOverride, applyGlobalOverride, blogContentBlocks, buildCmsConfig, codeBlock, codeBlockLanguages, codeTabsBlock, coerceBlogPostCategoriesOnRead, coerceBlogPostCategoriesOnWrite, createAccessHelpers, createAnnouncementBanner, createApiTokens, createAuthors, createBlogPosts, createCategories, createChangelog, createChangelogTags, createEventTags, createEvents, createLegalPages, createMedia, createPages, createS3MediaPlugin, createSiteSettings, createUsers, defaultCodeLanguage, downloadCtaBlock, draftsWithAutosave, getRichTextEditorConfig, imageBlock, languageOptions, leafBlocks, pageContentBlocks, readPayloadMediaAsDataUrl, readPayloadMediaBuffer, requireEnvAtRuntime, richTextBlock, richTextEditor, sectionHeadingBlock, seoFields, setPublishedAt, showcaseContentBlocks, slugify, stepsBlock, videoBlock };
|
|
3
|
+
export { ADMIN_ROLE, DEFAULT_COLLECTION_ROLES, DEFAULT_GLOBAL_ROLES, DEFAULT_ROLES, STOCK_COLLECTION_SLUGS, accordionBlock, adminRoleAccess, applyCollectionOverride, applyGlobalOverride, blogContentBlocks, buildCmsConfig, codeBlock, codeBlockLanguages, codeTabsBlock, coerceBlogPostCategoriesOnRead, coerceBlogPostCategoriesOnWrite, collectionRoleRule, createAccessHelpers, createAnnouncementBanner, createApiTokens, createAuthors, createBlogPosts, createCategories, createChangelog, createChangelogTags, createEventTags, createEvents, createLegalPages, createMedia, createPages, createS3MediaPlugin, createSiteSettings, createUsers, defaultCodeLanguage, downloadCtaBlock, draftsWithAutosave, effectiveRoles, getRichTextEditorConfig, globalRoleRule, hiddenUnless, imageBlock, isCmsAdmin, languageOptions, leafBlocks, pageContentBlocks, publishedOrRoleRead, readPayloadMediaAsDataUrl, readPayloadMediaBuffer, requireEnvAtRuntime, resolveRoleOptions, richTextBlock, richTextEditor, sectionHeadingBlock, seoFields, setPublishedAt, showcaseContentBlocks, slugify, stepsBlock, userHasAnyRole, videoBlock, withRoles, writeAccess };
|
package/dist/cms.mjs
CHANGED
|
@@ -30,6 +30,370 @@ function createAccessHelpers(options = {}) {
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
//#endregion
|
|
33
|
+
//#region src/cms/shared/roles.ts
|
|
34
|
+
const ADMIN_ROLE = "admin";
|
|
35
|
+
const CMS_ROLES_SLUG = "cms-roles";
|
|
36
|
+
const moderator = ["moderator"];
|
|
37
|
+
const developer = ["developer"];
|
|
38
|
+
const moderatorAndDeveloper = ["moderator", "developer"];
|
|
39
|
+
const DEFAULT_ROLES = [
|
|
40
|
+
{
|
|
41
|
+
label: "Admin",
|
|
42
|
+
value: ADMIN_ROLE
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
label: "Moderator",
|
|
46
|
+
value: "moderator"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
label: "Developer",
|
|
50
|
+
value: "developer"
|
|
51
|
+
}
|
|
52
|
+
];
|
|
53
|
+
const DEFAULT_COLLECTION_ROLES = {
|
|
54
|
+
"api-tokens": false,
|
|
55
|
+
"media": { roles: moderatorAndDeveloper },
|
|
56
|
+
"authors": { roles: moderator },
|
|
57
|
+
"categories": { roles: moderator },
|
|
58
|
+
"blog-posts": {
|
|
59
|
+
roles: moderator,
|
|
60
|
+
drafts: true
|
|
61
|
+
},
|
|
62
|
+
"pages": {
|
|
63
|
+
roles: moderator,
|
|
64
|
+
drafts: true
|
|
65
|
+
},
|
|
66
|
+
"legal-pages": {
|
|
67
|
+
roles: moderator,
|
|
68
|
+
drafts: true
|
|
69
|
+
},
|
|
70
|
+
"changelog-tags": { roles: developer },
|
|
71
|
+
"changelog": {
|
|
72
|
+
roles: developer,
|
|
73
|
+
drafts: true
|
|
74
|
+
},
|
|
75
|
+
"event-tags": { roles: moderator },
|
|
76
|
+
"events": {
|
|
77
|
+
roles: moderator,
|
|
78
|
+
drafts: true
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const DEFAULT_GLOBAL_ROLES = {
|
|
82
|
+
"site-settings": false,
|
|
83
|
+
"announcement-banner": { roles: moderator }
|
|
84
|
+
};
|
|
85
|
+
let roleCache = null;
|
|
86
|
+
function asCmsUser(user) {
|
|
87
|
+
if (!user || typeof user !== "object") return null;
|
|
88
|
+
const record = user;
|
|
89
|
+
if (record.collection !== "users") return null;
|
|
90
|
+
return record;
|
|
91
|
+
}
|
|
92
|
+
function roleRefs(roles) {
|
|
93
|
+
if (!Array.isArray(roles)) return {
|
|
94
|
+
ids: [],
|
|
95
|
+
slugs: []
|
|
96
|
+
};
|
|
97
|
+
const ids = [];
|
|
98
|
+
const slugs = [];
|
|
99
|
+
roles.forEach((role) => {
|
|
100
|
+
if (typeof role === "string" || typeof role === "number") {
|
|
101
|
+
const value = String(role);
|
|
102
|
+
if (/^[a-f0-9]{24}$/i.test(value)) ids.push(value);
|
|
103
|
+
else if (value) slugs.push(value);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (role && typeof role === "object" && "id" in role && role.id != null) ids.push(String(role.id));
|
|
107
|
+
});
|
|
108
|
+
return {
|
|
109
|
+
ids,
|
|
110
|
+
slugs
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function readPermissions(value) {
|
|
114
|
+
if (!Array.isArray(value)) return [];
|
|
115
|
+
return value.flatMap((entry) => {
|
|
116
|
+
if (!entry || typeof entry !== "object" || !("target" in entry)) return [];
|
|
117
|
+
const row = entry;
|
|
118
|
+
if (!row.target) return [];
|
|
119
|
+
return [{
|
|
120
|
+
target: row.target,
|
|
121
|
+
create: Boolean(row.create),
|
|
122
|
+
update: Boolean(row.update),
|
|
123
|
+
delete: Boolean(row.delete),
|
|
124
|
+
drafts: Boolean(row.drafts)
|
|
125
|
+
}];
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
async function refreshRoleCache(payload) {
|
|
129
|
+
const found = await payload.find({
|
|
130
|
+
collection: CMS_ROLES_SLUG,
|
|
131
|
+
depth: 0,
|
|
132
|
+
limit: 500,
|
|
133
|
+
pagination: false,
|
|
134
|
+
overrideAccess: true
|
|
135
|
+
});
|
|
136
|
+
const byId = /* @__PURE__ */ new Map();
|
|
137
|
+
const bySlug = /* @__PURE__ */ new Map();
|
|
138
|
+
found.docs.forEach((doc) => {
|
|
139
|
+
const record = doc;
|
|
140
|
+
if (!record.slug) return;
|
|
141
|
+
const role = {
|
|
142
|
+
id: String(record.id),
|
|
143
|
+
name: record.name ?? record.slug,
|
|
144
|
+
slug: record.slug,
|
|
145
|
+
fullAccess: Boolean(record.fullAccess) || record.slug === "admin",
|
|
146
|
+
permissions: readPermissions(record.permissions)
|
|
147
|
+
};
|
|
148
|
+
byId.set(role.id, role);
|
|
149
|
+
bySlug.set(role.slug, role);
|
|
150
|
+
});
|
|
151
|
+
roleCache = {
|
|
152
|
+
byId,
|
|
153
|
+
bySlug
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
async function ensureRoleCache(payload) {
|
|
157
|
+
if (!roleCache) await refreshRoleCache(payload);
|
|
158
|
+
return roleCache ?? {
|
|
159
|
+
byId: /* @__PURE__ */ new Map(),
|
|
160
|
+
bySlug: /* @__PURE__ */ new Map()
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function lookupRoles(refs, cache) {
|
|
164
|
+
return [...refs.ids.map((id) => cache.byId.get(id)), ...refs.slugs.map((slug) => cache.bySlug.get(slug))].filter((role) => Boolean(role));
|
|
165
|
+
}
|
|
166
|
+
function syncAssignment(user) {
|
|
167
|
+
const record = asCmsUser(user);
|
|
168
|
+
if (!record) return "guest";
|
|
169
|
+
const refs = roleRefs(record.roles);
|
|
170
|
+
if (refs.ids.length === 0 && refs.slugs.length === 0) return "admin";
|
|
171
|
+
if (!roleCache) return "unknown";
|
|
172
|
+
return lookupRoles(refs, roleCache);
|
|
173
|
+
}
|
|
174
|
+
async function loadAssignment(req) {
|
|
175
|
+
const record = asCmsUser(req.user);
|
|
176
|
+
if (!record) return "guest";
|
|
177
|
+
const refs = roleRefs(record.roles);
|
|
178
|
+
if (refs.ids.length === 0 && refs.slugs.length === 0) return "admin";
|
|
179
|
+
return lookupRoles(refs, await ensureRoleCache(req.payload));
|
|
180
|
+
}
|
|
181
|
+
function hasPermission(roles, target, action) {
|
|
182
|
+
if (roles.some((role) => role.fullAccess)) return true;
|
|
183
|
+
return roles.some((role) => role.permissions.some((permission) => permission.target === target && permission[action]));
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* `null` means this user has no roles yet and is treated as admin.
|
|
187
|
+
* `true` / `false` is the cached decision. A cold cache returns `null` so the
|
|
188
|
+
* nav stays visible until startup finishes loading roles.
|
|
189
|
+
*/
|
|
190
|
+
function cachedPermission(user, target, action) {
|
|
191
|
+
const assigned = syncAssignment(user);
|
|
192
|
+
if (assigned === "guest") return false;
|
|
193
|
+
if (assigned === "admin") return true;
|
|
194
|
+
if (assigned === "unknown") return null;
|
|
195
|
+
return hasPermission(assigned, target, action);
|
|
196
|
+
}
|
|
197
|
+
async function userIsFullAdmin(req) {
|
|
198
|
+
const assigned = await loadAssignment(req);
|
|
199
|
+
if (assigned === "guest") return false;
|
|
200
|
+
if (assigned === "admin") return true;
|
|
201
|
+
return assigned.some((role) => role.fullAccess);
|
|
202
|
+
}
|
|
203
|
+
async function userCan(req, target, action) {
|
|
204
|
+
const assigned = await loadAssignment(req);
|
|
205
|
+
if (assigned === "guest") return false;
|
|
206
|
+
if (assigned === "admin") return true;
|
|
207
|
+
return hasPermission(assigned, target, action);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Users created before the roles field existed have none stored.
|
|
211
|
+
* Treat that as admin so turning roles on does not lock the panel.
|
|
212
|
+
*/
|
|
213
|
+
function effectiveRoles(user) {
|
|
214
|
+
const assigned = syncAssignment(user);
|
|
215
|
+
if (assigned === "admin") return [ADMIN_ROLE];
|
|
216
|
+
if (assigned === "guest" || assigned === "unknown") return [];
|
|
217
|
+
return assigned.map((role) => role.slug);
|
|
218
|
+
}
|
|
219
|
+
function userHasAnyRole(user, roles) {
|
|
220
|
+
const assigned = effectiveRoles(user);
|
|
221
|
+
if (assigned.includes("admin")) return true;
|
|
222
|
+
return roles.some((role) => assigned.includes(role));
|
|
223
|
+
}
|
|
224
|
+
function isCmsAdmin(user) {
|
|
225
|
+
const assigned = syncAssignment(user);
|
|
226
|
+
if (assigned === "admin") return true;
|
|
227
|
+
if (assigned === "guest" || assigned === "unknown") return false;
|
|
228
|
+
return assigned.some((role) => role.fullAccess);
|
|
229
|
+
}
|
|
230
|
+
const adminRoleAccess = ({ req }) => userIsFullAdmin(req);
|
|
231
|
+
function hiddenUnless(roles) {
|
|
232
|
+
return ({ user }) => !userHasAnyRole(user, roles);
|
|
233
|
+
}
|
|
234
|
+
function writeAccess(roles) {
|
|
235
|
+
return ({ req }) => userHasAnyRole(req.user, roles);
|
|
236
|
+
}
|
|
237
|
+
function publishedOrRoleRead(roles) {
|
|
238
|
+
return ({ req }) => {
|
|
239
|
+
if (userHasAnyRole(req.user, roles)) return true;
|
|
240
|
+
return { _status: { equals: "published" } };
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const selfOrAdminAccess = async ({ req }) => {
|
|
244
|
+
if (await userIsFullAdmin(req)) return true;
|
|
245
|
+
const record = asCmsUser(req.user);
|
|
246
|
+
if (record?.id == null) return false;
|
|
247
|
+
return { id: { equals: record.id } };
|
|
248
|
+
};
|
|
249
|
+
const manageRolesAccess = ({ req }) => userIsFullAdmin(req);
|
|
250
|
+
function hiddenFor(target) {
|
|
251
|
+
return ({ user }) => {
|
|
252
|
+
const create = cachedPermission(user, target, "create");
|
|
253
|
+
const update = cachedPermission(user, target, "update");
|
|
254
|
+
const drafts = cachedPermission(user, target, "drafts");
|
|
255
|
+
if (create == null || update == null || drafts == null) return false;
|
|
256
|
+
return !(create || update || drafts);
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function hiddenUnlessFullAdmin() {
|
|
260
|
+
return ({ user }) => {
|
|
261
|
+
const assigned = syncAssignment(user);
|
|
262
|
+
if (assigned === "unknown" || assigned === "admin") return false;
|
|
263
|
+
if (assigned === "guest") return true;
|
|
264
|
+
return !assigned.some((role) => role.fullAccess);
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function resolveRoleOptions(options = {}) {
|
|
268
|
+
const custom = options.roles?.filter((role) => role.value !== "admin") ?? DEFAULT_ROLES.filter((role) => role.value !== "admin");
|
|
269
|
+
const merged = /* @__PURE__ */ new Map([[ADMIN_ROLE, {
|
|
270
|
+
label: "Admin",
|
|
271
|
+
value: ADMIN_ROLE
|
|
272
|
+
}]]);
|
|
273
|
+
[...custom, ...options.extraRoles ?? []].forEach((role) => {
|
|
274
|
+
if (!role.value || role.value === "admin") return;
|
|
275
|
+
merged.set(role.value, {
|
|
276
|
+
label: role.label,
|
|
277
|
+
value: role.value
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
return [...merged.values()];
|
|
281
|
+
}
|
|
282
|
+
function collectionRoleRule(options, slug, fallback) {
|
|
283
|
+
return roleRule(options, "collections", slug, fallback);
|
|
284
|
+
}
|
|
285
|
+
function globalRoleRule(options, slug, fallback) {
|
|
286
|
+
return roleRule(options, "globals", slug, fallback);
|
|
287
|
+
}
|
|
288
|
+
function roleRule(options, kind, slug, fallback) {
|
|
289
|
+
const overrides = (options === true || options == null ? {} : options)[kind];
|
|
290
|
+
if (overrides && Object.prototype.hasOwnProperty.call(overrides, slug)) return overrides[slug];
|
|
291
|
+
const defaults = kind === "collections" ? DEFAULT_COLLECTION_ROLES : DEFAULT_GLOBAL_ROLES;
|
|
292
|
+
if (Object.prototype.hasOwnProperty.call(defaults, slug)) return defaults[slug];
|
|
293
|
+
return fallback;
|
|
294
|
+
}
|
|
295
|
+
function rolesField() {
|
|
296
|
+
return {
|
|
297
|
+
name: "roles",
|
|
298
|
+
type: "relationship",
|
|
299
|
+
relationTo: CMS_ROLES_SLUG,
|
|
300
|
+
hasMany: true,
|
|
301
|
+
saveToJWT: true,
|
|
302
|
+
access: {
|
|
303
|
+
create: manageRolesAccess,
|
|
304
|
+
update: manageRolesAccess
|
|
305
|
+
},
|
|
306
|
+
admin: {
|
|
307
|
+
position: "sidebar",
|
|
308
|
+
description: "Only an admin can change roles. Several roles can be combined. Empty means Admin until a role is saved. The user must sign in again before a change applies."
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function permissionFor(target, rule, role, collection) {
|
|
313
|
+
if (rule == null || rule === false || !rule.roles.includes(role)) return null;
|
|
314
|
+
return {
|
|
315
|
+
target,
|
|
316
|
+
create: collection,
|
|
317
|
+
update: true,
|
|
318
|
+
delete: false,
|
|
319
|
+
drafts: collection && rule.drafts === true
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
/** Targets shown in the Roles editor: stock defaults plus anything the app configures. */
|
|
323
|
+
function roleTargetSlugs(options = {}) {
|
|
324
|
+
const targets = /* @__PURE__ */ new Set([
|
|
325
|
+
...Object.keys(DEFAULT_COLLECTION_ROLES),
|
|
326
|
+
...Object.keys(DEFAULT_GLOBAL_ROLES),
|
|
327
|
+
...Object.keys(options.collections ?? {}),
|
|
328
|
+
...Object.keys(options.globals ?? {})
|
|
329
|
+
]);
|
|
330
|
+
targets.delete("users");
|
|
331
|
+
targets.delete(CMS_ROLES_SLUG);
|
|
332
|
+
return [...targets].sort();
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Default role documents. Existing documents with the same slug are left alone,
|
|
336
|
+
* so edits made in the admin survive a restart.
|
|
337
|
+
*/
|
|
338
|
+
function buildRoleSeeds(options = {}) {
|
|
339
|
+
const roles = resolveRoleOptions(options);
|
|
340
|
+
const targets = roleTargetSlugs(options);
|
|
341
|
+
return roles.map((role) => {
|
|
342
|
+
if (role.value === "admin") return {
|
|
343
|
+
name: role.label,
|
|
344
|
+
slug: role.value,
|
|
345
|
+
fullAccess: true,
|
|
346
|
+
permissions: []
|
|
347
|
+
};
|
|
348
|
+
const permissions = targets.flatMap((target) => {
|
|
349
|
+
const collection = collectionRoleRule(options, target);
|
|
350
|
+
const global = globalRoleRule(options, target);
|
|
351
|
+
const row = permissionFor(target, collection !== void 0 ? collection : global, role.value, collection !== void 0);
|
|
352
|
+
return row ? [row] : [];
|
|
353
|
+
});
|
|
354
|
+
return {
|
|
355
|
+
name: role.label,
|
|
356
|
+
slug: role.value,
|
|
357
|
+
fullAccess: false,
|
|
358
|
+
permissions
|
|
359
|
+
};
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
function isCollectionConfig(config) {
|
|
363
|
+
if ("auth" in config || "upload" in config || "labels" in config || "defaultSort" in config) return true;
|
|
364
|
+
return Boolean(config.access && ("create" in config.access || "delete" in config.access));
|
|
365
|
+
}
|
|
366
|
+
/** Limits create, update, and delete from the Roles collection. Admin is always allowed. */
|
|
367
|
+
function withRoles(config) {
|
|
368
|
+
const collection = isCollectionConfig(config);
|
|
369
|
+
const users = collection && Boolean(config.auth);
|
|
370
|
+
const slug = config.slug;
|
|
371
|
+
const versioned = "versions" in config && Boolean(config.versions);
|
|
372
|
+
return {
|
|
373
|
+
...config,
|
|
374
|
+
admin: {
|
|
375
|
+
...config.admin,
|
|
376
|
+
...users ? { hidden: hiddenUnlessFullAdmin() } : { hidden: hiddenFor(slug) }
|
|
377
|
+
},
|
|
378
|
+
access: {
|
|
379
|
+
...config.access,
|
|
380
|
+
...collection ? {
|
|
381
|
+
create: users ? adminRoleAccess : ({ req }) => userCan(req, slug, "create"),
|
|
382
|
+
delete: users ? adminRoleAccess : ({ req }) => userCan(req, slug, "delete")
|
|
383
|
+
} : {},
|
|
384
|
+
...users ? {
|
|
385
|
+
read: selfOrAdminAccess,
|
|
386
|
+
update: selfOrAdminAccess
|
|
387
|
+
} : { update: ({ req }) => userCan(req, slug, "update") },
|
|
388
|
+
...versioned && !users ? { read: async ({ req }) => {
|
|
389
|
+
if (await userCan(req, slug, "update") || await userCan(req, slug, "drafts")) return true;
|
|
390
|
+
return { _status: { equals: "published" } };
|
|
391
|
+
} } : {}
|
|
392
|
+
},
|
|
393
|
+
...users && !(config.fields ?? []).some((field) => "name" in field && field.name === "roles") ? { fields: [...config.fields ?? [], rolesField()] } : {}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
//#endregion
|
|
33
397
|
//#region src/cms/collections/createApiTokens.ts
|
|
34
398
|
const adminOrSelf = ({ req }) => {
|
|
35
399
|
if (req.user?.collection === "users") return true;
|
|
@@ -96,6 +460,133 @@ function createApiTokens(access) {
|
|
|
96
460
|
//#region src/cms/hooks/slugify.ts
|
|
97
461
|
const slugify = ({ value }) => typeof value === "string" ? value.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "") : value;
|
|
98
462
|
//#endregion
|
|
463
|
+
//#region src/cms/collections/createCmsRoles.ts
|
|
464
|
+
const refresh = ({ req }) => refreshRoleCache(req.payload);
|
|
465
|
+
const refreshAfterDelete = ({ req }) => refreshRoleCache(req.payload);
|
|
466
|
+
const keepAdminRole = ({ data, originalDoc }) => {
|
|
467
|
+
if (originalDoc?.slug === "admin" || data?.slug === "admin") return {
|
|
468
|
+
...data,
|
|
469
|
+
slug: ADMIN_ROLE,
|
|
470
|
+
fullAccess: true
|
|
471
|
+
};
|
|
472
|
+
return data;
|
|
473
|
+
};
|
|
474
|
+
const blockAdminDelete = async ({ id, req }) => {
|
|
475
|
+
if ((await req.payload.findByID({
|
|
476
|
+
collection: "cms-roles",
|
|
477
|
+
id,
|
|
478
|
+
depth: 0,
|
|
479
|
+
overrideAccess: true
|
|
480
|
+
})).slug === "admin") throw new Error("The Admin role cannot be deleted.");
|
|
481
|
+
};
|
|
482
|
+
const fullAdmin = ({ req }) => userIsFullAdmin(req);
|
|
483
|
+
function createCmsRoles(targets) {
|
|
484
|
+
return {
|
|
485
|
+
slug: CMS_ROLES_SLUG,
|
|
486
|
+
labels: {
|
|
487
|
+
singular: "Role",
|
|
488
|
+
plural: "Roles"
|
|
489
|
+
},
|
|
490
|
+
admin: {
|
|
491
|
+
useAsTitle: "name",
|
|
492
|
+
hidden: hiddenUnlessFullAdmin(),
|
|
493
|
+
description: "Who can edit each collection and global. Only an admin can change this."
|
|
494
|
+
},
|
|
495
|
+
access: {
|
|
496
|
+
read: fullAdmin,
|
|
497
|
+
create: fullAdmin,
|
|
498
|
+
update: fullAdmin,
|
|
499
|
+
delete: fullAdmin
|
|
500
|
+
},
|
|
501
|
+
hooks: {
|
|
502
|
+
beforeChange: [keepAdminRole],
|
|
503
|
+
beforeDelete: [blockAdminDelete],
|
|
504
|
+
afterChange: [refresh],
|
|
505
|
+
afterDelete: [refreshAfterDelete]
|
|
506
|
+
},
|
|
507
|
+
fields: [
|
|
508
|
+
{
|
|
509
|
+
name: "name",
|
|
510
|
+
type: "text",
|
|
511
|
+
required: true
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
name: "slug",
|
|
515
|
+
type: "text",
|
|
516
|
+
required: true,
|
|
517
|
+
unique: true,
|
|
518
|
+
index: true,
|
|
519
|
+
hooks: { beforeValidate: [slugify] },
|
|
520
|
+
admin: { description: "Stable id. Do not change it after users are assigned this role." }
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
name: "fullAccess",
|
|
524
|
+
type: "checkbox",
|
|
525
|
+
defaultValue: false,
|
|
526
|
+
admin: { description: "Can edit every collection, including Roles and Users." }
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
name: "permissions",
|
|
530
|
+
type: "array",
|
|
531
|
+
admin: {
|
|
532
|
+
condition: (data) => !data?.fullAccess,
|
|
533
|
+
description: "One row per collection or global. No row means no access."
|
|
534
|
+
},
|
|
535
|
+
fields: [
|
|
536
|
+
{
|
|
537
|
+
name: "target",
|
|
538
|
+
type: "select",
|
|
539
|
+
required: true,
|
|
540
|
+
options: targets.map((slug) => ({
|
|
541
|
+
label: slug,
|
|
542
|
+
value: slug
|
|
543
|
+
}))
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
name: "create",
|
|
547
|
+
type: "checkbox",
|
|
548
|
+
defaultValue: false
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
name: "update",
|
|
552
|
+
type: "checkbox",
|
|
553
|
+
defaultValue: false
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
name: "delete",
|
|
557
|
+
type: "checkbox",
|
|
558
|
+
defaultValue: false
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
name: "drafts",
|
|
562
|
+
type: "checkbox",
|
|
563
|
+
defaultValue: false,
|
|
564
|
+
admin: { description: "Can open unpublished documents. Update also allows this." }
|
|
565
|
+
}
|
|
566
|
+
]
|
|
567
|
+
}
|
|
568
|
+
]
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
/** Creates each default role once. Documents that already exist are left as edited. */
|
|
572
|
+
async function seedDefaultCmsRoles(payload, options = {}) {
|
|
573
|
+
const seeds = buildRoleSeeds(options);
|
|
574
|
+
await Promise.all(seeds.map(async (role) => {
|
|
575
|
+
if ((await payload.find({
|
|
576
|
+
collection: "cms-roles",
|
|
577
|
+
where: { slug: { equals: role.slug } },
|
|
578
|
+
limit: 1,
|
|
579
|
+
depth: 0,
|
|
580
|
+
overrideAccess: true
|
|
581
|
+
})).docs.length > 0) return;
|
|
582
|
+
await payload.create({
|
|
583
|
+
collection: CMS_ROLES_SLUG,
|
|
584
|
+
data: role,
|
|
585
|
+
overrideAccess: true
|
|
586
|
+
});
|
|
587
|
+
}));
|
|
588
|
+
}
|
|
589
|
+
//#endregion
|
|
99
590
|
//#region src/cms/shared/seo-fields.ts
|
|
100
591
|
const MAX_META_TITLE = 60;
|
|
101
592
|
const MAX_META_DESCRIPTION = 160;
|
|
@@ -1693,12 +2184,31 @@ function buildCmsConfig(options) {
|
|
|
1693
2184
|
"event-tags": createEventTags(access),
|
|
1694
2185
|
"events": createEvents(access)
|
|
1695
2186
|
};
|
|
1696
|
-
const
|
|
1697
|
-
const
|
|
2187
|
+
const rolesSetting = options.access?.roles;
|
|
2188
|
+
const rolesEnabled = rolesSetting != null;
|
|
2189
|
+
const rolesConfig = rolesSetting === true || rolesSetting == null ? {} : rolesSetting;
|
|
2190
|
+
if (rolesEnabled) Object.keys(stock).forEach((slug) => {
|
|
2191
|
+
stock[slug] = withRoles(stock[slug]);
|
|
2192
|
+
});
|
|
2193
|
+
const collections = [
|
|
2194
|
+
...rolesEnabled ? [createCmsRoles(roleTargetSlugs(rolesConfig))] : [],
|
|
2195
|
+
...STOCK_COLLECTION_SLUGS.map((slug) => applyCollectionOverride(stock[slug], overrides[slug])).filter((c) => c !== null),
|
|
2196
|
+
...options.extraCollections ?? []
|
|
2197
|
+
];
|
|
2198
|
+
const siteSettingsBase = createSiteSettings(access);
|
|
2199
|
+
const siteSettingsWithRoles = rolesEnabled ? withRoles(siteSettingsBase) : siteSettingsBase;
|
|
2200
|
+
const siteSettings = applyGlobalOverride(siteSettingsWithRoles, overrides["site-settings"]);
|
|
1698
2201
|
const globals = [...siteSettings ? [siteSettings] : [], ...options.extraGlobals ?? []];
|
|
1699
|
-
const { jobs: jobsOverride, ...payloadRest } = options.payload ?? {};
|
|
2202
|
+
const { jobs: jobsOverride, onInit: callerOnInit, ...payloadRest } = options.payload ?? {};
|
|
1700
2203
|
return buildConfig({
|
|
1701
2204
|
...payloadRest,
|
|
2205
|
+
onInit: async (payload) => {
|
|
2206
|
+
if (rolesEnabled) {
|
|
2207
|
+
await seedDefaultCmsRoles(payload, rolesConfig);
|
|
2208
|
+
await refreshRoleCache(payload);
|
|
2209
|
+
}
|
|
2210
|
+
if (callerOnInit) await callerOnInit(payload);
|
|
2211
|
+
},
|
|
1702
2212
|
jobs: {
|
|
1703
2213
|
autoRun: [{ cron: "* * * * *" }],
|
|
1704
2214
|
...jobsOverride
|
|
@@ -1843,6 +2353,6 @@ function createAnnouncementBanner(access) {
|
|
|
1843
2353
|
};
|
|
1844
2354
|
}
|
|
1845
2355
|
//#endregion
|
|
1846
|
-
export { richTextBlock as A, languageOptions as B, blogContentBlocks as C, accordionBlock as D, sectionHeadingBlock as E, downloadCtaBlock as F,
|
|
2356
|
+
export { hiddenUnless as $, richTextBlock as A, languageOptions as B, blogContentBlocks as C, accordionBlock as D, sectionHeadingBlock as E, downloadCtaBlock as F, ADMIN_ROLE as G, seoFields as H, codeTabsBlock as I, DEFAULT_ROLES as J, DEFAULT_COLLECTION_ROLES as K, codeBlock as L, richTextEditor as M, videoBlock as N, leafBlocks as O, imageBlock as P, globalRoleRule as Q, codeBlockLanguages as R, coerceBlogPostCategoriesOnWrite as S, showcaseContentBlocks as T, slugify as U, createAuthors as V, createApiTokens as W, collectionRoleRule as X, adminRoleAccess as Y, effectiveRoles as Z, createCategories as _, STOCK_COLLECTION_SLUGS as a, writeAccess as at, setPublishedAt as b, createSiteSettings as c, createPages as d, isCmsAdmin as et, createMedia as f, createChangelog as g, createChangelogTags as h, buildCmsConfig as i, withRoles as it, getRichTextEditorConfig as j, stepsBlock as k, createUsers as l, createEventTags as m, createS3MediaPlugin as n, resolveRoleOptions as nt, applyCollectionOverride as o, createAccessHelpers as ot, createEvents as p, DEFAULT_GLOBAL_ROLES as q, requireEnvAtRuntime as r, userHasAnyRole as rt, applyGlobalOverride as s, createAnnouncementBanner as t, publishedOrRoleRead as tt, createLegalPages as u, createBlogPosts as v, pageContentBlocks as w, coerceBlogPostCategoriesOnRead as x, draftsWithAutosave as y, defaultCodeLanguage as z };
|
|
1847
2357
|
|
|
1848
2358
|
//# sourceMappingURL=cms.mjs.map
|