fix(richtext-lexical, plugin-multi-tenant): text editor exposes documents from other tenants (#13229)
## What Before this PR, an internal link in the Lexical editor could reference a document from a different tenant than the active one. Reproduction: 1. `pnpm dev plugin-multi-tenant` 2. Log in with `dev@payloadcms.com` and password `test` 3. Go to `http://localhost:3000/admin/collections/food-items` and switch between the `Blue Dog` and `Steel Cat` tenants to see which food items each tenant has. 4. Go to http://localhost:3000/admin/collections/food-items/create, and in the new richtext field enter an internal link 5. In the relationship select menu, you will see the 6 food items at once (3 of each of those tenants). In the relationship select menu, you would previously see all 6 food items at once (3 from each of those tenants). Now, you'll only see the 3 from the active tenant. The new test verifies that this is fixed. ## How `baseListFilter` is used, but now it's called `baseFilter` for obvious reasons: it doesn't just filter the List View. Having two different properties where the same function was supposed to be placed wasn't feasible. `baseListFilter` is still supported for backwards compatibility. It's used as a fallback if `baseFilter` isn't defined, and it's documented as deprecated. `baseFilter` is injected into `filterOptions` of the internal link field in the Lexical Editor.
This commit is contained in:
@@ -142,7 +142,7 @@ The following options are available:
|
|||||||
| `components` | Swap in your own React components to be used within this Collection. [More details](#custom-components). |
|
| `components` | Swap in your own React components to be used within this Collection. [More details](#custom-components). |
|
||||||
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More details](#list-searchable-fields). |
|
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More details](#list-searchable-fields). |
|
||||||
| `pagination` | Set pagination-specific options for this Collection. [More details](#pagination). |
|
| `pagination` | Set pagination-specific options for this Collection. [More details](#pagination). |
|
||||||
| `baseListFilter` | You can define a default base filter for this collection's List view, which will be merged into any filters that the user performs. |
|
| `baseFilter` | Defines a default base filter which will be applied to the List View (along with any other filters applied by the user) and internal links in Lexical Editor, |
|
||||||
|
|
||||||
<Banner type="warning">
|
<Banner type="warning">
|
||||||
**Note:** If you set `useAsTitle` to a relationship or join field, it will use
|
**Note:** If you set `useAsTitle` to a relationship or join field, it will use
|
||||||
|
|||||||
@@ -76,7 +76,19 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
|||||||
isGlobal?: boolean
|
isGlobal?: boolean
|
||||||
/**
|
/**
|
||||||
* Set to `false` if you want to manually apply
|
* Set to `false` if you want to manually apply
|
||||||
* the baseListFilter
|
* the baseFilter
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
useBaseFilter?: boolean
|
||||||
|
/**
|
||||||
|
* @deprecated Use `useBaseFilter` instead. If both are defined,
|
||||||
|
* `useBaseFilter` will take precedence. This property remains only
|
||||||
|
* for backward compatibility and may be removed in a future version.
|
||||||
|
*
|
||||||
|
* Originally, `baseListFilter` was intended to filter only the List View
|
||||||
|
* in the admin panel. However, base filtering is often required in other areas
|
||||||
|
* such as internal link relationships in the Lexical editor.
|
||||||
*
|
*
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
@@ -203,12 +215,12 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
|||||||
*/
|
*/
|
||||||
useTenantsCollectionAccess?: boolean
|
useTenantsCollectionAccess?: boolean
|
||||||
/**
|
/**
|
||||||
* Opt out including the baseListFilter to filter
|
* Opt out including the baseFilter to filter
|
||||||
* tenants by selected tenant
|
* tenants by selected tenant
|
||||||
*/
|
*/
|
||||||
useTenantsListFilter?: boolean
|
useTenantsListFilter?: boolean
|
||||||
/**
|
/**
|
||||||
* Opt out including the baseListFilter to filter
|
* Opt out including the baseFilter to filter
|
||||||
* users by selected tenant
|
* users by selected tenant
|
||||||
*/
|
*/
|
||||||
useUsersTenantFilter?: boolean
|
useUsersTenantFilter?: boolean
|
||||||
|
|||||||
@@ -138,16 +138,14 @@ export const renderListView = async (
|
|||||||
throw new Error('not-found')
|
throw new Error('not-found')
|
||||||
}
|
}
|
||||||
|
|
||||||
let baseListFilter = undefined
|
const baseFilterConstraint = await (
|
||||||
|
collectionConfig.admin?.baseFilter ?? collectionConfig.admin?.baseListFilter
|
||||||
if (typeof collectionConfig.admin?.baseListFilter === 'function') {
|
)?.({
|
||||||
baseListFilter = await collectionConfig.admin.baseListFilter({
|
limit: query.limit,
|
||||||
limit: query.limit,
|
page: query.page,
|
||||||
page: query.page,
|
req,
|
||||||
req,
|
sort: query.sort,
|
||||||
sort: query.sort,
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let queryPreset: QueryPreset | undefined
|
let queryPreset: QueryPreset | undefined
|
||||||
let queryPresetPermissions: SanitizedCollectionPermission | undefined
|
let queryPresetPermissions: SanitizedCollectionPermission | undefined
|
||||||
@@ -155,7 +153,7 @@ export const renderListView = async (
|
|||||||
let whereWithMergedSearch = mergeListSearchAndWhere({
|
let whereWithMergedSearch = mergeListSearchAndWhere({
|
||||||
collectionConfig,
|
collectionConfig,
|
||||||
search: typeof query?.search === 'string' ? query.search : undefined,
|
search: typeof query?.search === 'string' ? query.search : undefined,
|
||||||
where: combineWhereConstraints([query?.where, baseListFilter]),
|
where: combineWhereConstraints([query?.where, baseFilterConstraint]),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (trash === true) {
|
if (trash === true) {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export type ServerOnlyCollectionProperties = keyof Pick<
|
|||||||
|
|
||||||
export type ServerOnlyCollectionAdminProperties = keyof Pick<
|
export type ServerOnlyCollectionAdminProperties = keyof Pick<
|
||||||
SanitizedCollectionConfig['admin'],
|
SanitizedCollectionConfig['admin'],
|
||||||
'baseListFilter' | 'components' | 'hidden'
|
'baseFilter' | 'baseListFilter' | 'components' | 'hidden'
|
||||||
>
|
>
|
||||||
|
|
||||||
export type ServerOnlyUploadProperties = keyof Pick<
|
export type ServerOnlyUploadProperties = keyof Pick<
|
||||||
@@ -94,6 +94,7 @@ const serverOnlyUploadProperties: Partial<ServerOnlyUploadProperties>[] = [
|
|||||||
|
|
||||||
const serverOnlyCollectionAdminProperties: Partial<ServerOnlyCollectionAdminProperties>[] = [
|
const serverOnlyCollectionAdminProperties: Partial<ServerOnlyCollectionAdminProperties>[] = [
|
||||||
'hidden',
|
'hidden',
|
||||||
|
'baseFilter',
|
||||||
'baseListFilter',
|
'baseListFilter',
|
||||||
'components',
|
'components',
|
||||||
// 'preview' is handled separately
|
// 'preview' is handled separately
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ export type EnableFoldersOptions = {
|
|||||||
debug?: boolean
|
debug?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BaseListFilter = (args: {
|
export type BaseFilter = (args: {
|
||||||
limit: number
|
limit: number
|
||||||
locale?: TypedLocale
|
locale?: TypedLocale
|
||||||
page: number
|
page: number
|
||||||
@@ -278,7 +278,31 @@ export type BaseListFilter = (args: {
|
|||||||
sort: string
|
sort: string
|
||||||
}) => null | Promise<null | Where> | Where
|
}) => null | Promise<null | Where> | Where
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use `BaseFilter` instead.
|
||||||
|
*/
|
||||||
|
export type BaseListFilter = BaseFilter
|
||||||
|
|
||||||
export type CollectionAdminOptions = {
|
export type CollectionAdminOptions = {
|
||||||
|
/**
|
||||||
|
* Defines a default base filter which will be applied in the following parts of the admin panel:
|
||||||
|
* - List View
|
||||||
|
* - Relationship fields for internal links within the Lexical editor
|
||||||
|
*
|
||||||
|
* This is especially useful for plugins like multi-tenant. For example,
|
||||||
|
* a user may have access to multiple tenants, but should only see content
|
||||||
|
* related to the currently active or selected tenant in those places.
|
||||||
|
*/
|
||||||
|
baseFilter?: BaseFilter
|
||||||
|
/**
|
||||||
|
* @deprecated Use `baseFilter` instead. If both are defined,
|
||||||
|
* `baseFilter` will take precedence. This property remains only
|
||||||
|
* for backward compatibility and may be removed in a future version.
|
||||||
|
*
|
||||||
|
* Originally, `baseListFilter` was intended to filter only the List View
|
||||||
|
* in the admin panel. However, base filtering is often required in other areas
|
||||||
|
* such as internal link relationships in the Lexical editor.
|
||||||
|
*/
|
||||||
baseListFilter?: BaseListFilter
|
baseListFilter?: BaseListFilter
|
||||||
/**
|
/**
|
||||||
* Custom admin components
|
* Custom admin components
|
||||||
|
|||||||
@@ -28,20 +28,20 @@ export async function buildFolderWhereConstraints({
|
|||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
|
|
||||||
if (typeof collectionConfig.admin?.baseListFilter === 'function') {
|
const baseFilterConstraint = await (
|
||||||
const baseListFilterConstraint = await collectionConfig.admin.baseListFilter({
|
collectionConfig.admin?.baseFilter ?? collectionConfig.admin?.baseListFilter
|
||||||
limit: 0,
|
)?.({
|
||||||
locale: localeCode,
|
limit: 0,
|
||||||
page: 1,
|
locale: localeCode,
|
||||||
req,
|
page: 1,
|
||||||
sort:
|
req,
|
||||||
sort ||
|
sort:
|
||||||
(typeof collectionConfig.defaultSort === 'string' ? collectionConfig.defaultSort : 'id'),
|
sort ||
|
||||||
})
|
(typeof collectionConfig.defaultSort === 'string' ? collectionConfig.defaultSort : 'id'),
|
||||||
|
})
|
||||||
|
|
||||||
if (baseListFilterConstraint) {
|
if (baseFilterConstraint) {
|
||||||
constraints.push(baseListFilterConstraint)
|
constraints.push(baseFilterConstraint)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (folderID) {
|
if (folderID) {
|
||||||
|
|||||||
@@ -1168,6 +1168,7 @@ export type {
|
|||||||
AfterRefreshHook as CollectionAfterRefreshHook,
|
AfterRefreshHook as CollectionAfterRefreshHook,
|
||||||
AuthCollection,
|
AuthCollection,
|
||||||
AuthOperationsFromCollectionSlug,
|
AuthOperationsFromCollectionSlug,
|
||||||
|
BaseFilter,
|
||||||
BaseListFilter,
|
BaseListFilter,
|
||||||
BeforeChangeHook as CollectionBeforeChangeHook,
|
BeforeChangeHook as CollectionBeforeChangeHook,
|
||||||
BeforeDeleteHook as CollectionBeforeDeleteHook,
|
BeforeDeleteHook as CollectionBeforeDeleteHook,
|
||||||
|
|||||||
@@ -36,11 +36,11 @@ type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
|||||||
*/
|
*/
|
||||||
isGlobal?: boolean
|
isGlobal?: boolean
|
||||||
/**
|
/**
|
||||||
* Set to `false` if you want to manually apply the baseListFilter
|
* Set to `false` if you want to manually apply the baseFilter
|
||||||
*
|
*
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
useBaseListFilter?: boolean
|
useBaseFilter?: boolean
|
||||||
/**
|
/**
|
||||||
* Set to `false` if you want to handle collection access manually without the multi-tenant constraints applied
|
* Set to `false` if you want to handle collection access manually without the multi-tenant constraints applied
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { addTenantCleanup } from './hooks/afterTenantDelete.js'
|
|||||||
import { translations } from './translations/index.js'
|
import { translations } from './translations/index.js'
|
||||||
import { addCollectionAccess } from './utilities/addCollectionAccess.js'
|
import { addCollectionAccess } from './utilities/addCollectionAccess.js'
|
||||||
import { addFilterOptionsToFields } from './utilities/addFilterOptionsToFields.js'
|
import { addFilterOptionsToFields } from './utilities/addFilterOptionsToFields.js'
|
||||||
import { combineListFilters } from './utilities/combineListFilters.js'
|
import { combineFilters } from './utilities/combineFilters.js'
|
||||||
|
|
||||||
export const multiTenantPlugin =
|
export const multiTenantPlugin =
|
||||||
<ConfigType>(pluginConfig: MultiTenantPluginConfig<ConfigType>) =>
|
<ConfigType>(pluginConfig: MultiTenantPluginConfig<ConfigType>) =>
|
||||||
@@ -143,8 +143,10 @@ export const multiTenantPlugin =
|
|||||||
adminUsersCollection.admin = {}
|
adminUsersCollection.admin = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
adminUsersCollection.admin.baseListFilter = combineListFilters({
|
const baseFilter =
|
||||||
baseListFilter: adminUsersCollection.admin?.baseListFilter,
|
adminUsersCollection.admin?.baseFilter ?? adminUsersCollection.admin?.baseListFilter
|
||||||
|
adminUsersCollection.admin.baseFilter = combineFilters({
|
||||||
|
baseFilter,
|
||||||
customFilter: (args) =>
|
customFilter: (args) =>
|
||||||
filterDocumentsByTenants({
|
filterDocumentsByTenants({
|
||||||
filterFieldName: `${tenantsArrayFieldName}.${tenantsArrayTenantFieldName}`,
|
filterFieldName: `${tenantsArrayFieldName}.${tenantsArrayTenantFieldName}`,
|
||||||
@@ -207,8 +209,9 @@ export const multiTenantPlugin =
|
|||||||
collection.admin = {}
|
collection.admin = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.admin.baseListFilter = combineListFilters({
|
const baseFilter = collection.admin?.baseFilter ?? collection.admin?.baseListFilter
|
||||||
baseListFilter: collection.admin?.baseListFilter,
|
collection.admin.baseFilter = combineFilters({
|
||||||
|
baseFilter,
|
||||||
customFilter: (args) =>
|
customFilter: (args) =>
|
||||||
filterDocumentsByTenants({
|
filterDocumentsByTenants({
|
||||||
filterFieldName: 'id',
|
filterFieldName: 'id',
|
||||||
@@ -296,7 +299,9 @@ export const multiTenantPlugin =
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (pluginConfig.collections[collection.slug]?.useBaseListFilter !== false) {
|
const { useBaseFilter, useBaseListFilter } = pluginConfig.collections[collection.slug] || {}
|
||||||
|
|
||||||
|
if (useBaseFilter ?? useBaseListFilter ?? true) {
|
||||||
/**
|
/**
|
||||||
* Add list filter to enabled collections
|
* Add list filter to enabled collections
|
||||||
* - filters results by selected tenant
|
* - filters results by selected tenant
|
||||||
@@ -305,8 +310,9 @@ export const multiTenantPlugin =
|
|||||||
collection.admin = {}
|
collection.admin = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.admin.baseListFilter = combineListFilters({
|
const baseFilter = collection.admin?.baseFilter ?? collection.admin?.baseListFilter
|
||||||
baseListFilter: collection.admin?.baseListFilter,
|
collection.admin.baseFilter = combineFilters({
|
||||||
|
baseFilter,
|
||||||
customFilter: (args) =>
|
customFilter: (args) =>
|
||||||
filterDocumentsByTenants({
|
filterDocumentsByTenants({
|
||||||
filterFieldName: tenantFieldName,
|
filterFieldName: tenantFieldName,
|
||||||
|
|||||||
@@ -30,7 +30,19 @@ export type MultiTenantPluginConfig<ConfigTypes = unknown> = {
|
|||||||
*/
|
*/
|
||||||
isGlobal?: boolean
|
isGlobal?: boolean
|
||||||
/**
|
/**
|
||||||
* Set to `false` if you want to manually apply the baseListFilter
|
* Set to `false` if you want to manually apply the baseFilter
|
||||||
|
*
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
useBaseFilter?: boolean
|
||||||
|
/**
|
||||||
|
* @deprecated Use `useBaseFilter` instead. If both are defined,
|
||||||
|
* `useBaseFilter` will take precedence. This property remains only
|
||||||
|
* for backward compatibility and may be removed in a future version.
|
||||||
|
*
|
||||||
|
* Originally, `baseListFilter` was intended to filter only the List View
|
||||||
|
* in the admin panel. However, base filtering is often required in other areas
|
||||||
|
* such as internal link relationships in the Lexical editor.
|
||||||
*
|
*
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
import type { BaseListFilter, Where } from 'payload'
|
import type { BaseFilter, Where } from 'payload'
|
||||||
|
|
||||||
type Args = {
|
type Args = {
|
||||||
baseListFilter?: BaseListFilter
|
baseFilter?: BaseFilter
|
||||||
customFilter: BaseListFilter
|
customFilter: BaseFilter
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Combines a base list filter with a tenant list filter
|
* Combines a base filter with a tenant list filter
|
||||||
*
|
*
|
||||||
* Combines where constraints inside of an AND operator
|
* Combines where constraints inside of an AND operator
|
||||||
*/
|
*/
|
||||||
export const combineListFilters =
|
export const combineFilters =
|
||||||
({ baseListFilter, customFilter }: Args): BaseListFilter =>
|
({ baseFilter, customFilter }: Args): BaseFilter =>
|
||||||
async (args) => {
|
async (args) => {
|
||||||
const filterConstraints = []
|
const filterConstraints = []
|
||||||
|
|
||||||
if (typeof baseListFilter === 'function') {
|
if (typeof baseFilter === 'function') {
|
||||||
const baseListFilterResult = await baseListFilter(args)
|
const baseFilterResult = await baseFilter(args)
|
||||||
|
|
||||||
if (baseListFilterResult) {
|
if (baseFilterResult) {
|
||||||
filterConstraints.push(baseListFilterResult)
|
filterConstraints.push(baseFilterResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,13 +117,23 @@ export const getBaseFields = (
|
|||||||
type: 'relationship',
|
type: 'relationship',
|
||||||
filterOptions:
|
filterOptions:
|
||||||
!enabledCollections && !disabledCollections
|
!enabledCollections && !disabledCollections
|
||||||
? ({ relationTo, user }) => {
|
? async ({ relationTo, req, user }) => {
|
||||||
const hidden = config.collections.find(({ slug }) => slug === relationTo)?.admin
|
const admin = config.collections.find(({ slug }) => slug === relationTo)?.admin
|
||||||
.hidden
|
|
||||||
|
const hidden = admin?.hidden
|
||||||
if (typeof hidden === 'function' && hidden({ user } as { user: TypedUser })) {
|
if (typeof hidden === 'function' && hidden({ user } as { user: TypedUser })) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
|
const baseFilter = admin?.baseFilter ?? admin?.baseListFilter
|
||||||
|
return (
|
||||||
|
(await baseFilter?.({
|
||||||
|
limit: 0,
|
||||||
|
page: 1,
|
||||||
|
req,
|
||||||
|
sort: 'id',
|
||||||
|
})) ?? true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
label: ({ t }) => t('fields:chooseDocumentToLink'),
|
label: ({ t }) => t('fields:chooseDocumentToLink'),
|
||||||
|
|||||||
@@ -1499,6 +1499,6 @@ export interface Auth {
|
|||||||
|
|
||||||
|
|
||||||
declare module 'payload' {
|
declare module 'payload' {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export interface GeneratedTypes extends Config {}
|
export interface GeneratedTypes extends Config {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,5 +90,9 @@ export const MenuItems: CollectionConfig = {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'content',
|
||||||
|
type: 'richText',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ export const credentials = {
|
|||||||
email: 'owner@anchorAndBlueDog.com',
|
email: 'owner@anchorAndBlueDog.com',
|
||||||
password: 'test',
|
password: 'test',
|
||||||
},
|
},
|
||||||
}
|
} as const
|
||||||
|
|||||||
@@ -311,6 +311,31 @@ test.describe('Multi Tenant', () => {
|
|||||||
confirmationModal.getByText('You are about to change ownership from Blue Dog to Steel Cat'),
|
confirmationModal.getByText('You are about to change ownership from Blue Dog to Steel Cat'),
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
|
test('should filter internal links in Lexical editor', async () => {
|
||||||
|
await loginClientSide({
|
||||||
|
page,
|
||||||
|
serverURL,
|
||||||
|
data: credentials.admin,
|
||||||
|
})
|
||||||
|
await selectTenant({
|
||||||
|
page,
|
||||||
|
tenant: 'Blue Dog',
|
||||||
|
})
|
||||||
|
await page.goto(menuItemsURL.create)
|
||||||
|
const editor = page.locator('[data-lexical-editor="true"]')
|
||||||
|
await editor.focus()
|
||||||
|
await page.keyboard.type('Hello World')
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
for (let i = 0; i < 'World'.length; i++) {
|
||||||
|
await page.keyboard.press('ArrowLeft')
|
||||||
|
}
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await page.locator('.toolbar-popup__button-link').click()
|
||||||
|
await page.locator('.radio-input__styled-radio').last().click()
|
||||||
|
await page.locator('.drawer__content').locator('.rs__input').click()
|
||||||
|
await expect(page.getByText('Chorizo Con Queso')).toBeVisible()
|
||||||
|
await expect(page.getByText('Pretzel Bites')).toBeHidden()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.describe('Globals', () => {
|
test.describe('Globals', () => {
|
||||||
|
|||||||
@@ -177,6 +177,21 @@ export interface FoodItem {
|
|||||||
id: string;
|
id: string;
|
||||||
tenant?: (string | null) | Tenant;
|
tenant?: (string | null) | Tenant;
|
||||||
name: string;
|
name: string;
|
||||||
|
content?: {
|
||||||
|
root: {
|
||||||
|
type: string;
|
||||||
|
children: {
|
||||||
|
type: string;
|
||||||
|
version: number;
|
||||||
|
[k: string]: unknown;
|
||||||
|
}[];
|
||||||
|
direction: ('ltr' | 'rtl') | null;
|
||||||
|
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||||
|
indent: number;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
[k: string]: unknown;
|
||||||
|
} | null;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
@@ -315,6 +330,7 @@ export interface UsersSelect<T extends boolean = true> {
|
|||||||
export interface FoodItemsSelect<T extends boolean = true> {
|
export interface FoodItemsSelect<T extends boolean = true> {
|
||||||
tenant?: T;
|
tenant?: T;
|
||||||
name?: T;
|
name?: T;
|
||||||
|
content?: T;
|
||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user