@easy-cms/core 0.44.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +82 -7
- package/dist/index.js +152 -47
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1087,9 +1087,15 @@ declare class MediaFolders {
|
|
|
1087
1087
|
* The folder with this key (`folder: 'banners'` on upload fields), made at the top level when
|
|
1088
1088
|
* there is none yet.
|
|
1089
1089
|
*/
|
|
1090
|
-
|
|
1090
|
+
/**
|
|
1091
|
+
* The folder with this key, made when first needed. `scope`: the values the key is unique
|
|
1092
|
+
* within (`uniqueWithin` of the `key` field, e.g. the tenant), given to a new folder too.
|
|
1093
|
+
* `null` when there is none and `create` is false.
|
|
1094
|
+
*/
|
|
1095
|
+
keyed(key: string, scope?: Readonly<Record<string, unknown>>): Promise<ID>;
|
|
1096
|
+
keyed(key: string, scope: Readonly<Record<string, unknown>>, create: boolean): Promise<ID | null>;
|
|
1091
1097
|
/** Whether a file in `folder` is in the folder with this key, or below it. */
|
|
1092
|
-
inKeyed(key: string, folder: ID | null): Promise<boolean>;
|
|
1098
|
+
inKeyed(key: string, folder: ID | null, scope?: Readonly<Record<string, unknown>>): Promise<boolean>;
|
|
1093
1099
|
/** The user's level in a folder (`null`: the top level). */
|
|
1094
1100
|
level(user: AuthUser | null, id: ID | null): Promise<FolderLevel | 'none'>;
|
|
1095
1101
|
/**
|
|
@@ -1673,6 +1679,23 @@ declare class EasyCMS<C extends Config = Config> {
|
|
|
1673
1679
|
private makeSlugsUnique;
|
|
1674
1680
|
private checkUnique;
|
|
1675
1681
|
private isTaken;
|
|
1682
|
+
/**
|
|
1683
|
+
* What folder keys are unique within for a call (`uniqueWithin` of the `key` field of media
|
|
1684
|
+
* folders, e.g. the tenant): the value a folder made by this call would get from its hooks.
|
|
1685
|
+
*/
|
|
1686
|
+
private folderScope;
|
|
1687
|
+
/**
|
|
1688
|
+
* Where a unique field's values must differ for a call (`uniqueWithin`, e.g. the tenant): the
|
|
1689
|
+
* value a document created by this call would get there from its hooks, as `{ [field]: value }`;
|
|
1690
|
+
* `{}` when the field is unique everywhere. For finding documents by such a field, e.g. a page
|
|
1691
|
+
* by its path in the current tenant.
|
|
1692
|
+
*/
|
|
1693
|
+
uniqueScope(collection: string, field: string, options?: Pick<AccessOptions, 'user' | 'context'>): Promise<Record<string, unknown>>;
|
|
1694
|
+
/**
|
|
1695
|
+
* @internal The id of the media folder with this key (`folder` of upload fields), made when
|
|
1696
|
+
* needed; `null` when the call has no scope to make it in (e.g. all tenants) and none exists.
|
|
1697
|
+
*/
|
|
1698
|
+
keyedFolder(key: string, options?: AccessOptions): Promise<ID | null>;
|
|
1676
1699
|
/** Each reference is one its relationship's `filterOptions` allow. */
|
|
1677
1700
|
private checkFilterOptions;
|
|
1678
1701
|
private checkReferences;
|
|
@@ -1711,6 +1734,23 @@ interface FieldAdmin {
|
|
|
1711
1734
|
readonly after?: readonly AdminComponent[];
|
|
1712
1735
|
/** Shows the value in the admin's lists (e.g. a color swatch). Default: the plain value. */
|
|
1713
1736
|
readonly cell?: AdminComponent;
|
|
1737
|
+
/**
|
|
1738
|
+
* A column of the list at first (each user can change it): `true`, or per request, e.g.
|
|
1739
|
+
* only when the admin shows every tenant.
|
|
1740
|
+
*/
|
|
1741
|
+
readonly column?: boolean | ((args: AdminFieldArgs) => boolean | Promise<boolean>);
|
|
1742
|
+
/** Relationships: offer to create the related document in place. Default true. */
|
|
1743
|
+
readonly allowCreate?: boolean;
|
|
1744
|
+
/**
|
|
1745
|
+
* The value a new document's form starts with, worked out for each user, e.g. from the
|
|
1746
|
+
* request's context. The server's own default (`defaultValue`, hooks) still applies.
|
|
1747
|
+
*/
|
|
1748
|
+
readonly defaultValue?: (args: AdminFieldArgs) => unknown;
|
|
1749
|
+
}
|
|
1750
|
+
/** What per-request admin options of a field receive. */
|
|
1751
|
+
interface AdminFieldArgs {
|
|
1752
|
+
readonly user: AuthUser;
|
|
1753
|
+
readonly context: RequestContext;
|
|
1714
1754
|
}
|
|
1715
1755
|
/**
|
|
1716
1756
|
* Field types that packages add (`fieldTypes` in the config), for the types inferred from the
|
|
@@ -1741,11 +1781,11 @@ interface BaseField<TType extends string, TValue> {
|
|
|
1741
1781
|
readonly required?: boolean;
|
|
1742
1782
|
readonly unique?: boolean;
|
|
1743
1783
|
/**
|
|
1744
|
-
* With `unique` (or on a slug field):
|
|
1784
|
+
* With `unique` (or on a slug field): a sibling field (e.g. `parent` or `tenant`), or several,
|
|
1745
1785
|
* whose documents only need to differ among themselves, so `/about/team` and `/careers/team`
|
|
1746
|
-
* can both be `team`.
|
|
1786
|
+
* can both be `team`. Single, unlocalized relationship, select, text or number fields.
|
|
1747
1787
|
*/
|
|
1748
|
-
readonly uniqueWithin?: string;
|
|
1788
|
+
readonly uniqueWithin?: string | readonly string[];
|
|
1749
1789
|
readonly index?: boolean;
|
|
1750
1790
|
readonly defaultValue?: TValue;
|
|
1751
1791
|
readonly validate?: FieldValidate<TValue>;
|
|
@@ -1825,6 +1865,8 @@ interface UploadField extends BaseField<'upload', never> {
|
|
|
1825
1865
|
readonly folder?: string;
|
|
1826
1866
|
/** With `folder`: only files in that folder (and its subfolders) may be chosen. */
|
|
1827
1867
|
readonly folderOnly?: boolean;
|
|
1868
|
+
/** Which media documents may be chosen, as for relationships: offered and checked on save. */
|
|
1869
|
+
readonly filterOptions?: FilterOptions;
|
|
1828
1870
|
}
|
|
1829
1871
|
interface FilterOptionsArgs {
|
|
1830
1872
|
/** The document being edited; `undefined` while it is being created. */
|
|
@@ -1876,6 +1918,8 @@ interface BlocksField extends BaseField<'blocks', never> {
|
|
|
1876
1918
|
readonly minRows?: number;
|
|
1877
1919
|
readonly maxRows?: number;
|
|
1878
1920
|
}
|
|
1921
|
+
/** The fields `uniqueWithin` names, as a list. */
|
|
1922
|
+
declare const uniqueWithinOf: (field: Field) => readonly string[];
|
|
1879
1923
|
/** Fields with `hasMany`: their value is a list of options, documents or files. */
|
|
1880
1924
|
declare const isHasMany: (field: Field) => boolean;
|
|
1881
1925
|
/** Whether a MIME type is one of `patterns` (`image/*` matches every image). */
|
|
@@ -1896,6 +1940,12 @@ interface AuditConfig {
|
|
|
1896
1940
|
readonly values?: boolean;
|
|
1897
1941
|
/** Failed logins within an hour that the dashboard warns about. Default 20. */
|
|
1898
1942
|
readonly failedLogins?: number;
|
|
1943
|
+
/**
|
|
1944
|
+
* The part of the site an entry belongs to, from the request's context, e.g. its tenant
|
|
1945
|
+
* (usually set by a plugin). Users whose role holds in a part only (`scoped`) see that part's
|
|
1946
|
+
* entries; others see the chosen part's, or all.
|
|
1947
|
+
*/
|
|
1948
|
+
readonly scope?: ((context: RequestContext) => string | null | undefined) | null;
|
|
1899
1949
|
}
|
|
1900
1950
|
/** A field that changed: values before and after, unless they are not kept. */
|
|
1901
1951
|
interface AuditChange {
|
|
@@ -1943,6 +1993,8 @@ interface AuditFilter {
|
|
|
1943
1993
|
/** ISO dates. */
|
|
1944
1994
|
from?: string | null;
|
|
1945
1995
|
to?: string | null;
|
|
1996
|
+
/** Only entries of this part of the site (`scope`). */
|
|
1997
|
+
scope?: string | null;
|
|
1946
1998
|
}
|
|
1947
1999
|
/** `POST <api>/admin/audit/verify`. */
|
|
1948
2000
|
interface AuditVerification {
|
|
@@ -1979,12 +2031,14 @@ declare class AuditLog {
|
|
|
1979
2031
|
email?: string | null;
|
|
1980
2032
|
changes?: AuditChange[] | null;
|
|
1981
2033
|
detail?: Record<string, unknown> | null;
|
|
2034
|
+
/** The call's context, when it is not the request's. */
|
|
2035
|
+
context?: RequestContext;
|
|
1982
2036
|
}): Promise<void>;
|
|
1983
2037
|
/**
|
|
1984
2038
|
* A change to a document or global: what changed between `before` and `after`. Internal
|
|
1985
2039
|
* collections (sessions, versions…) are not logged.
|
|
1986
2040
|
*/
|
|
1987
|
-
content(config: CollectionConfig | GlobalConfig, global: boolean, action: string, after: Record<string, unknown> | undefined, before: Record<string, unknown> | undefined, user: AuthUser | null): Promise<void>;
|
|
2041
|
+
content(config: CollectionConfig | GlobalConfig, global: boolean, action: string, after: Record<string, unknown> | undefined, before: Record<string, unknown> | undefined, user: AuthUser | null, context?: RequestContext): Promise<void>;
|
|
1988
2042
|
/** Fields whose stored value changed. Hidden ones never (the password only as "changed"). */
|
|
1989
2043
|
diff(fields: readonly Field[], before: Record<string, unknown>, after: Record<string, unknown>): AuditChange[];
|
|
1990
2044
|
list(filter: AuditFilter, page?: number): Promise<AuditPage>;
|
|
@@ -2009,6 +2063,8 @@ declare class AuditLog {
|
|
|
2009
2063
|
invalid: number;
|
|
2010
2064
|
at: string;
|
|
2011
2065
|
})[]>;
|
|
2066
|
+
/** The part of the site a context is in (`scope`), or `null`. */
|
|
2067
|
+
scopeOf(context: RequestContext | undefined): string | null;
|
|
2012
2068
|
private sign;
|
|
2013
2069
|
private lastVerification;
|
|
2014
2070
|
private where;
|
|
@@ -2299,6 +2355,16 @@ interface CollectionAdmin extends ContainerAdmin {
|
|
|
2299
2355
|
* `author`, for roles given "own documents only". Default: who created it (`createdBy`).
|
|
2300
2356
|
*/
|
|
2301
2357
|
readonly ownerField?: string;
|
|
2358
|
+
/**
|
|
2359
|
+
* Deleting asks more than "Are you sure?", for documents that take much with them (e.g. a
|
|
2360
|
+
* tenant): `typeTitle` has the user type the document's title; `impact` is a path under the
|
|
2361
|
+
* API, called with `?id=`, that returns `{ message }` (a label) saying what will be lost.
|
|
2362
|
+
* Documents are then deleted one at a time, not from a selection.
|
|
2363
|
+
*/
|
|
2364
|
+
readonly confirmDelete?: {
|
|
2365
|
+
readonly typeTitle?: boolean;
|
|
2366
|
+
readonly impact?: string;
|
|
2367
|
+
};
|
|
2302
2368
|
}
|
|
2303
2369
|
interface CollectionListAdmin {
|
|
2304
2370
|
/**
|
|
@@ -3199,6 +3265,10 @@ interface AdminField {
|
|
|
3199
3265
|
after?: AdminComponentRef[];
|
|
3200
3266
|
cell?: AdminComponentRef;
|
|
3201
3267
|
};
|
|
3268
|
+
/** A column of the list at first (`admin.column`). */
|
|
3269
|
+
column?: boolean;
|
|
3270
|
+
/** A relationship that offers no "Create" in place (`admin.allowCreate: false`). */
|
|
3271
|
+
noCreate?: boolean;
|
|
3202
3272
|
/** A field of an added type (`fieldTypes`), e.g. `color`; `type` is the base it's stored as. */
|
|
3203
3273
|
customType?: string;
|
|
3204
3274
|
}
|
|
@@ -3236,6 +3306,11 @@ interface AdminCollection {
|
|
|
3236
3306
|
};
|
|
3237
3307
|
/** With `auth.rbac`: the field naming a document's owner (`createdBy` or `admin.ownerField`). */
|
|
3238
3308
|
owner?: string;
|
|
3309
|
+
/** Deleting asks to type the title, and says what goes too (`admin.confirmDelete`). */
|
|
3310
|
+
confirmDelete?: {
|
|
3311
|
+
typeTitle?: boolean;
|
|
3312
|
+
impact?: string;
|
|
3313
|
+
};
|
|
3239
3314
|
}
|
|
3240
3315
|
interface AdminGlobal {
|
|
3241
3316
|
slug: string;
|
|
@@ -3437,4 +3512,4 @@ declare function validateConfig(config: Config): ConfigIssue[];
|
|
|
3437
3512
|
/** The version of `@easy-cms/core`, set when the package is built. */
|
|
3438
3513
|
declare const VERSION: string;
|
|
3439
3514
|
|
|
3440
|
-
export { ADMIN_ICONS, API_KEYS, API_KEY_GLOBAL_OPERATIONS, API_KEY_OPERATIONS, type Access, type AccessArgs, type AccessOptions, type AdminAttention, type AdminBackup, type AdminBackups, type AdminBrand, type AdminCollection, type AdminComponent, type AdminComponentRef, type AdminConfig, type AdminDeliveries, type AdminDelivery, type AdminEmail, type AdminEmailDelivery, type AdminField, type AdminGlobal, type AdminIcon, type AdminLocale, type AdminPage, type AdminPageRef, type AdminRole, type AdminRoleChange, type AdminRoleField, type AdminRoleTarget, type AdminRoleView, type AdminRoles, type AdminSchema, type AdminSso, type AdminStatus, type AdminSwitcher, type AdminViewAccess, type AdminWebhookDelivery, type AdminWidgetRef, type AfterChangeHook, type AfterDeleteHook, type AfterReadHook, type ApiHandlerOptions, type ApiKeyContext, type ApiKeyGlobalOperation, type ApiKeyOperation, type ApiKeyPermissions, type ArrayField, type AuditChange, type AuditConfig, type AuditEntry, AuditLog, type AuditPage, type AuditVerification, Auth, type AuthConfig, type AuthProvider, type AuthProviderProfile, type AuthProviderRequest, type AuthUser, BUILTIN_COLLECTIONS, type BackupState, type BackupsConfig, type BeforeChangeHook, type BeforeDeleteHook, type BeforeValidateHook, type Block, type BlocksField, type BooleanField, type BuiltinApiKeysCollection, type BuiltinMediaCollection, type BuiltinMediaFoldersCollection, type BuiltinUsersCollection, type BundleHints, CONFIG_FILE_NAMES, CSRF_COOKIE, CSRF_HEADER, type CliCommand, type CollectionAccess, type CollectionAdmin, type CollectionConfig, type CollectionDocument, type CollectionHooks, type CollectionListAdmin, type CollectionSlug, type Config, ConfigError, type ConfigIssue, type ContainerAdmin, type CopyProgress, type CopyResult, type CreateEasyCMSOptions, type CreateInput, type CreateMigrationResult, type CustomField, type CustomFieldTypes, DEFAULT_ADMIN_PATH, DEFAULT_API_PATH, DEFAULT_DEPTH, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_VERSIONS, DEFAULT_PASSWORD_EMAILS, DEFAULT_TOKEN_EXPIRATION, type DashboardWidget, type Database, type DatabaseAdapter, type DatabaseInitArgs, type DatabaseTransfer, type DateField, type DeliveryKind, type DeliveryState, type DepthOptions, type DirectUpload, type DocumentOf, EXTENSIONS, EasyCMS, EasyCMSError, type EmailAdapter, type EmailCheck, type EmailField, type EmailMessage, type EmailSetting, type Endpoint, type EndpointRequest, FIELD_TYPES, FIELD_TYPE_BASES, FOLDER_LEVELS, type Field, type FieldAccess, type FieldAccessFn, type FieldAdmin, type FieldError, type FieldRule, type FieldType, type FieldTypeBase, type FieldTypeDefinition, type FieldTypeValidateContext, type FieldValidate, type FieldValidateContext, type FieldValue, type FieldsInput, type FieldsValue, type FilterOptions, type FilterOptionsArgs, type FindArgs, type FindOptions, type FolderLevel, type FolderPermissions, ForbiddenError, type GlobalAccess, type GlobalConfig, type GlobalDocument, type GlobalHooks, type GlobalInput, type GlobalSlug, type GroupField, type ID, INTERNAL_COLLECTIONS, type ImageSize, type InferCollection, type InferGlobal, type JsonField, type Label, type LivePreview, type LoadConfigOptions, type LocalStorageOptions, type LocalizationConfig, type Logger, type LoginArgs, MAX_DEPTH, MEDIA, MEDIA_FOLDERS, MIME_GROUPS, MIN_PASSWORD_LENGTH, MIN_SECRET_LENGTH, type MediaDocument, MediaFolders, type MediaSize, type MigrationInfo, NotFoundError, type NumberField, type OnRequest, type OnRequestArgs, type Operation, type PaginatedDocs, type PasswordEmail, type PasswordEmailArgs, type PasswordEmailFn, type PasswordLinkOptions, PayloadTooLargeError, type Plugin, type PluginInfo, type PluginTypes, type PreviewTarget, type PreviewURL, QueryError, ROLE_OPERATIONS, type RawDocument, type ReadOptions, type RelationshipField, type RequestContext, type ResolvedConfig, type RestHandler, type RestHandlerOptions, type RichTextDocument, type RichTextField, type Role, type RoleOperation, type RolePermissions, Roles, type RoutesConfig, SESSION_COOKIE, SSO_COOKIE, type ScheduledJob, SchemaError, type SchemaMode, type SelectField, type SelectOption, type Session, type SlugField, type SlugOf, type SsoOutcome, type SsoProviderRef, type StorageAdapter, type StoredFile, type TextField, type TextareaField, TooManyRequestsError, type TypedPlugin, UnauthorizedError, type UpdateInput, type UpdateOptions, type UploadConfig, type UploadField, type UploadFromURLConfig, type UserIdentity, VERSION, ValidationError, type Version, type VersionSummary, type VersionsConfig, WEBHOOK_EVENTS, type WebhookConfig, type WebhookEvent, type WebhookPayload, type Where, type WhereOperators, anyone, applyDefaults, applyPlugins, configSignature, consoleEmail, consoleLogger, copyDatabase, createApiHandler, createEasyCMS, createRestHandler, createRootEndpointHandler, defineConfig, defineFieldType, definePlugin, expandMimeTypes, fillMissing, findConfigFile, generateSlugs, generateTypes, hasRows, hashPassword, imageDimensions, importConfig, isAdmin, isHasMany, isLoggedIn, isSystemAdmin, keyAllows, loadConfig, localStorage, mergeForUpdate, mimeAllowed, mimeAllowedBy, parseId, populate, readCookie, resolveAdminModule, resolveConfig, rowFields, sharedEasyCMS, silentLogger, singularize, slugify, sniffMimeType, validateConfig, validateFields, verifyPassword, writeBackupFile };
|
|
3515
|
+
export { ADMIN_ICONS, API_KEYS, API_KEY_GLOBAL_OPERATIONS, API_KEY_OPERATIONS, type Access, type AccessArgs, type AccessOptions, type AdminAttention, type AdminBackup, type AdminBackups, type AdminBrand, type AdminCollection, type AdminComponent, type AdminComponentRef, type AdminConfig, type AdminDeliveries, type AdminDelivery, type AdminEmail, type AdminEmailDelivery, type AdminField, type AdminFieldArgs, type AdminGlobal, type AdminIcon, type AdminLocale, type AdminPage, type AdminPageRef, type AdminRole, type AdminRoleChange, type AdminRoleField, type AdminRoleTarget, type AdminRoleView, type AdminRoles, type AdminSchema, type AdminSso, type AdminStatus, type AdminSwitcher, type AdminViewAccess, type AdminWebhookDelivery, type AdminWidgetRef, type AfterChangeHook, type AfterDeleteHook, type AfterReadHook, type ApiHandlerOptions, type ApiKeyContext, type ApiKeyGlobalOperation, type ApiKeyOperation, type ApiKeyPermissions, type ArrayField, type AuditChange, type AuditConfig, type AuditEntry, AuditLog, type AuditPage, type AuditVerification, Auth, type AuthConfig, type AuthProvider, type AuthProviderProfile, type AuthProviderRequest, type AuthUser, BUILTIN_COLLECTIONS, type BackupState, type BackupsConfig, type BeforeChangeHook, type BeforeDeleteHook, type BeforeValidateHook, type Block, type BlocksField, type BooleanField, type BuiltinApiKeysCollection, type BuiltinMediaCollection, type BuiltinMediaFoldersCollection, type BuiltinUsersCollection, type BundleHints, CONFIG_FILE_NAMES, CSRF_COOKIE, CSRF_HEADER, type CliCommand, type CollectionAccess, type CollectionAdmin, type CollectionConfig, type CollectionDocument, type CollectionHooks, type CollectionListAdmin, type CollectionSlug, type Config, ConfigError, type ConfigIssue, type ContainerAdmin, type CopyProgress, type CopyResult, type CreateEasyCMSOptions, type CreateInput, type CreateMigrationResult, type CustomField, type CustomFieldTypes, DEFAULT_ADMIN_PATH, DEFAULT_API_PATH, DEFAULT_DEPTH, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_VERSIONS, DEFAULT_PASSWORD_EMAILS, DEFAULT_TOKEN_EXPIRATION, type DashboardWidget, type Database, type DatabaseAdapter, type DatabaseInitArgs, type DatabaseTransfer, type DateField, type DeliveryKind, type DeliveryState, type DepthOptions, type DirectUpload, type DocumentOf, EXTENSIONS, EasyCMS, EasyCMSError, type EmailAdapter, type EmailCheck, type EmailField, type EmailMessage, type EmailSetting, type Endpoint, type EndpointRequest, FIELD_TYPES, FIELD_TYPE_BASES, FOLDER_LEVELS, type Field, type FieldAccess, type FieldAccessFn, type FieldAdmin, type FieldError, type FieldRule, type FieldType, type FieldTypeBase, type FieldTypeDefinition, type FieldTypeValidateContext, type FieldValidate, type FieldValidateContext, type FieldValue, type FieldsInput, type FieldsValue, type FilterOptions, type FilterOptionsArgs, type FindArgs, type FindOptions, type FolderLevel, type FolderPermissions, ForbiddenError, type GlobalAccess, type GlobalConfig, type GlobalDocument, type GlobalHooks, type GlobalInput, type GlobalSlug, type GroupField, type ID, INTERNAL_COLLECTIONS, type ImageSize, type InferCollection, type InferGlobal, type JsonField, type Label, type LivePreview, type LoadConfigOptions, type LocalStorageOptions, type LocalizationConfig, type Logger, type LoginArgs, MAX_DEPTH, MEDIA, MEDIA_FOLDERS, MIME_GROUPS, MIN_PASSWORD_LENGTH, MIN_SECRET_LENGTH, type MediaDocument, MediaFolders, type MediaSize, type MigrationInfo, NotFoundError, type NumberField, type OnRequest, type OnRequestArgs, type Operation, type PaginatedDocs, type PasswordEmail, type PasswordEmailArgs, type PasswordEmailFn, type PasswordLinkOptions, PayloadTooLargeError, type Plugin, type PluginInfo, type PluginTypes, type PreviewTarget, type PreviewURL, QueryError, ROLE_OPERATIONS, type RawDocument, type ReadOptions, type RelationshipField, type RequestContext, type ResolvedConfig, type RestHandler, type RestHandlerOptions, type RichTextDocument, type RichTextField, type Role, type RoleOperation, type RolePermissions, Roles, type RoutesConfig, SESSION_COOKIE, SSO_COOKIE, type ScheduledJob, SchemaError, type SchemaMode, type SelectField, type SelectOption, type Session, type SlugField, type SlugOf, type SsoOutcome, type SsoProviderRef, type StorageAdapter, type StoredFile, type TextField, type TextareaField, TooManyRequestsError, type TypedPlugin, UnauthorizedError, type UpdateInput, type UpdateOptions, type UploadConfig, type UploadField, type UploadFromURLConfig, type UserIdentity, VERSION, ValidationError, type Version, type VersionSummary, type VersionsConfig, WEBHOOK_EVENTS, type WebhookConfig, type WebhookEvent, type WebhookPayload, type Where, type WhereOperators, anyone, applyDefaults, applyPlugins, configSignature, consoleEmail, consoleLogger, copyDatabase, createApiHandler, createEasyCMS, createRestHandler, createRootEndpointHandler, defineConfig, defineFieldType, definePlugin, expandMimeTypes, fillMissing, findConfigFile, generateSlugs, generateTypes, hasRows, hashPassword, imageDimensions, importConfig, isAdmin, isHasMany, isLoggedIn, isSystemAdmin, keyAllows, loadConfig, localStorage, mergeForUpdate, mimeAllowed, mimeAllowedBy, parseId, populate, readCookie, resolveAdminModule, resolveConfig, rowFields, sharedEasyCMS, silentLogger, singularize, slugify, sniffMimeType, uniqueWithinOf, validateConfig, validateFields, verifyPassword, writeBackupFile };
|
package/dist/index.js
CHANGED
|
@@ -427,7 +427,16 @@ function withMedia(config) {
|
|
|
427
427
|
const customFolders = config.collections?.find((c) => c.slug === MEDIA_FOLDERS);
|
|
428
428
|
const mediaFolders = customFolders ? {
|
|
429
429
|
...mediaFoldersCollection,
|
|
430
|
-
|
|
430
|
+
// A field named like a built-in one changes it (e.g. `key` unique per tenant).
|
|
431
|
+
fields: [
|
|
432
|
+
...mediaFoldersCollection.fields.map((field) => {
|
|
433
|
+
const own = customFolders.fields.find((f) => f.name === field.name);
|
|
434
|
+
return own ? { ...field, ...own } : field;
|
|
435
|
+
}),
|
|
436
|
+
...customFolders.fields.filter(
|
|
437
|
+
(f) => !mediaFoldersCollection.fields.some((b) => b.name === f.name)
|
|
438
|
+
)
|
|
439
|
+
],
|
|
431
440
|
access: { ...mediaFoldersCollection.access, ...customFolders.access },
|
|
432
441
|
hooks: {
|
|
433
442
|
...customFolders.hooks,
|
|
@@ -562,7 +571,9 @@ var auditLogsCollection = {
|
|
|
562
571
|
{ name: "userAgent", type: "text" },
|
|
563
572
|
{ name: "changes", type: "json" },
|
|
564
573
|
{ name: "detail", type: "json" },
|
|
565
|
-
{ name: "signature", type: "text" }
|
|
574
|
+
{ name: "signature", type: "text" },
|
|
575
|
+
// The part of the site, e.g. the tenant (`audit.scope`).
|
|
576
|
+
{ name: "scope", type: "text", index: true }
|
|
566
577
|
]
|
|
567
578
|
};
|
|
568
579
|
var emailDeliveriesCollection = {
|
|
@@ -635,7 +646,8 @@ var AuditLog = class {
|
|
|
635
646
|
return this.cms.config.audit || {
|
|
636
647
|
keep: DEFAULT_AUDIT_KEEP,
|
|
637
648
|
values: true,
|
|
638
|
-
failedLogins: DEFAULT_FAILED_LOGINS
|
|
649
|
+
failedLogins: DEFAULT_FAILED_LOGINS,
|
|
650
|
+
scope: null
|
|
639
651
|
};
|
|
640
652
|
}
|
|
641
653
|
/**
|
|
@@ -646,6 +658,7 @@ var AuditLog = class {
|
|
|
646
658
|
if (!this.enabled) return;
|
|
647
659
|
try {
|
|
648
660
|
const context = auditContext.getStore();
|
|
661
|
+
const scope = this.scopeOf(entry.context ?? context?.context);
|
|
649
662
|
const user = entry.user !== void 0 ? entry.user : context?.user ?? null;
|
|
650
663
|
const apiKey = user?.apiKey;
|
|
651
664
|
const row = {
|
|
@@ -660,7 +673,8 @@ var AuditLog = class {
|
|
|
660
673
|
userAgent: context?.userAgent ? context.userAgent.slice(0, 200) : null,
|
|
661
674
|
changes: entry.changes && entry.changes.length > 0 ? entry.changes : null,
|
|
662
675
|
detail: apiKey ? { ...entry.detail, apiKey: apiKey.name ?? "" } : entry.detail ?? null,
|
|
663
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
676
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
677
|
+
...scope ? { scope } : {}
|
|
664
678
|
};
|
|
665
679
|
row.signature = this.sign(row);
|
|
666
680
|
row.updatedAt = row.createdAt;
|
|
@@ -676,7 +690,7 @@ var AuditLog = class {
|
|
|
676
690
|
* A change to a document or global: what changed between `before` and `after`. Internal
|
|
677
691
|
* collections (sessions, versions…) are not logged.
|
|
678
692
|
*/
|
|
679
|
-
async content(config, global2, action, after, before, user) {
|
|
693
|
+
async content(config, global2, action, after, before, user, context) {
|
|
680
694
|
if (!this.enabled) return;
|
|
681
695
|
if (!global2 && INTERNAL_COLLECTIONS.has(config.slug)) return;
|
|
682
696
|
const doc = after ?? before;
|
|
@@ -692,7 +706,8 @@ var AuditLog = class {
|
|
|
692
706
|
title,
|
|
693
707
|
// A trusted call without a user keeps the request's (or is the system).
|
|
694
708
|
...user ? { user } : {},
|
|
695
|
-
changes: action === "delete" ? null : this.diff(config.fields, before ?? {}, after ?? {})
|
|
709
|
+
changes: action === "delete" ? null : this.diff(config.fields, before ?? {}, after ?? {}),
|
|
710
|
+
...context && Object.keys(context).length > 0 ? { context } : {}
|
|
696
711
|
});
|
|
697
712
|
}
|
|
698
713
|
/** Fields whose stored value changed. Hidden ones never (the password only as "changed"). */
|
|
@@ -837,8 +852,15 @@ var AuditLog = class {
|
|
|
837
852
|
return out;
|
|
838
853
|
}
|
|
839
854
|
// -------------------------------------------------------------------------
|
|
855
|
+
/** The part of the site a context is in (`scope`), or `null`. */
|
|
856
|
+
scopeOf(context) {
|
|
857
|
+
const scope = this.settings.scope;
|
|
858
|
+
return (scope && context ? scope(context) : null) ?? null;
|
|
859
|
+
}
|
|
840
860
|
sign(row) {
|
|
841
|
-
const
|
|
861
|
+
const values = SIGNED.map((key2) => row[key2] ?? null);
|
|
862
|
+
if (typeof row.scope === "string" && row.scope) values.push(row.scope);
|
|
863
|
+
const payload = stable(values);
|
|
842
864
|
return createHmac("sha256", this.cms.config.secret).update(`audit:${payload}`).digest("base64url");
|
|
843
865
|
}
|
|
844
866
|
async lastVerification() {
|
|
@@ -866,6 +888,7 @@ var AuditLog = class {
|
|
|
866
888
|
if (filter.actor) parts.push({ actorEmail: { like: filter.actor } });
|
|
867
889
|
if (filter.from) parts.push({ createdAt: { gte: filter.from } });
|
|
868
890
|
if (filter.to) parts.push({ createdAt: { lte: filter.to } });
|
|
891
|
+
if (filter.scope) parts.push({ scope: { equals: filter.scope } });
|
|
869
892
|
return parts.length === 0 ? void 0 : parts.length === 1 ? parts[0] : { and: parts };
|
|
870
893
|
}
|
|
871
894
|
};
|
|
@@ -1137,6 +1160,7 @@ function sizeKey(filename, size) {
|
|
|
1137
1160
|
var PRIVATE_KEY = /^[\p{L}\p{M}\p{N}-]+\.private(-[\p{L}\p{N}_-]+)?\.[a-z0-9]+$/u;
|
|
1138
1161
|
|
|
1139
1162
|
// src/fields.ts
|
|
1163
|
+
var uniqueWithinOf = (field) => field.uniqueWithin === void 0 ? [] : [field.uniqueWithin].flat();
|
|
1140
1164
|
var isHasMany = (field) => (field.type === "select" || field.type === "relationship" || field.type === "upload") && field.hasMany === true;
|
|
1141
1165
|
function mimeAllowedBy(mimeType, patterns) {
|
|
1142
1166
|
const type = mimeType.toLowerCase();
|
|
@@ -2890,7 +2914,7 @@ async function normalizeValue(field, raw, path, options, fail, errors, reference
|
|
|
2890
2914
|
field: path,
|
|
2891
2915
|
collection: target,
|
|
2892
2916
|
id,
|
|
2893
|
-
...field.
|
|
2917
|
+
...field.filterOptions ? { filterOptions: field.filterOptions } : {},
|
|
2894
2918
|
...field.type === "upload" && field.mimeTypes ? { mimeTypes: field.mimeTypes } : {},
|
|
2895
2919
|
...field.type === "upload" && field.folder && field.folderOnly ? { folderOnly: field.folder } : {}
|
|
2896
2920
|
});
|
|
@@ -3376,7 +3400,8 @@ var MediaFolders = class {
|
|
|
3376
3400
|
name: String(doc.name ?? ""),
|
|
3377
3401
|
key: typeof doc.key === "string" && doc.key ? doc.key : null,
|
|
3378
3402
|
private: doc.private === true,
|
|
3379
|
-
permissions: permissions && typeof permissions === "object" && !Array.isArray(permissions) ? permissions : null
|
|
3403
|
+
permissions: permissions && typeof permissions === "object" && !Array.isArray(permissions) ? permissions : null,
|
|
3404
|
+
doc
|
|
3380
3405
|
});
|
|
3381
3406
|
}
|
|
3382
3407
|
this.cache = { at: Date.now(), rows };
|
|
@@ -3446,18 +3471,15 @@ var MediaFolders = class {
|
|
|
3446
3471
|
});
|
|
3447
3472
|
return this.within(rows, [id]).map((f) => ({ id: f, private: this.privateIn(rows, f) }));
|
|
3448
3473
|
}
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
* there is none yet.
|
|
3452
|
-
*/
|
|
3453
|
-
async keyed(key2) {
|
|
3454
|
-
const find = async () => [...(await this.rows()).values()].find((r) => r.key === key2);
|
|
3474
|
+
async keyed(key2, scope = {}, create = true) {
|
|
3475
|
+
const find = async () => [...(await this.rows()).values()].find((r) => r.key === key2 && inScope(r.doc, scope));
|
|
3455
3476
|
let found = await find();
|
|
3456
3477
|
if (!found) {
|
|
3457
3478
|
this.invalidate();
|
|
3458
3479
|
found = await find();
|
|
3459
3480
|
}
|
|
3460
3481
|
if (found) return found.id;
|
|
3482
|
+
if (!create) return null;
|
|
3461
3483
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3462
3484
|
try {
|
|
3463
3485
|
const doc = await this.db.create({
|
|
@@ -3468,6 +3490,7 @@ var MediaFolders = class {
|
|
|
3468
3490
|
parent: null,
|
|
3469
3491
|
permissions: null,
|
|
3470
3492
|
private: false,
|
|
3493
|
+
...scope,
|
|
3471
3494
|
createdAt: now,
|
|
3472
3495
|
updatedAt: now
|
|
3473
3496
|
}
|
|
@@ -3482,10 +3505,10 @@ var MediaFolders = class {
|
|
|
3482
3505
|
}
|
|
3483
3506
|
}
|
|
3484
3507
|
/** Whether a file in `folder` is in the folder with this key, or below it. */
|
|
3485
|
-
async inKeyed(key2, folder) {
|
|
3508
|
+
async inKeyed(key2, folder, scope = {}) {
|
|
3486
3509
|
if (folder === null) return false;
|
|
3487
3510
|
const rows = await this.rows();
|
|
3488
|
-
const root = [...rows.values()].find((r) => r.key === key2);
|
|
3511
|
+
const root = [...rows.values()].find((r) => r.key === key2 && inScope(r.doc, scope));
|
|
3489
3512
|
return !!root && this.within(rows, [root.id]).some((id) => String(id) === String(folder));
|
|
3490
3513
|
}
|
|
3491
3514
|
/** The user's level in a folder (`null`: the top level). */
|
|
@@ -3610,6 +3633,11 @@ var MediaFolders = class {
|
|
|
3610
3633
|
return this.rows();
|
|
3611
3634
|
}
|
|
3612
3635
|
};
|
|
3636
|
+
function inScope(row, scope) {
|
|
3637
|
+
return Object.entries(scope).every(
|
|
3638
|
+
([name, value]) => value === null || value === void 0 ? row[name] === null || row[name] === void 0 : String(row[name]) === String(value)
|
|
3639
|
+
);
|
|
3640
|
+
}
|
|
3613
3641
|
|
|
3614
3642
|
// src/populate.ts
|
|
3615
3643
|
var DEFAULT_DEPTH = 1;
|
|
@@ -4099,6 +4127,8 @@ var Roles = class {
|
|
|
4099
4127
|
async canView(user, id) {
|
|
4100
4128
|
if (isSystemAdmin(user)) return true;
|
|
4101
4129
|
if (user.apiKey) return false;
|
|
4130
|
+
if (id === "audit" && user.role === "admin" && user.scoped === true && this.config.audit !== false && typeof this.config.audit.scope === "function")
|
|
4131
|
+
return true;
|
|
4102
4132
|
if (!this.enabled) return id.startsWith("page:") || id.startsWith("widget:");
|
|
4103
4133
|
return (await this.permissionsOf(user.role)).admin?.includes(id) ?? false;
|
|
4104
4134
|
}
|
|
@@ -5528,17 +5558,20 @@ function validateField(field, path, siblings, collectionSlugs, add) {
|
|
|
5528
5558
|
}
|
|
5529
5559
|
}
|
|
5530
5560
|
if (field.uniqueWithin !== void 0) {
|
|
5531
|
-
|
|
5532
|
-
if (!field.unique && field.type !== "slug") {
|
|
5561
|
+
if (!field.unique && field.type !== "slug")
|
|
5533
5562
|
add(`${path}.uniqueWithin`, "needs `unique: true` (or a slug field)");
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5563
|
+
else
|
|
5564
|
+
for (const name of [field.uniqueWithin].flat()) {
|
|
5565
|
+
const scope = siblings.get(name);
|
|
5566
|
+
if (!scope || scope === field) {
|
|
5567
|
+
add(`${path}.uniqueWithin`, `no sibling field named "${name}"`);
|
|
5568
|
+
} else if (!["relationship", "select", "text", "number"].includes(scope.type) || "hasMany" in scope && scope.hasMany || scope.localized) {
|
|
5569
|
+
add(
|
|
5570
|
+
`${path}.uniqueWithin`,
|
|
5571
|
+
`"${name}" must be a single, unlocalized relationship, select, text or number field`
|
|
5572
|
+
);
|
|
5573
|
+
}
|
|
5574
|
+
}
|
|
5542
5575
|
}
|
|
5543
5576
|
switch (field.type) {
|
|
5544
5577
|
case "text":
|
|
@@ -5914,7 +5947,8 @@ function resolveAudit(audit) {
|
|
|
5914
5947
|
return {
|
|
5915
5948
|
keep: audit.keep ?? DEFAULT_AUDIT_KEEP,
|
|
5916
5949
|
values: audit.values !== false,
|
|
5917
|
-
failedLogins: audit.failedLogins ?? DEFAULT_FAILED_LOGINS
|
|
5950
|
+
failedLogins: audit.failedLogins ?? DEFAULT_FAILED_LOGINS,
|
|
5951
|
+
scope: audit.scope ?? null
|
|
5918
5952
|
};
|
|
5919
5953
|
}
|
|
5920
5954
|
|
|
@@ -7809,7 +7843,8 @@ var EasyCMS = class {
|
|
|
7809
7843
|
action,
|
|
7810
7844
|
event === "delete" ? void 0 : doc,
|
|
7811
7845
|
event === "create" ? void 0 : previous,
|
|
7812
|
-
guard.user
|
|
7846
|
+
guard.user,
|
|
7847
|
+
guard.context
|
|
7813
7848
|
);
|
|
7814
7849
|
}
|
|
7815
7850
|
/** Records a version after a save, when the collection or global keeps versions. */
|
|
@@ -7919,7 +7954,7 @@ var EasyCMS = class {
|
|
|
7919
7954
|
await this.makeSlugsUnique(config, clean2, selfId);
|
|
7920
7955
|
errors.push(...await this.checkUnique(config, clean2, selfId));
|
|
7921
7956
|
}
|
|
7922
|
-
errors.push(...await this.checkReferences(result.references));
|
|
7957
|
+
errors.push(...await this.checkReferences(result.references, guard));
|
|
7923
7958
|
if (errors.length === 0)
|
|
7924
7959
|
errors.push(...await this.checkFilterOptions(result.references, selfId, guard));
|
|
7925
7960
|
if (errors.length > 0) throw new ValidationError(config.slug, errors);
|
|
@@ -7949,7 +7984,11 @@ var EasyCMS = class {
|
|
|
7949
7984
|
async makeSlugsUnique(config, data, selfId) {
|
|
7950
7985
|
for (const field of config.fields) {
|
|
7951
7986
|
if (field.type !== "slug") continue;
|
|
7952
|
-
const scope = field.uniqueWithin === void 0 ? void 0 : {
|
|
7987
|
+
const scope = field.uniqueWithin === void 0 ? void 0 : {
|
|
7988
|
+
and: uniqueWithinOf(field).map((name) => ({
|
|
7989
|
+
[name]: { equals: data[name] ?? null }
|
|
7990
|
+
}))
|
|
7991
|
+
};
|
|
7953
7992
|
const unique = async (path, base) => {
|
|
7954
7993
|
let candidate = base;
|
|
7955
7994
|
for (let n = 2; await this.isTaken(config.slug, path, candidate, selfId, scope); n++) {
|
|
@@ -7980,7 +8019,11 @@ var EasyCMS = class {
|
|
|
7980
8019
|
locale === this.config.localization?.defaultLocale ? field.name : `${field.name}.${locale}`,
|
|
7981
8020
|
v
|
|
7982
8021
|
]) : [[field.name, field.name, value]];
|
|
7983
|
-
const scope = field.uniqueWithin === void 0 ? void 0 : {
|
|
8022
|
+
const scope = field.uniqueWithin === void 0 ? void 0 : {
|
|
8023
|
+
and: uniqueWithinOf(field).map((name) => ({
|
|
8024
|
+
[name]: { equals: data[name] ?? null }
|
|
8025
|
+
}))
|
|
8026
|
+
};
|
|
7984
8027
|
for (const [path, errorField, v] of checks) {
|
|
7985
8028
|
if (v === null || v === void 0) continue;
|
|
7986
8029
|
if (await this.isTaken(config.slug, path, v, selfId, scope)) {
|
|
@@ -8000,6 +8043,46 @@ var EasyCMS = class {
|
|
|
8000
8043
|
};
|
|
8001
8044
|
return await this.db.count({ collection: collection2, where }) > 0;
|
|
8002
8045
|
}
|
|
8046
|
+
/**
|
|
8047
|
+
* What folder keys are unique within for a call (`uniqueWithin` of the `key` field of media
|
|
8048
|
+
* folders, e.g. the tenant): the value a folder made by this call would get from its hooks.
|
|
8049
|
+
*/
|
|
8050
|
+
async folderScope(guard) {
|
|
8051
|
+
return this.uniqueScope(MEDIA_FOLDERS, "key", guard);
|
|
8052
|
+
}
|
|
8053
|
+
/**
|
|
8054
|
+
* Where a unique field's values must differ for a call (`uniqueWithin`, e.g. the tenant): the
|
|
8055
|
+
* value a document created by this call would get there from its hooks, as `{ [field]: value }`;
|
|
8056
|
+
* `{}` when the field is unique everywhere. For finding documents by such a field, e.g. a page
|
|
8057
|
+
* by its path in the current tenant.
|
|
8058
|
+
*/
|
|
8059
|
+
async uniqueScope(collection2, field, options = {}) {
|
|
8060
|
+
const config = this.config.collections.find((c) => c.slug === collection2);
|
|
8061
|
+
const target = config?.fields.find((f) => f.name === field);
|
|
8062
|
+
const within = target ? uniqueWithinOf(target) : [];
|
|
8063
|
+
if (!config || within.length === 0) return {};
|
|
8064
|
+
const guard = {
|
|
8065
|
+
enforce: false,
|
|
8066
|
+
user: options.user ?? null,
|
|
8067
|
+
context: options.context ?? NO_CONTEXT
|
|
8068
|
+
};
|
|
8069
|
+
const data = await this.transform(
|
|
8070
|
+
config.hooks?.beforeValidate,
|
|
8071
|
+
"data",
|
|
8072
|
+
{ ...this.hookArgs(config, guard), operation: "create" },
|
|
8073
|
+
{}
|
|
8074
|
+
).catch(() => ({}));
|
|
8075
|
+
return Object.fromEntries(within.map((name) => [name, data[name] ?? null]));
|
|
8076
|
+
}
|
|
8077
|
+
/**
|
|
8078
|
+
* @internal The id of the media folder with this key (`folder` of upload fields), made when
|
|
8079
|
+
* needed; `null` when the call has no scope to make it in (e.g. all tenants) and none exists.
|
|
8080
|
+
*/
|
|
8081
|
+
async keyedFolder(key2, options = {}) {
|
|
8082
|
+
const scope = await this.folderScope(guardOf(options));
|
|
8083
|
+
const complete = Object.values(scope).every((v) => v !== null && v !== void 0);
|
|
8084
|
+
return this.folders.keyed(key2, scope, complete);
|
|
8085
|
+
}
|
|
8003
8086
|
/** Each reference is one its relationship's `filterOptions` allow. */
|
|
8004
8087
|
async checkFilterOptions(references2, selfId, guard) {
|
|
8005
8088
|
const errors = [];
|
|
@@ -8025,7 +8108,7 @@ var EasyCMS = class {
|
|
|
8025
8108
|
}
|
|
8026
8109
|
return errors;
|
|
8027
8110
|
}
|
|
8028
|
-
async checkReferences(references2) {
|
|
8111
|
+
async checkReferences(references2, guard) {
|
|
8029
8112
|
const byCollection = /* @__PURE__ */ new Map();
|
|
8030
8113
|
for (const ref of references2) {
|
|
8031
8114
|
if (!this.config.collections.some((c) => c.slug === ref.collection)) continue;
|
|
@@ -8054,7 +8137,8 @@ var EasyCMS = class {
|
|
|
8054
8137
|
}
|
|
8055
8138
|
if (ref.folderOnly && this.folders.enabled && !await this.folders.inKeyed(
|
|
8056
8139
|
ref.folderOnly,
|
|
8057
|
-
doc.folder ?? null
|
|
8140
|
+
doc.folder ?? null,
|
|
8141
|
+
await this.folderScope(guard)
|
|
8058
8142
|
))
|
|
8059
8143
|
errors.push({
|
|
8060
8144
|
field: ref.field,
|
|
@@ -8400,7 +8484,7 @@ function componentRef(component) {
|
|
|
8400
8484
|
async function allowed(access, user, context) {
|
|
8401
8485
|
return await evaluateAccess(access, { user, context }) !== false;
|
|
8402
8486
|
}
|
|
8403
|
-
async function serializeFields(fields, checks, localized, folderId) {
|
|
8487
|
+
async function serializeFields(fields, checks, localized, folderId, who) {
|
|
8404
8488
|
const { update: update2 } = checks;
|
|
8405
8489
|
const out = [];
|
|
8406
8490
|
for (const field of fields) {
|
|
@@ -8410,6 +8494,13 @@ async function serializeFields(fields, checks, localized, folderId) {
|
|
|
8410
8494
|
if (field.required) f.required = true;
|
|
8411
8495
|
if (field.unique) f.unique = true;
|
|
8412
8496
|
if (field.defaultValue !== void 0) f.defaultValue = field.defaultValue;
|
|
8497
|
+
if (field.admin?.defaultValue) {
|
|
8498
|
+
const value = await field.admin.defaultValue(who);
|
|
8499
|
+
if (value !== void 0) f.defaultValue = value;
|
|
8500
|
+
}
|
|
8501
|
+
const column = field.admin?.column;
|
|
8502
|
+
if (column === true || typeof column === "function" && await column(who)) f.column = true;
|
|
8503
|
+
if (field.admin?.allowCreate === false) f.noCreate = true;
|
|
8413
8504
|
if (!await update2.allows(field)) f.readOnly = true;
|
|
8414
8505
|
if (field.localized && localized) f.localized = true;
|
|
8415
8506
|
if (field.position === "sidebar") f.position = "sidebar";
|
|
@@ -8450,16 +8541,19 @@ async function serializeFields(fields, checks, localized, folderId) {
|
|
|
8450
8541
|
case "upload":
|
|
8451
8542
|
f.to = "media";
|
|
8452
8543
|
if (field.hasMany) f.hasMany = true;
|
|
8544
|
+
if (field.filterOptions) f.filtered = true;
|
|
8453
8545
|
if (field.minRows !== void 0) f.minRows = field.minRows;
|
|
8454
8546
|
if (field.maxRows !== void 0) f.maxRows = field.maxRows;
|
|
8455
8547
|
if (field.mimeTypes) f.mimeTypes = expandMimeTypes(field.mimeTypes);
|
|
8456
|
-
if (field.folder && folderId)
|
|
8457
|
-
|
|
8548
|
+
if (field.folder && folderId) {
|
|
8549
|
+
const id = await folderId(field.folder);
|
|
8550
|
+
if (id !== null) f.folder = { id, only: field.folderOnly === true };
|
|
8551
|
+
}
|
|
8458
8552
|
break;
|
|
8459
8553
|
case "array":
|
|
8460
8554
|
if (field.minRows !== void 0) f.minRows = field.minRows;
|
|
8461
8555
|
if (field.maxRows !== void 0) f.maxRows = field.maxRows;
|
|
8462
|
-
f.fields = await serializeFields(field.fields, checks, localized, folderId);
|
|
8556
|
+
f.fields = await serializeFields(field.fields, checks, localized, folderId, who);
|
|
8463
8557
|
break;
|
|
8464
8558
|
case "blocks":
|
|
8465
8559
|
if (field.minRows !== void 0) f.minRows = field.minRows;
|
|
@@ -8468,12 +8562,12 @@ async function serializeFields(fields, checks, localized, folderId) {
|
|
|
8468
8562
|
field.blocks.map(async (block) => ({
|
|
8469
8563
|
slug: block.slug,
|
|
8470
8564
|
...block.labels ? { labels: block.labels } : {},
|
|
8471
|
-
fields: await serializeFields(block.fields, checks, localized, folderId)
|
|
8565
|
+
fields: await serializeFields(block.fields, checks, localized, folderId, who)
|
|
8472
8566
|
}))
|
|
8473
8567
|
);
|
|
8474
8568
|
break;
|
|
8475
8569
|
case "group":
|
|
8476
|
-
f.fields = await serializeFields(field.fields, checks, localized, folderId);
|
|
8570
|
+
f.fields = await serializeFields(field.fields, checks, localized, folderId, who);
|
|
8477
8571
|
break;
|
|
8478
8572
|
}
|
|
8479
8573
|
out.push(f);
|
|
@@ -8496,7 +8590,8 @@ async function collection(cms, config, user, localized, roles, context) {
|
|
|
8496
8590
|
config.fields,
|
|
8497
8591
|
await checkers(cms, user, context),
|
|
8498
8592
|
localized,
|
|
8499
|
-
folderIds(cms)
|
|
8593
|
+
folderIds(cms, user, context),
|
|
8594
|
+
{ user, context }
|
|
8500
8595
|
);
|
|
8501
8596
|
if (config.slug === USERS && cms.roles.enabled) {
|
|
8502
8597
|
const role = fields.find((f) => f.name === "role");
|
|
@@ -8527,6 +8622,7 @@ async function collection(cms, config, user, localized, roles, context) {
|
|
|
8527
8622
|
if (config.admin?.group === "settings" || SETTINGS.has(config.slug)) result.group = "settings";
|
|
8528
8623
|
if (config.admin?.list?.tree) result.tree = config.admin.list.tree;
|
|
8529
8624
|
if (config.admin?.list?.sort) result.defaultSort = config.admin.list.sort;
|
|
8625
|
+
if (config.admin?.confirmDelete) result.confirmDelete = { ...config.admin.confirmDelete };
|
|
8530
8626
|
const owner = cms.roles.ownerOf(config.slug);
|
|
8531
8627
|
if (owner && fields.some((f) => f.name === owner)) result.owner = owner;
|
|
8532
8628
|
if (config.editIn === "drawer" && !result.drafts && !result.versions && !result.preview)
|
|
@@ -8542,7 +8638,10 @@ async function global(cms, config, user, localized, context) {
|
|
|
8542
8638
|
versions: Boolean(config.versions),
|
|
8543
8639
|
preview: typeof config.preview === "function",
|
|
8544
8640
|
schedule: config.schedule === true,
|
|
8545
|
-
fields: await serializeFields(config.fields, checks, localized, folderIds(cms)
|
|
8641
|
+
fields: await serializeFields(config.fields, checks, localized, folderIds(cms, user, context), {
|
|
8642
|
+
user,
|
|
8643
|
+
context
|
|
8644
|
+
}),
|
|
8546
8645
|
permissions: {
|
|
8547
8646
|
read: await may(cms, config.access?.read, user, target, "read", context),
|
|
8548
8647
|
update: await may(cms, config.access?.update, user, target, "update", context),
|
|
@@ -8643,12 +8742,12 @@ async function widgets(cms, user) {
|
|
|
8643
8742
|
}
|
|
8644
8743
|
return out;
|
|
8645
8744
|
}
|
|
8646
|
-
function folderIds(cms) {
|
|
8647
|
-
return cms.folders.enabled ? (key2) => cms.
|
|
8745
|
+
function folderIds(cms, user, context) {
|
|
8746
|
+
return cms.folders.enabled ? (key2) => cms.keyedFolder(key2, { user, context }) : void 0;
|
|
8648
8747
|
}
|
|
8649
8748
|
|
|
8650
8749
|
// src/version.ts
|
|
8651
|
-
var VERSION = true ? "0.
|
|
8750
|
+
var VERSION = true ? "0.45.0" : "0.0.0-dev";
|
|
8652
8751
|
|
|
8653
8752
|
// src/rest/admin-status.ts
|
|
8654
8753
|
var DAY2 = 864e5;
|
|
@@ -8745,7 +8844,8 @@ async function pickerFilter(cms, url, target, user, context = {}) {
|
|
|
8745
8844
|
const slug2 = dot > 0 ? spec.slice(0, dot) : "";
|
|
8746
8845
|
const owner = forCollection !== null ? cms.config.collections.find((c) => c.slug === slug2) : cms.config.globals.find((g) => g.slug === slug2);
|
|
8747
8846
|
const field = owner ? fieldAt(owner.fields, spec.slice(dot + 1)) : void 0;
|
|
8748
|
-
|
|
8847
|
+
const to = field?.type === "upload" ? "media" : field?.type === "relationship" ? field.to : null;
|
|
8848
|
+
if (!field || field.type !== "relationship" && field.type !== "upload" || to !== target)
|
|
8749
8849
|
throw new QueryError(`filterFor: no relationship to "${target}" at "${spec}"`);
|
|
8750
8850
|
if (!field.filterOptions) return void 0;
|
|
8751
8851
|
const rawId = url.searchParams.get("filterId");
|
|
@@ -8927,6 +9027,7 @@ async function respond(cms, request, url, path, root, options, production) {
|
|
|
8927
9027
|
if (method !== "GET" && method !== "HEAD") checkCsrf(cms, ctx);
|
|
8928
9028
|
const audit = {
|
|
8929
9029
|
user,
|
|
9030
|
+
context,
|
|
8930
9031
|
ip: options.getClientIp?.(request),
|
|
8931
9032
|
userAgent: request.headers.get("user-agent")
|
|
8932
9033
|
};
|
|
@@ -9199,8 +9300,11 @@ async function route(cms, ctx, method, segments, options) {
|
|
|
9199
9300
|
doc: q.get("doc"),
|
|
9200
9301
|
actor: q.get("actor"),
|
|
9201
9302
|
from: q.get("from"),
|
|
9202
|
-
to: q.get("to")
|
|
9303
|
+
to: q.get("to"),
|
|
9304
|
+
// Users of a part of the site (e.g. one tenant) see its entries; others the chosen part's.
|
|
9305
|
+
scope: cms.audit.scopeOf(ctx.context)
|
|
9203
9306
|
};
|
|
9307
|
+
if (ctx.user.scoped && !filter.scope) throw new ForbiddenError();
|
|
9204
9308
|
if (second === "audit.csv" && third === void 0) {
|
|
9205
9309
|
if (method !== "GET") throw methodNotAllowed(ctx, "GET");
|
|
9206
9310
|
return {
|
|
@@ -10255,6 +10359,7 @@ export {
|
|
|
10255
10359
|
singularize,
|
|
10256
10360
|
slugify,
|
|
10257
10361
|
sniffMimeType,
|
|
10362
|
+
uniqueWithinOf,
|
|
10258
10363
|
validateConfig,
|
|
10259
10364
|
validateFields,
|
|
10260
10365
|
verifyPassword,
|