chore(plugin-multi-tenant): test suite enhancements (#10732)
### What? Updates test suite multi-tenant config as a better example. ### Why? So it is easier to follow and write logical tests for in the future.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { Field, FilterOptionsProps, RelationshipField, Where } from 'payload'
|
||||
import type { Field, FilterOptionsProps, RelationshipField } from 'payload'
|
||||
|
||||
import { getTenantFromCookie } from './getTenantFromCookie.js'
|
||||
|
||||
@@ -7,6 +7,7 @@ type AddFilterOptionsToFieldsArgs = {
|
||||
tenantEnabledCollectionSlugs: string[]
|
||||
tenantEnabledGlobalSlugs: string[]
|
||||
}
|
||||
|
||||
export function addFilterOptionsToFields({
|
||||
fields,
|
||||
tenantEnabledCollectionSlugs,
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const LinksCollection: CollectionConfig = {
|
||||
slug: 'links',
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
}
|
||||
43
test/plugin-multi-tenant/collections/Menu.ts
Normal file
43
test/plugin-multi-tenant/collections/Menu.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { menuSlug } from '../shared.js'
|
||||
|
||||
export const Menu: CollectionConfig = {
|
||||
slug: menuSlug,
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
},
|
||||
{
|
||||
name: 'menuItems',
|
||||
label: 'Menu Items',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'menuItem',
|
||||
label: 'Menu Item',
|
||||
type: 'relationship',
|
||||
relationTo: 'menu-items',
|
||||
required: true,
|
||||
admin: {
|
||||
description: 'Automatically filtered by selected tenant',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'active',
|
||||
type: 'checkbox',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
17
test/plugin-multi-tenant/collections/MenuItems.ts
Normal file
17
test/plugin-multi-tenant/collections/MenuItems.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { menuItemsSlug } from '../shared.js'
|
||||
|
||||
export const MenuItems: CollectionConfig = {
|
||||
slug: menuItemsSlug,
|
||||
admin: {
|
||||
useAsTitle: 'name',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { postsSlug } from '../shared.js'
|
||||
import { userFilterOptions } from './Users/filterOptions.js'
|
||||
|
||||
export const Posts: CollectionConfig = {
|
||||
slug: postsSlug,
|
||||
labels: {
|
||||
singular: 'Post',
|
||||
plural: 'Posts',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'relatedLinks',
|
||||
relationTo: 'links',
|
||||
type: 'relationship',
|
||||
},
|
||||
{
|
||||
name: 'author',
|
||||
relationTo: 'users',
|
||||
type: 'relationship',
|
||||
filterOptions: userFilterOptions,
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -18,15 +18,16 @@ export const Tenants: CollectionConfig = {
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'domain',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'join',
|
||||
name: 'users',
|
||||
collection: 'users',
|
||||
on: 'tenants.tenant',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { usersSlug } from '../shared.js'
|
||||
|
||||
export const Users: CollectionConfig = {
|
||||
slug: usersSlug,
|
||||
auth: true,
|
||||
admin: {
|
||||
useAsTitle: 'email',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
fields: [
|
||||
// Email added by default
|
||||
// Add more fields as needed
|
||||
{
|
||||
type: 'select',
|
||||
name: 'roles',
|
||||
hasMany: true,
|
||||
options: [
|
||||
{
|
||||
label: 'Admin',
|
||||
value: 'admin',
|
||||
},
|
||||
{
|
||||
label: 'User',
|
||||
value: 'user',
|
||||
},
|
||||
],
|
||||
saveToJWT: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FilterOptions, Where } from 'payload'
|
||||
import type { FilterOptions } from 'payload'
|
||||
|
||||
import { getTenantFromCookie } from '@payloadcms/plugin-multi-tenant/utilities'
|
||||
|
||||
@@ -9,17 +9,8 @@ export const userFilterOptions: FilterOptions = ({ req }) => {
|
||||
}
|
||||
|
||||
return {
|
||||
or: [
|
||||
{
|
||||
'tenants.tenant': {
|
||||
equals: selectedTenant,
|
||||
},
|
||||
},
|
||||
{
|
||||
roles: {
|
||||
in: ['admin'],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as Where
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
border-radius: 100%;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
background-color: var(--theme-error-300);
|
||||
}
|
||||
|
||||
[data-selected-tenant-title="Blue Dog"] #tenant-icon {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
border-radius: 100%;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
background-color: var(--theme-error-300);
|
||||
}
|
||||
|
||||
[data-selected-tenant-title="Blue Dog"] #tenant-logo {
|
||||
|
||||
@@ -7,15 +7,14 @@ const dirname = path.dirname(filename)
|
||||
import type { Config as ConfigType } from './payload-types.js'
|
||||
|
||||
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
|
||||
import { LinksCollection } from './collections/Links.js'
|
||||
import { Posts } from './collections/Posts.js'
|
||||
import { Menu } from './collections/Menu.js'
|
||||
import { MenuItems } from './collections/MenuItems.js'
|
||||
import { Tenants } from './collections/Tenants.js'
|
||||
import { Users } from './collections/Users.js'
|
||||
import { NavigationGlobalCollection } from './globals/Navigation.js'
|
||||
import { Users } from './collections/Users/index.js'
|
||||
import { seed } from './seed/index.js'
|
||||
|
||||
export default buildConfigWithDefaults({
|
||||
collections: [Users, Tenants, Posts, LinksCollection, NavigationGlobalCollection],
|
||||
collections: [Tenants, Users, Menu, MenuItems],
|
||||
admin: {
|
||||
importMap: {
|
||||
baseDir: path.resolve(dirname),
|
||||
@@ -47,9 +46,8 @@ export default buildConfigWithDefaults({
|
||||
access: {},
|
||||
},
|
||||
collections: {
|
||||
posts: {},
|
||||
links: {},
|
||||
'navigation-global': {
|
||||
'menu-items': {},
|
||||
menu: {
|
||||
isGlobal: true,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const NavigationGlobalCollection: CollectionConfig = {
|
||||
slug: 'navigation-global',
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -11,22 +11,24 @@ export interface Config {
|
||||
users: UserAuthOperations;
|
||||
};
|
||||
collections: {
|
||||
users: User;
|
||||
tenants: Tenant;
|
||||
posts: Post;
|
||||
links: Link;
|
||||
'navigation-global': NavigationGlobal;
|
||||
users: User;
|
||||
menu: Menu;
|
||||
'menu-items': MenuItem;
|
||||
'payload-locked-documents': PayloadLockedDocument;
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
};
|
||||
collectionsJoins: {};
|
||||
collectionsJoins: {
|
||||
tenants: {
|
||||
users: 'users';
|
||||
};
|
||||
};
|
||||
collectionsSelect: {
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
tenants: TenantsSelect<false> | TenantsSelect<true>;
|
||||
posts: PostsSelect<false> | PostsSelect<true>;
|
||||
links: LinksSelect<false> | LinksSelect<true>;
|
||||
'navigation-global': NavigationGlobalSelect<false> | NavigationGlobalSelect<true>;
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
menu: MenuSelect<false> | MenuSelect<true>;
|
||||
'menu-items': MenuItemsSelect<false> | MenuItemsSelect<true>;
|
||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
|
||||
@@ -63,6 +65,21 @@ export interface UserAuthOperations {
|
||||
password: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "tenants".
|
||||
*/
|
||||
export interface Tenant {
|
||||
id: string;
|
||||
name: string;
|
||||
domain: string;
|
||||
users?: {
|
||||
docs?: (string | User)[] | null;
|
||||
hasNextPage?: boolean | null;
|
||||
} | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users".
|
||||
@@ -90,49 +107,34 @@ export interface User {
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "tenants".
|
||||
* via the `definition` "menu".
|
||||
*/
|
||||
export interface Tenant {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
domain: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts".
|
||||
*/
|
||||
export interface Post {
|
||||
export interface Menu {
|
||||
id: string;
|
||||
tenant?: (string | null) | Tenant;
|
||||
title: string;
|
||||
relatedLinks?: (string | null) | Link;
|
||||
author?: (string | null) | User;
|
||||
description?: string | null;
|
||||
menuItems?:
|
||||
| {
|
||||
/**
|
||||
* Automatically filtered by selected tenant
|
||||
*/
|
||||
menuItem: string | MenuItem;
|
||||
active?: boolean | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "links".
|
||||
* via the `definition` "menu-items".
|
||||
*/
|
||||
export interface Link {
|
||||
export interface MenuItem {
|
||||
id: string;
|
||||
tenant?: (string | null) | Tenant;
|
||||
title?: string | null;
|
||||
url?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "navigation-global".
|
||||
*/
|
||||
export interface NavigationGlobal {
|
||||
id: string;
|
||||
tenant?: (string | null) | Tenant;
|
||||
title?: string | null;
|
||||
name: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -143,25 +145,21 @@ export interface NavigationGlobal {
|
||||
export interface PayloadLockedDocument {
|
||||
id: string;
|
||||
document?:
|
||||
| ({
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'tenants';
|
||||
value: string | Tenant;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'posts';
|
||||
value: string | Post;
|
||||
relationTo: 'users';
|
||||
value: string | User;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'links';
|
||||
value: string | Link;
|
||||
relationTo: 'menu';
|
||||
value: string | Menu;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'navigation-global';
|
||||
value: string | NavigationGlobal;
|
||||
relationTo: 'menu-items';
|
||||
value: string | MenuItem;
|
||||
} | null);
|
||||
globalSlug?: string | null;
|
||||
user: {
|
||||
@@ -205,6 +203,17 @@ export interface PayloadMigration {
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "tenants_select".
|
||||
*/
|
||||
export interface TenantsSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
domain?: T;
|
||||
users?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "users_select".
|
||||
@@ -230,45 +239,29 @@ export interface UsersSelect<T extends boolean = true> {
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "tenants_select".
|
||||
* via the `definition` "menu_select".
|
||||
*/
|
||||
export interface TenantsSelect<T extends boolean = true> {
|
||||
export interface MenuSelect<T extends boolean = true> {
|
||||
tenant?: T;
|
||||
title?: T;
|
||||
description?: T;
|
||||
menuItems?:
|
||||
| T
|
||||
| {
|
||||
menuItem?: T;
|
||||
active?: T;
|
||||
id?: T;
|
||||
};
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "menu-items_select".
|
||||
*/
|
||||
export interface MenuItemsSelect<T extends boolean = true> {
|
||||
tenant?: T;
|
||||
name?: T;
|
||||
slug?: T;
|
||||
domain?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts_select".
|
||||
*/
|
||||
export interface PostsSelect<T extends boolean = true> {
|
||||
tenant?: T;
|
||||
title?: T;
|
||||
relatedLinks?: T;
|
||||
author?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "links_select".
|
||||
*/
|
||||
export interface LinksSelect<T extends boolean = true> {
|
||||
tenant?: T;
|
||||
title?: T;
|
||||
url?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "navigation-global_select".
|
||||
*/
|
||||
export interface NavigationGlobalSelect<T extends boolean = true> {
|
||||
tenant?: T;
|
||||
title?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
|
||||
@@ -1,39 +1,67 @@
|
||||
import type { Config } from 'payload'
|
||||
|
||||
import { devUser, regularUser } from '../../credentials.js'
|
||||
import { devUser } from '../../credentials.js'
|
||||
|
||||
export const seed: Config['onInit'] = async (payload) => {
|
||||
// create tenants
|
||||
const tenant1 = await payload.create({
|
||||
const blueDogTenant = await payload.create({
|
||||
collection: 'tenants',
|
||||
data: {
|
||||
name: 'Blue Dog',
|
||||
slug: 'blue-dog',
|
||||
domain: 'bluedog.com',
|
||||
},
|
||||
})
|
||||
const tenant2 = await payload.create({
|
||||
const steelCatTenant = await payload.create({
|
||||
collection: 'tenants',
|
||||
data: {
|
||||
name: 'Steel Cat',
|
||||
slug: 'steel-cat',
|
||||
domain: 'steelcat.com',
|
||||
},
|
||||
})
|
||||
|
||||
// create posts
|
||||
// Create blue dog menu items
|
||||
await payload.create({
|
||||
collection: 'posts',
|
||||
collection: 'menu-items',
|
||||
data: {
|
||||
title: 'Blue Dog Post',
|
||||
tenant: tenant1.id,
|
||||
name: 'Chorizo Con Queso and Chips',
|
||||
tenant: blueDogTenant.id,
|
||||
},
|
||||
})
|
||||
await payload.create({
|
||||
collection: 'posts',
|
||||
collection: 'menu-items',
|
||||
data: {
|
||||
title: 'Steel Cat Post',
|
||||
tenant: tenant2.id,
|
||||
name: 'Garlic Parmesan Tots',
|
||||
tenant: blueDogTenant.id,
|
||||
},
|
||||
})
|
||||
await payload.create({
|
||||
collection: 'menu-items',
|
||||
data: {
|
||||
name: 'Spicy Mac',
|
||||
tenant: blueDogTenant.id,
|
||||
},
|
||||
})
|
||||
|
||||
// Create steel cat menu items
|
||||
await payload.create({
|
||||
collection: 'menu-items',
|
||||
data: {
|
||||
name: 'Pretzel Bites',
|
||||
tenant: steelCatTenant.id,
|
||||
},
|
||||
})
|
||||
await payload.create({
|
||||
collection: 'menu-items',
|
||||
data: {
|
||||
name: 'Buffalo Chicken Dip',
|
||||
tenant: steelCatTenant.id,
|
||||
},
|
||||
})
|
||||
await payload.create({
|
||||
collection: 'menu-items',
|
||||
data: {
|
||||
name: 'Pulled Pork Nachos',
|
||||
tenant: steelCatTenant.id,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -50,12 +78,44 @@ export const seed: Config['onInit'] = async (payload) => {
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: regularUser.email,
|
||||
password: regularUser.password,
|
||||
email: 'jane@blue-dog.com',
|
||||
password: 'test',
|
||||
roles: ['user'],
|
||||
tenants: [
|
||||
{
|
||||
tenant: tenant1.id,
|
||||
tenant: blueDogTenant.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
// create menus
|
||||
await payload.create({
|
||||
collection: 'menu',
|
||||
data: {
|
||||
description: 'This collection behaves like globals, 1 document per tenant. No list view.',
|
||||
title: 'Blue Dog Menu',
|
||||
tenant: blueDogTenant.id,
|
||||
},
|
||||
})
|
||||
await payload.create({
|
||||
collection: 'menu',
|
||||
data: {
|
||||
description: 'This collection behaves like globals, 1 document per tenant. No list view.',
|
||||
title: 'Steel Cat Menu',
|
||||
tenant: steelCatTenant.id,
|
||||
},
|
||||
})
|
||||
|
||||
await payload.create({
|
||||
collection: 'users',
|
||||
data: {
|
||||
email: 'huel@steel-cat.com',
|
||||
password: 'test',
|
||||
roles: ['user'],
|
||||
tenants: [
|
||||
{
|
||||
tenant: steelCatTenant.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export const tenantsSlug = 'tenants'
|
||||
|
||||
export const postsSlug = 'posts'
|
||||
|
||||
export const usersSlug = 'users'
|
||||
|
||||
export const menuItemsSlug = 'menu-items'
|
||||
|
||||
export const menuSlug = 'menu'
|
||||
|
||||
Reference in New Issue
Block a user