UNPKG

@payloadcms/plugin-multi-tenant

Version:
277 lines (276 loc) 12.4 kB
import { defaults } from './defaults.js'; import { tenantField } from './fields/tenantField/index.js'; import { tenantsArrayField } from './fields/tenantsArrayField/index.js'; import { addTenantCleanup } from './hooks/afterTenantDelete.js'; import { filterDocumentsBySelectedTenant } from './list-filters/filterDocumentsBySelectedTenant.js'; import { filterTenantsBySelectedTenant } from './list-filters/filterTenantsBySelectedTenant.js'; import { filterUsersBySelectedTenant } from './list-filters/filterUsersBySelectedTenant.js'; import { addCollectionAccess } from './utilities/addCollectionAccess.js'; import { addFilterOptionsToFields } from './utilities/addFilterOptionsToFields.js'; import { combineListFilters } from './utilities/combineListFilters.js'; export const multiTenantPlugin = (pluginConfig)=>(incomingConfig)=>{ if (pluginConfig.enabled === false) { return incomingConfig; } /** * Set defaults */ const userHasAccessToAllTenants = typeof pluginConfig.userHasAccessToAllTenants === 'function' ? pluginConfig.userHasAccessToAllTenants : ()=>false; const tenantsCollectionSlug = pluginConfig.tenantsSlug = pluginConfig.tenantsSlug || defaults.tenantCollectionSlug; const tenantFieldName = pluginConfig?.tenantField?.name || defaults.tenantFieldName; const tenantsArrayFieldName = pluginConfig?.tenantsArrayField?.arrayFieldName || defaults.tenantsArrayFieldName; const tenantsArrayTenantFieldName = pluginConfig?.tenantsArrayField?.arrayTenantFieldName || defaults.tenantsArrayTenantFieldName; const tenantSelectorLabel = pluginConfig.tenantSelectorLabel || defaults.tenantSelectorLabel; const basePath = pluginConfig.basePath || defaults.basePath; /** * Add defaults for admin properties */ if (!incomingConfig.admin) { incomingConfig.admin = {}; } if (!incomingConfig.admin?.components) { incomingConfig.admin.components = { actions: [], beforeNavLinks: [], providers: [] }; } if (!incomingConfig.admin.components?.providers) { incomingConfig.admin.components.providers = []; } if (!incomingConfig.admin.components?.actions) { incomingConfig.admin.components.actions = []; } if (!incomingConfig.admin.components?.beforeNavLinks) { incomingConfig.admin.components.beforeNavLinks = []; } if (!incomingConfig.collections) { incomingConfig.collections = []; } /** * Add tenant selector localized labels */ if (typeof tenantSelectorLabel === 'object') { if (!incomingConfig.i18n) { incomingConfig.i18n = {}; } Object.entries(tenantSelectorLabel).forEach(([_locale, label])=>{ const locale = _locale; if (!incomingConfig.i18n) { incomingConfig.i18n = {}; } if (!incomingConfig.i18n.translations) { incomingConfig.i18n.translations = {}; } if (!(locale in incomingConfig.i18n.translations)) { incomingConfig.i18n.translations[locale] = {}; } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error if (!('multiTenant' in incomingConfig.i18n.translations[locale])) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error incomingConfig.i18n.translations[locale].multiTenant = {}; } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error incomingConfig.i18n.translations[locale].multiTenant.selectorLabel = label; }); } /** * Add tenants array field to users collection */ const adminUsersCollection = incomingConfig.collections.find(({ slug, auth })=>{ if (incomingConfig.admin?.user) { return slug === incomingConfig.admin.user; } else if (auth) { return true; } }); if (!adminUsersCollection) { throw Error('An auth enabled collection was not found'); } /** * Add tenants array field to users collection */ if (pluginConfig?.tenantsArrayField?.includeDefaultField !== false) { adminUsersCollection.fields.push(tenantsArrayField({ ...pluginConfig?.tenantsArrayField || {}, tenantsArrayFieldName, tenantsArrayTenantFieldName, tenantsCollectionSlug })); } addCollectionAccess({ adminUsersSlug: adminUsersCollection.slug, collection: adminUsersCollection, fieldName: `${tenantsArrayFieldName}.${tenantsArrayTenantFieldName}`, tenantsArrayFieldName, tenantsArrayTenantFieldName, userHasAccessToAllTenants }); if (pluginConfig.useUsersTenantFilter !== false) { if (!adminUsersCollection.admin) { adminUsersCollection.admin = {}; } adminUsersCollection.admin.baseListFilter = combineListFilters({ baseListFilter: adminUsersCollection.admin?.baseListFilter, customFilter: (args)=>filterUsersBySelectedTenant({ req: args.req, tenantsArrayFieldName, tenantsArrayTenantFieldName, tenantsCollectionSlug }) }); } let tenantCollection; const [collectionSlugs, globalCollectionSlugs] = Object.keys(pluginConfig.collections).reduce((acc, slug)=>{ if (pluginConfig?.collections?.[slug]?.isGlobal) { acc[1].push(slug); } else { acc[0].push(slug); } return acc; }, [ [], [] ]); /** * Modify collections */ incomingConfig.collections.forEach((collection)=>{ /** * Modify tenants collection */ if (collection.slug === tenantsCollectionSlug) { tenantCollection = collection; if (pluginConfig.useTenantsCollectionAccess !== false) { /** * Add access control constraint to tenants collection * - constrains access a users assigned tenants */ addCollectionAccess({ adminUsersSlug: adminUsersCollection.slug, collection, fieldName: 'id', tenantsArrayFieldName, tenantsArrayTenantFieldName, userHasAccessToAllTenants }); } if (pluginConfig.useTenantsListFilter !== false) { /** * Add list filter to tenants collection * - filter by selected tenant */ if (!collection.admin) { collection.admin = {}; } collection.admin.baseListFilter = combineListFilters({ baseListFilter: collection.admin?.baseListFilter, customFilter: (args)=>filterTenantsBySelectedTenant({ req: args.req, tenantsCollectionSlug }) }); } if (pluginConfig.cleanupAfterTenantDelete !== false) { /** * Add cleanup logic when tenant is deleted * - delete documents related to tenant * - remove tenant from users */ addTenantCleanup({ collection, enabledSlugs: [ ...collectionSlugs, ...globalCollectionSlugs ], tenantFieldName, tenantsCollectionSlug, usersSlug: adminUsersCollection.slug, usersTenantsArrayFieldName: tenantsArrayFieldName, usersTenantsArrayTenantFieldName: tenantsArrayTenantFieldName }); } } else if (pluginConfig.collections?.[collection.slug]) { const isGlobal = Boolean(pluginConfig.collections[collection.slug]?.isGlobal); if (isGlobal) { collection.disableDuplicate = true; } /** * Modify enabled collections */ addFilterOptionsToFields({ config: incomingConfig, fields: collection.fields, tenantEnabledCollectionSlugs: collectionSlugs, tenantEnabledGlobalSlugs: globalCollectionSlugs, tenantFieldName, tenantsCollectionSlug }); /** * Add tenant field to enabled collections */ collection.fields.splice(0, 0, tenantField({ ...pluginConfig?.tenantField || {}, name: tenantFieldName, debug: pluginConfig.debug, tenantsCollectionSlug, unique: isGlobal })); if (pluginConfig.collections[collection.slug]?.useBaseListFilter !== false) { /** * Add list filter to enabled collections * - filters results by selected tenant */ if (!collection.admin) { collection.admin = {}; } collection.admin.baseListFilter = combineListFilters({ baseListFilter: collection.admin?.baseListFilter, customFilter: (args)=>filterDocumentsBySelectedTenant({ req: args.req, tenantFieldName, tenantsCollectionSlug }) }); } if (pluginConfig.collections[collection.slug]?.useTenantAccess !== false) { /** * Add access control constraint to tenant enabled collection */ addCollectionAccess({ adminUsersSlug: adminUsersCollection.slug, collection, fieldName: tenantFieldName, tenantsArrayFieldName, tenantsArrayTenantFieldName, userHasAccessToAllTenants }); } } }); if (!tenantCollection) { throw new Error(`Tenants collection not found with slug: ${tenantsCollectionSlug}`); } /** * Add TenantSelectionProvider to admin providers */ incomingConfig.admin.components.providers.push({ clientProps: { tenantsCollectionSlug: tenantCollection.slug, useAsTitle: tenantCollection.admin?.useAsTitle || 'id' }, path: '@payloadcms/plugin-multi-tenant/rsc#TenantSelectionProvider' }); /** * Add global redirect action */ if (globalCollectionSlugs.length) { incomingConfig.admin.components.actions.push({ path: '@payloadcms/plugin-multi-tenant/rsc#GlobalViewRedirect', serverProps: { basePath, globalSlugs: globalCollectionSlugs, tenantFieldName, tenantsCollectionSlug, useAsTitle: tenantCollection.admin?.useAsTitle || 'id' } }); } /** * Add tenant selector to admin UI */ incomingConfig.admin.components.beforeNavLinks.push({ clientProps: { label: tenantSelectorLabel }, path: '@payloadcms/plugin-multi-tenant/client#TenantSelector' }); return incomingConfig; }; //# sourceMappingURL=index.js.map