Skip to content

Commit c1c64a0

Browse files
fix(plugin-multi-tenant): issue #10740 - "The following field is invalid: Assigned Tenant" (#10764)
### What? Tenant ID is a `number` but the `beforeChange` hook shows it as a `string`. Getting tenant ID from cookie does not work properly in PG ### Why? A `ValidationError` is throwing when reading a pg numeric id from the cookie. ### How? Adjust the id based on the idType on the collection. Fixes #10740 --------- Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
1 parent 6a39279 commit c1c64a0

File tree

11 files changed

+108
-16
lines changed

11 files changed

+108
-16
lines changed

packages/plugin-multi-tenant/src/fields/tenantField/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { type RelationshipField } from 'payload'
22
import { APIError } from 'payload'
33

4+
import { getCollectionIDType } from '../../utilities/getCollectionIDType.js'
45
import { getTenantFromCookie } from '../../utilities/getTenantFromCookie.js'
56

67
type Args = {
@@ -39,13 +40,19 @@ export const tenantField = ({
3940
hooks: {
4041
beforeChange: [
4142
({ req, value }) => {
43+
const idType = getCollectionIDType({
44+
collectionSlug: tenantsCollectionSlug,
45+
payload: req.payload,
46+
})
4247
if (!value) {
43-
const tenantFromCookie = getTenantFromCookie(req.headers, req.payload.db.defaultIDType)
48+
const tenantFromCookie = getTenantFromCookie(req.headers, idType)
4449
if (tenantFromCookie) {
4550
return tenantFromCookie
4651
}
4752
throw new APIError('You must select a tenant', 400, null, true)
4853
}
54+
55+
return idType === 'number' ? parseFloat(value) : value
4956
},
5057
],
5158
},

packages/plugin-multi-tenant/src/fields/tenantsArrayField/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export const tenantsArrayField = (args: {
44
arrayFieldAccess?: ArrayField['access']
55
rowFields?: ArrayField['fields']
66
tenantFieldAccess?: RelationshipField['access']
7+
tenantsCollectionSlug: string
78
}): ArrayField => ({
89
name: 'tenants',
910
type: 'array',
@@ -14,7 +15,7 @@ export const tenantsArrayField = (args: {
1415
type: 'relationship',
1516
access: args.tenantFieldAccess,
1617
index: true,
17-
relationTo: 'tenants',
18+
relationTo: args.tenantsCollectionSlug,
1819
required: true,
1920
saveToJWT: true,
2021
},

packages/plugin-multi-tenant/src/hooks/afterTenantDelete.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ import { generateCookie, mergeHeaders } from 'payload'
99

1010
import type { UserWithTenantsField } from '../types.js'
1111

12+
import { getCollectionIDType } from '../utilities/getCollectionIDType.js'
1213
import { getTenantFromCookie } from '../utilities/getTenantFromCookie.js'
1314

1415
type Args = {
1516
collection: CollectionConfig
1617
enabledSlugs: string[]
1718
tenantFieldName: string
19+
tenantsCollectionSlug: string
1820
usersSlug: string
1921
}
2022
/**
@@ -26,6 +28,7 @@ export const addTenantCleanup = ({
2628
collection,
2729
enabledSlugs,
2830
tenantFieldName,
31+
tenantsCollectionSlug,
2932
usersSlug,
3033
}: Args) => {
3134
if (!collection.hooks) {
@@ -38,6 +41,7 @@ export const addTenantCleanup = ({
3841
afterTenantDelete({
3942
enabledSlugs,
4043
tenantFieldName,
44+
tenantsCollectionSlug,
4145
usersSlug,
4246
}),
4347
)
@@ -47,10 +51,15 @@ export const afterTenantDelete =
4751
({
4852
enabledSlugs,
4953
tenantFieldName,
54+
tenantsCollectionSlug,
5055
usersSlug,
5156
}: Omit<Args, 'collection'>): CollectionAfterDeleteHook =>
5257
async ({ id, req }) => {
53-
const currentTenantCookieID = getTenantFromCookie(req.headers, req.payload.db.defaultIDType)
58+
const idType = getCollectionIDType({
59+
collectionSlug: tenantsCollectionSlug,
60+
payload: req.payload,
61+
})
62+
const currentTenantCookieID = getTenantFromCookie(req.headers, idType)
5463
if (currentTenantCookieID === id) {
5564
const newHeaders = new Headers({
5665
'Set-Cookie': generateCookie<string>({

packages/plugin-multi-tenant/src/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,12 @@ export const multiTenantPlugin =
8080
* Add tenants array field to users collection
8181
*/
8282
if (pluginConfig?.tenantsArrayField?.includeDefaultField !== false) {
83-
adminUsersCollection.fields.push(tenantsArrayField(pluginConfig?.tenantsArrayField || {}))
83+
adminUsersCollection.fields.push(
84+
tenantsArrayField({
85+
...(pluginConfig?.tenantsArrayField || {}),
86+
tenantsCollectionSlug,
87+
}),
88+
)
8489
}
8590

8691
addCollectionAccess({
@@ -136,6 +141,7 @@ export const multiTenantPlugin =
136141
collection,
137142
enabledSlugs: [...collectionSlugs, ...globalCollectionSlugs],
138143
tenantFieldName,
144+
tenantsCollectionSlug,
139145
usersSlug: adminUsersCollection.slug,
140146
})
141147
}
@@ -153,6 +159,8 @@ export const multiTenantPlugin =
153159
fields: collection.fields,
154160
tenantEnabledCollectionSlugs: collectionSlugs,
155161
tenantEnabledGlobalSlugs: globalCollectionSlugs,
162+
tenantFieldName,
163+
tenantsCollectionSlug,
156164
})
157165

158166
/**
@@ -180,6 +188,7 @@ export const multiTenantPlugin =
180188
collection.admin.baseListFilter = withTenantListFilter({
181189
baseListFilter: collection.admin?.baseListFilter,
182190
tenantFieldName,
191+
tenantsCollectionSlug,
183192
})
184193
}
185194

packages/plugin-multi-tenant/src/utilities/addFilterOptionsToFields.ts

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
import type { Field, FilterOptionsProps, RelationshipField } from 'payload'
22

3+
import { getCollectionIDType } from './getCollectionIDType.js'
34
import { getTenantFromCookie } from './getTenantFromCookie.js'
45

56
type AddFilterOptionsToFieldsArgs = {
67
fields: Field[]
78
tenantEnabledCollectionSlugs: string[]
89
tenantEnabledGlobalSlugs: string[]
10+
tenantFieldName: string
11+
tenantsCollectionSlug: string
912
}
1013

1114
export function addFilterOptionsToFields({
1215
fields,
1316
tenantEnabledCollectionSlugs,
1417
tenantEnabledGlobalSlugs,
18+
tenantFieldName,
19+
tenantsCollectionSlug,
1520
}: AddFilterOptionsToFieldsArgs) {
1621
fields.forEach((field) => {
1722
if (field.type === 'relationship') {
@@ -26,7 +31,7 @@ export function addFilterOptionsToFields({
2631
)
2732
}
2833
if (tenantEnabledCollectionSlugs.includes(field.relationTo)) {
29-
addFilter(field, tenantEnabledCollectionSlugs)
34+
addFilter({ field, tenantEnabledCollectionSlugs, tenantFieldName, tenantsCollectionSlug })
3035
}
3136
} else {
3237
field.relationTo.map((relationTo) => {
@@ -36,7 +41,12 @@ export function addFilterOptionsToFields({
3641
)
3742
}
3843
if (tenantEnabledCollectionSlugs.includes(relationTo)) {
39-
addFilter(field, tenantEnabledCollectionSlugs)
44+
addFilter({
45+
field,
46+
tenantEnabledCollectionSlugs,
47+
tenantFieldName,
48+
tenantsCollectionSlug,
49+
})
4050
}
4151
})
4252
}
@@ -52,6 +62,8 @@ export function addFilterOptionsToFields({
5262
fields: field.fields,
5363
tenantEnabledCollectionSlugs,
5464
tenantEnabledGlobalSlugs,
65+
tenantFieldName,
66+
tenantsCollectionSlug,
5567
})
5668
}
5769

@@ -61,6 +73,8 @@ export function addFilterOptionsToFields({
6173
fields: block.fields,
6274
tenantEnabledCollectionSlugs,
6375
tenantEnabledGlobalSlugs,
76+
tenantFieldName,
77+
tenantsCollectionSlug,
6478
})
6579
})
6680
}
@@ -71,13 +85,26 @@ export function addFilterOptionsToFields({
7185
fields: tab.fields,
7286
tenantEnabledCollectionSlugs,
7387
tenantEnabledGlobalSlugs,
88+
tenantFieldName,
89+
tenantsCollectionSlug,
7490
})
7591
})
7692
}
7793
})
7894
}
7995

80-
function addFilter(field: RelationshipField, tenantEnabledCollectionSlugs: string[]) {
96+
type AddFilterArgs = {
97+
field: RelationshipField
98+
tenantEnabledCollectionSlugs: string[]
99+
tenantFieldName: string
100+
tenantsCollectionSlug: string
101+
}
102+
function addFilter({
103+
field,
104+
tenantEnabledCollectionSlugs,
105+
tenantFieldName,
106+
tenantsCollectionSlug,
107+
}: AddFilterArgs) {
81108
// User specified filter
82109
const originalFilter = field.filterOptions
83110
field.filterOptions = async (args) => {
@@ -95,7 +122,11 @@ function addFilter(field: RelationshipField, tenantEnabledCollectionSlugs: strin
95122
}
96123

97124
// Custom tenant filter
98-
const tenantFilterResults = filterOptionsByTenant(args)
125+
const tenantFilterResults = filterOptionsByTenant({
126+
...args,
127+
tenantFieldName,
128+
tenantsCollectionSlug,
129+
})
99130

100131
// If the tenant filter returns true, just use the original filter
101132
if (tenantFilterResults === true) {
@@ -115,9 +146,18 @@ function addFilter(field: RelationshipField, tenantEnabledCollectionSlugs: strin
115146

116147
type Args = {
117148
tenantFieldName?: string
149+
tenantsCollectionSlug: string
118150
} & FilterOptionsProps
119-
const filterOptionsByTenant = ({ req, tenantFieldName = 'tenant' }: Args) => {
120-
const selectedTenant = getTenantFromCookie(req.headers, req.payload.db.defaultIDType)
151+
const filterOptionsByTenant = ({
152+
req,
153+
tenantFieldName = 'tenant',
154+
tenantsCollectionSlug,
155+
}: Args) => {
156+
const idType = getCollectionIDType({
157+
collectionSlug: tenantsCollectionSlug,
158+
payload: req.payload,
159+
})
160+
const selectedTenant = getTenantFromCookie(req.headers, idType)
121161
if (!selectedTenant) {
122162
return true
123163
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { CollectionSlug, Payload } from 'payload'
2+
3+
type Args = {
4+
collectionSlug: CollectionSlug
5+
payload: Payload
6+
}
7+
export const getCollectionIDType = ({ collectionSlug, payload }: Args): 'number' | 'text' => {
8+
return payload.collections[collectionSlug]?.customIDType ?? payload.db.defaultIDType
9+
}

packages/plugin-multi-tenant/src/utilities/getGlobalViewRedirect.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Payload, User, ViewTypes } from 'payload'
22

33
import { SELECT_ALL } from '../constants.js'
44
import { findTenantOptions } from '../queries/findTenantOptions.js'
5+
import { getCollectionIDType } from './getCollectionIDType.js'
56
import { getTenantFromCookie } from './getTenantFromCookie.js'
67

78
type Args = {
@@ -26,7 +27,11 @@ export async function getGlobalViewRedirect({
2627
user,
2728
view,
2829
}: Args): Promise<string | void> {
29-
let tenant = getTenantFromCookie(headers, payload.db.defaultIDType)
30+
const idType = getCollectionIDType({
31+
collectionSlug: tenantsCollectionSlug,
32+
payload,
33+
})
34+
let tenant = getTenantFromCookie(headers, idType)
3035
let redirectRoute
3136

3237
if (!tenant || tenant === SELECT_ALL) {

packages/plugin-multi-tenant/src/utilities/getTenantFromCookie.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ export function getTenantFromCookie(
1313
): null | number | string {
1414
const cookies = parseCookies(headers)
1515
const selectedTenant = cookies.get('payload-tenant') || null
16-
return selectedTenant ? (idType === 'number' ? parseInt(selectedTenant) : selectedTenant) : null
16+
return selectedTenant ? (idType === 'number' ? parseFloat(selectedTenant) : selectedTenant) : null
1717
}

packages/plugin-multi-tenant/src/utilities/getTenantListFilter.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
import type { PayloadRequest, Where } from 'payload'
22

33
import { SELECT_ALL } from '../constants.js'
4+
import { getCollectionIDType } from './getCollectionIDType.js'
45
import { getTenantFromCookie } from './getTenantFromCookie.js'
56

67
type Args = {
78
req: PayloadRequest
89
tenantFieldName: string
10+
tenantsCollectionSlug: string
911
}
10-
export const getTenantListFilter = ({ req, tenantFieldName }: Args): null | Where => {
11-
const selectedTenant = getTenantFromCookie(req.headers, req.payload.db.defaultIDType)
12+
export const getTenantListFilter = ({
13+
req,
14+
tenantFieldName,
15+
tenantsCollectionSlug,
16+
}: Args): null | Where => {
17+
const idType = getCollectionIDType({
18+
collectionSlug: tenantsCollectionSlug,
19+
payload: req.payload,
20+
})
21+
const selectedTenant = getTenantFromCookie(req.headers, idType)
1222

1323
if (selectedTenant === SELECT_ALL) {
1424
return {}

packages/plugin-multi-tenant/src/utilities/withTenantListFilter.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import { getTenantListFilter } from './getTenantListFilter.js'
55
type Args = {
66
baseListFilter?: BaseListFilter
77
tenantFieldName: string
8+
tenantsCollectionSlug: string
89
}
910
/**
1011
* Combines a base list filter with a tenant list filter
1112
*
1213
* Combines where constraints inside of an AND operator
1314
*/
1415
export const withTenantListFilter =
15-
({ baseListFilter, tenantFieldName }: Args): BaseListFilter =>
16+
({ baseListFilter, tenantFieldName, tenantsCollectionSlug }: Args): BaseListFilter =>
1617
async (args) => {
1718
const filterConstraints = []
1819

@@ -27,6 +28,7 @@ export const withTenantListFilter =
2728
const tenantListFilter = getTenantListFilter({
2829
req: args.req,
2930
tenantFieldName,
31+
tenantsCollectionSlug,
3032
})
3133

3234
if (tenantListFilter) {

0 commit comments

Comments
 (0)