examples: multi-tenant seed script, readme and other improvements (#10702)
This commit is contained in:
@@ -10,11 +10,17 @@ To spin up this example locally, follow these steps:
|
|||||||
|
|
||||||
- `npx create-payload-app --example multi-tenant`
|
- `npx create-payload-app --example multi-tenant`
|
||||||
|
|
||||||
2. `pnpm dev`, `yarn dev` or `npm run dev` to start the server
|
2. `cp .env.example .env` to copy the example environment variables
|
||||||
|
|
||||||
|
3. `pnpm dev`, `yarn dev` or `npm run dev` to start the server
|
||||||
- Press `y` when prompted to seed the database
|
- Press `y` when prompted to seed the database
|
||||||
3. `open http://localhost:3000` to access the home page
|
4. `open http://localhost:3000` to access the home page
|
||||||
4. `open http://localhost:3000/admin` to access the admin panel
|
5. `open http://localhost:3000/admin` to access the admin panel
|
||||||
- Login with email `demo@payloadcms.com` and password `demo`
|
|
||||||
|
### Default users
|
||||||
|
|
||||||
|
The seed script seeds 3 tenants.
|
||||||
|
Login with email `demo@payloadcms.com` and password `demo`
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
@@ -28,7 +34,7 @@ See the [Collections](https://payloadcms.com/docs/configuration/collections) doc
|
|||||||
|
|
||||||
- #### Users
|
- #### Users
|
||||||
|
|
||||||
The `users` collection is auth-enabled and encompass both app-wide and tenant-scoped users based on the value of their `roles` and `tenants` fields. Users with the role `super-admin` can manage your entire application, while users with the _tenant role_ of `admin` have limited access to the platform and can manage only the tenant(s) they are assigned to, see [Tenants](#tenants) for more details.
|
The `users` collection is auth-enabled and encompasses both app-wide and tenant-scoped users based on the value of their `roles` and `tenants` fields. Users with the role `super-admin` can manage your entire application, while users with the _tenant role_ of `admin` have limited access to the platform and can manage only the tenant(s) they are assigned to, see [Tenants](#tenants) for more details.
|
||||||
|
|
||||||
For additional help with authentication, see the official [Auth Example](https://github.com/payloadcms/payload/tree/main/examples/cms#readme) or the [Authentication](https://payloadcms.com/docs/authentication/overview#authentication-overview) docs.
|
For additional help with authentication, see the official [Auth Example](https://github.com/payloadcms/payload/tree/main/examples/cms#readme) or the [Authentication](https://payloadcms.com/docs/authentication/overview#authentication-overview) docs.
|
||||||
|
|
||||||
@@ -40,13 +46,13 @@ See the [Collections](https://payloadcms.com/docs/configuration/collections) doc
|
|||||||
|
|
||||||
**Domain-based Tenant Setting**:
|
**Domain-based Tenant Setting**:
|
||||||
|
|
||||||
This example also supports domain-based tenant selection, where tenants can be associated with a specific domain. If a tenant is associated with a domain (e.g., `gold.localhost.com:3000`), when a user logs in from that domain, they will be automatically scoped to the matching tenant. This is accomplished through an optional `afterLogin` hook that sets a `payload-tenant` cookie based on the domain.
|
This example also supports domain-based tenant selection, where tenants can be associated with a specific domain. If a tenant is associated with a domain (e.g., `gold.test:3000`), when a user logs in from that domain, they will be automatically scoped to the matching tenant. This is accomplished through an optional `afterLogin` hook that sets a `payload-tenant` cookie based on the domain.
|
||||||
|
|
||||||
The seed script seeds 3 tenants, for the domain portion of the example to function properly you will need to add the following entries to your systems `/etc/hosts` file:
|
For the domain portion of the example to function properly, you will need to add the following entries to your system's `/etc/hosts` file:
|
||||||
|
|
||||||
- gold.localhost.com:3000
|
```
|
||||||
- silver.localhost.com:3000
|
127.0.0.1 gold.test silver.test bronze.test
|
||||||
- bronze.localhost.com:3000
|
```
|
||||||
|
|
||||||
- #### Pages
|
- #### Pages
|
||||||
|
|
||||||
@@ -54,7 +60,7 @@ See the [Collections](https://payloadcms.com/docs/configuration/collections) doc
|
|||||||
|
|
||||||
## Access control
|
## Access control
|
||||||
|
|
||||||
Basic role-based access control is setup to determine what users can and cannot do based on their roles, which are:
|
Basic role-based access control is set up to determine what users can and cannot do based on their roles, which are:
|
||||||
|
|
||||||
- `super-admin`: They can access the Payload admin panel to manage your multi-tenant application. They can see all tenants and make all operations.
|
- `super-admin`: They can access the Payload admin panel to manage your multi-tenant application. They can see all tenants and make all operations.
|
||||||
- `user`: They can only access the Payload admin panel if they are a tenant-admin, in which case they have a limited access to operations based on their tenant (see below).
|
- `user`: They can only access the Payload admin panel if they are a tenant-admin, in which case they have a limited access to operations based on their tenant (see below).
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ export default async ({ params: paramsPromise }: { params: Promise<{ slug: strin
|
|||||||
<p>When you visit a tenant by domain, the domain is used to determine the tenant.</p>
|
<p>When you visit a tenant by domain, the domain is used to determine the tenant.</p>
|
||||||
<p>
|
<p>
|
||||||
For example, visiting{' '}
|
For example, visiting{' '}
|
||||||
<a href="http://gold.localhost.com:3000/tenant-domains/login">
|
<a href="http://gold.test:3000/tenant-domains/login">
|
||||||
http://gold.localhost.com:3000/tenant-domains/login
|
http://gold.test:3000/tenant-domains/login
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
will show the tenant with the domain "gold.localhost.com".
|
will show the tenant with the domain "gold.test".
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Slugs</h2>
|
<h2>Slugs</h2>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Access } from 'payload'
|
|||||||
/**
|
/**
|
||||||
* Tenant admins and super admins can will be allowed access
|
* Tenant admins and super admins can will be allowed access
|
||||||
*/
|
*/
|
||||||
export const superAdminOrTeanantAdminAccess: Access = ({ req }) => {
|
export const superAdminOrTenantAdminAccess: Access = ({ req }) => {
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import type { CollectionConfig } from 'payload'
|
import type { CollectionConfig } from 'payload'
|
||||||
|
|
||||||
import { ensureUniqueSlug } from './hooks/ensureUniqueSlug'
|
import { ensureUniqueSlug } from './hooks/ensureUniqueSlug'
|
||||||
import { superAdminOrTeanantAdminAccess } from '@/collections/Pages/access/superAdminOrTenantAdmin'
|
import { superAdminOrTenantAdminAccess } from '@/collections/Pages/access/superAdminOrTenantAdmin'
|
||||||
|
|
||||||
export const Pages: CollectionConfig = {
|
export const Pages: CollectionConfig = {
|
||||||
slug: 'pages',
|
slug: 'pages',
|
||||||
access: {
|
access: {
|
||||||
create: superAdminOrTeanantAdminAccess,
|
create: superAdminOrTenantAdminAccess,
|
||||||
delete: superAdminOrTeanantAdminAccess,
|
delete: superAdminOrTenantAdminAccess,
|
||||||
read: () => true,
|
read: () => true,
|
||||||
update: superAdminOrTeanantAdminAccess,
|
update: superAdminOrTenantAdminAccess,
|
||||||
},
|
},
|
||||||
admin: {
|
admin: {
|
||||||
useAsTitle: 'title',
|
useAsTitle: 'title',
|
||||||
|
|||||||
@@ -1,6 +1,33 @@
|
|||||||
import type { MigrateUpArgs } from '@payloadcms/db-mongodb'
|
import type { MigrateUpArgs } from '@payloadcms/db-mongodb'
|
||||||
|
|
||||||
export async function up({ payload }: MigrateUpArgs): Promise<void> {
|
export async function up({ payload }: MigrateUpArgs): Promise<void> {
|
||||||
|
const tenant1 = await payload.create({
|
||||||
|
collection: 'tenants',
|
||||||
|
data: {
|
||||||
|
name: 'Tenant 1',
|
||||||
|
slug: 'gold',
|
||||||
|
domain: 'gold.test',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const tenant2 = await payload.create({
|
||||||
|
collection: 'tenants',
|
||||||
|
data: {
|
||||||
|
name: 'Tenant 2',
|
||||||
|
slug: 'silver',
|
||||||
|
domain: 'silver.test',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const tenant3 = await payload.create({
|
||||||
|
collection: 'tenants',
|
||||||
|
data: {
|
||||||
|
name: 'Tenant 3',
|
||||||
|
slug: 'bronze',
|
||||||
|
domain: 'bronze.test',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
await payload.create({
|
await payload.create({
|
||||||
collection: 'users',
|
collection: 'users',
|
||||||
data: {
|
data: {
|
||||||
@@ -10,47 +37,16 @@ export async function up({ payload }: MigrateUpArgs): Promise<void> {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const tenant1 = await payload.create({
|
|
||||||
collection: 'tenants',
|
|
||||||
data: {
|
|
||||||
name: 'Tenant 1',
|
|
||||||
slug: 'gold',
|
|
||||||
domain: 'gold.localhost.com',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const tenant2 = await payload.create({
|
|
||||||
collection: 'tenants',
|
|
||||||
data: {
|
|
||||||
name: 'Tenant 2',
|
|
||||||
slug: 'silver',
|
|
||||||
domain: 'silver.localhost.com',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const tenant3 = await payload.create({
|
|
||||||
collection: 'tenants',
|
|
||||||
data: {
|
|
||||||
name: 'Tenant 3',
|
|
||||||
slug: 'bronze',
|
|
||||||
domain: 'bronze.localhost.com',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await payload.create({
|
await payload.create({
|
||||||
collection: 'users',
|
collection: 'users',
|
||||||
data: {
|
data: {
|
||||||
email: 'tenant1@payloadcms.com',
|
email: 'tenant1@payloadcms.com',
|
||||||
password: 'test',
|
password: 'demo',
|
||||||
tenants: [
|
tenants: [
|
||||||
{
|
{
|
||||||
roles: ['tenant-admin'],
|
roles: ['tenant-admin'],
|
||||||
tenant: tenant1.id,
|
tenant: tenant1.id,
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// roles: ['tenant-admin'],
|
|
||||||
// tenant: tenant2.id,
|
|
||||||
// },
|
|
||||||
],
|
],
|
||||||
username: 'tenant1',
|
username: 'tenant1',
|
||||||
},
|
},
|
||||||
@@ -60,7 +56,7 @@ export async function up({ payload }: MigrateUpArgs): Promise<void> {
|
|||||||
collection: 'users',
|
collection: 'users',
|
||||||
data: {
|
data: {
|
||||||
email: 'tenant2@payloadcms.com',
|
email: 'tenant2@payloadcms.com',
|
||||||
password: 'test',
|
password: 'demo',
|
||||||
tenants: [
|
tenants: [
|
||||||
{
|
{
|
||||||
roles: ['tenant-admin'],
|
roles: ['tenant-admin'],
|
||||||
@@ -75,7 +71,7 @@ export async function up({ payload }: MigrateUpArgs): Promise<void> {
|
|||||||
collection: 'users',
|
collection: 'users',
|
||||||
data: {
|
data: {
|
||||||
email: 'tenant3@payloadcms.com',
|
email: 'tenant3@payloadcms.com',
|
||||||
password: 'test',
|
password: 'demo',
|
||||||
tenants: [
|
tenants: [
|
||||||
{
|
{
|
||||||
roles: ['tenant-admin'],
|
roles: ['tenant-admin'],
|
||||||
@@ -90,7 +86,7 @@ export async function up({ payload }: MigrateUpArgs): Promise<void> {
|
|||||||
collection: 'users',
|
collection: 'users',
|
||||||
data: {
|
data: {
|
||||||
email: 'multi-admin@payloadcms.com',
|
email: 'multi-admin@payloadcms.com',
|
||||||
password: 'test',
|
password: 'demo',
|
||||||
tenants: [
|
tenants: [
|
||||||
{
|
{
|
||||||
roles: ['tenant-admin'],
|
roles: ['tenant-admin'],
|
||||||
@@ -105,7 +101,7 @@ export async function up({ payload }: MigrateUpArgs): Promise<void> {
|
|||||||
tenant: tenant3.id,
|
tenant: tenant3.id,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
username: 'tenant3',
|
username: 'multi-admin',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user