feat(plugin-multi-tenant): filter users list and tenants lists (#11417)
### What? - Adds `users` base list filtering when tenant is selected - Adds `tenants` base list filtering when tenant is selected
This commit is contained in:
@@ -52,7 +52,7 @@ The plugin accepts an object with the following properties:
|
||||
|
||||
```ts
|
||||
type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
||||
/**
|
||||
/**
|
||||
* After a tenant is deleted, the plugin will attempt to clean up related documents
|
||||
* - removing documents with the tenant ID
|
||||
* - removing the tenant from users
|
||||
@@ -176,6 +176,14 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
||||
* Opt out of adding access constraints to the tenants collection
|
||||
*/
|
||||
useTenantsCollectionAccess?: boolean
|
||||
/**
|
||||
* Opt out including the baseListFilter to filter tenants by selected tenant
|
||||
*/
|
||||
useTenantsListFilter?: boolean
|
||||
/**
|
||||
* Opt out including the baseListFilter to filter users by selected tenant
|
||||
*/
|
||||
useUsersTenantFilter?: boolean
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export { filterDocumentsBySelectedTenant as getTenantListFilter } from '../list-filters/filterDocumentsBySelectedTenant.js'
|
||||
export { getGlobalViewRedirect } from '../utilities/getGlobalViewRedirect.js'
|
||||
export { getTenantAccess } from '../utilities/getTenantAccess.js'
|
||||
export { getTenantFromCookie } from '../utilities/getTenantFromCookie.js'
|
||||
export { getTenantListFilter } from '../utilities/getTenantListFilter.js'
|
||||
export { getUserTenantIDs } from '../utilities/getUserTenantIDs.js'
|
||||
|
||||
@@ -6,9 +6,12 @@ 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 { withTenantListFilter } from './utilities/withTenantListFilter.js'
|
||||
import { combineListFilters } from './utilities/combineListFilters.js'
|
||||
|
||||
export const multiTenantPlugin =
|
||||
<ConfigType>(pluginConfig: MultiTenantPluginConfig<ConfigType>) =>
|
||||
@@ -97,6 +100,23 @@ export const multiTenantPlugin =
|
||||
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: CollectionConfig | undefined
|
||||
|
||||
const [collectionSlugs, globalCollectionSlugs] = Object.keys(pluginConfig.collections).reduce<
|
||||
@@ -138,6 +158,25 @@ export const multiTenantPlugin =
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
@@ -195,10 +234,15 @@ export const multiTenantPlugin =
|
||||
if (!collection.admin) {
|
||||
collection.admin = {}
|
||||
}
|
||||
collection.admin.baseListFilter = withTenantListFilter({
|
||||
|
||||
collection.admin.baseListFilter = combineListFilters({
|
||||
baseListFilter: collection.admin?.baseListFilter,
|
||||
tenantFieldName,
|
||||
tenantsCollectionSlug,
|
||||
customFilter: (args) =>
|
||||
filterDocumentsBySelectedTenant({
|
||||
req: args.req,
|
||||
tenantFieldName,
|
||||
tenantsCollectionSlug,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import type { PayloadRequest, Where } from 'payload'
|
||||
|
||||
import { SELECT_ALL } from '../constants.js'
|
||||
import { getCollectionIDType } from './getCollectionIDType.js'
|
||||
import { getTenantFromCookie } from './getTenantFromCookie.js'
|
||||
import { getCollectionIDType } from '../utilities/getCollectionIDType.js'
|
||||
import { getTenantFromCookie } from '../utilities/getTenantFromCookie.js'
|
||||
|
||||
type Args = {
|
||||
req: PayloadRequest
|
||||
tenantFieldName: string
|
||||
tenantsCollectionSlug: string
|
||||
}
|
||||
export const getTenantListFilter = ({
|
||||
export const filterDocumentsBySelectedTenant = ({
|
||||
req,
|
||||
tenantFieldName,
|
||||
tenantsCollectionSlug,
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { PayloadRequest, Where } from 'payload'
|
||||
|
||||
import { SELECT_ALL } from '../constants.js'
|
||||
import { getCollectionIDType } from '../utilities/getCollectionIDType.js'
|
||||
import { getTenantFromCookie } from '../utilities/getTenantFromCookie.js'
|
||||
|
||||
type Args = {
|
||||
req: PayloadRequest
|
||||
tenantsCollectionSlug: string
|
||||
}
|
||||
export const filterTenantsBySelectedTenant = ({
|
||||
req,
|
||||
tenantsCollectionSlug,
|
||||
}: Args): null | Where => {
|
||||
const idType = getCollectionIDType({
|
||||
collectionSlug: tenantsCollectionSlug,
|
||||
payload: req.payload,
|
||||
})
|
||||
const selectedTenant = getTenantFromCookie(req.headers, idType)
|
||||
|
||||
if (selectedTenant === SELECT_ALL) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
id: {
|
||||
equals: selectedTenant,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { PayloadRequest, Where } from 'payload'
|
||||
|
||||
import { SELECT_ALL } from '../constants.js'
|
||||
import { getCollectionIDType } from '../utilities/getCollectionIDType.js'
|
||||
import { getTenantFromCookie } from '../utilities/getTenantFromCookie.js'
|
||||
|
||||
type Args = {
|
||||
req: PayloadRequest
|
||||
tenantsArrayFieldName: string
|
||||
tenantsArrayTenantFieldName: string
|
||||
tenantsCollectionSlug: string
|
||||
}
|
||||
/**
|
||||
* Filter the list of users by the selected tenant
|
||||
*/
|
||||
export const filterUsersBySelectedTenant = ({
|
||||
req,
|
||||
tenantsArrayFieldName,
|
||||
tenantsArrayTenantFieldName,
|
||||
tenantsCollectionSlug,
|
||||
}: Args): null | Where => {
|
||||
const idType = getCollectionIDType({
|
||||
collectionSlug: tenantsCollectionSlug,
|
||||
payload: req.payload,
|
||||
})
|
||||
const selectedTenant = getTenantFromCookie(req.headers, idType)
|
||||
|
||||
if (selectedTenant === SELECT_ALL) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
[`${tenantsArrayFieldName}.${tenantsArrayTenantFieldName}`]: {
|
||||
in: [selectedTenant],
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -125,6 +125,14 @@ export type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
||||
* Opt out of adding access constraints to the tenants collection
|
||||
*/
|
||||
useTenantsCollectionAccess?: boolean
|
||||
/**
|
||||
* Opt out including the baseListFilter to filter tenants by selected tenant
|
||||
*/
|
||||
useTenantsListFilter?: boolean
|
||||
/**
|
||||
* Opt out including the baseListFilter to filter users by selected tenant
|
||||
*/
|
||||
useUsersTenantFilter?: boolean
|
||||
}
|
||||
|
||||
export type Tenant<IDType = number | string> = {
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import type { BaseListFilter, Where } from 'payload'
|
||||
|
||||
import { getTenantListFilter } from './getTenantListFilter.js'
|
||||
|
||||
type Args = {
|
||||
baseListFilter?: BaseListFilter
|
||||
tenantFieldName: string
|
||||
tenantsCollectionSlug: string
|
||||
customFilter: BaseListFilter
|
||||
}
|
||||
/**
|
||||
* Combines a base list filter with a tenant list filter
|
||||
*
|
||||
* Combines where constraints inside of an AND operator
|
||||
*/
|
||||
export const withTenantListFilter =
|
||||
({ baseListFilter, tenantFieldName, tenantsCollectionSlug }: Args): BaseListFilter =>
|
||||
export const combineListFilters =
|
||||
({ baseListFilter, customFilter }: Args): BaseListFilter =>
|
||||
async (args) => {
|
||||
const filterConstraints = []
|
||||
|
||||
@@ -25,14 +22,10 @@ export const withTenantListFilter =
|
||||
}
|
||||
}
|
||||
|
||||
const tenantListFilter = getTenantListFilter({
|
||||
req: args.req,
|
||||
tenantFieldName,
|
||||
tenantsCollectionSlug,
|
||||
})
|
||||
const customFilterResult = await customFilter(args)
|
||||
|
||||
if (tenantListFilter) {
|
||||
filterConstraints.push(tenantListFilter)
|
||||
if (customFilterResult) {
|
||||
filterConstraints.push(customFilterResult)
|
||||
}
|
||||
|
||||
if (filterConstraints.length) {
|
||||
Reference in New Issue
Block a user