feat: client, instructor, tenant join relationship

This commit is contained in:
Yehoshua Sandler 2025-05-19 15:34:55 -05:00
parent 5d03b2c414
commit 553ca30d12
6 changed files with 91 additions and 18 deletions

View File

@ -0,0 +1,31 @@
import { CollectionConfig } from "payload";
export const InstructorClientTenantRelations: CollectionConfig = {
slug: 'instructorClientTenantRelations',
fields: [
{
name: 'displayName',
type: 'text',
},
{
name: 'instructor',
type: 'relationship',
relationTo: 'users',
hasMany: false,
},
{
name: 'client',
type: 'relationship',
relationTo: 'users',
hasMany: false,
},
{
name: 'tenant',
type: 'relationship',
relationTo: 'tenants',
hasMany: false,
}
]
}
export default InstructorClientTenantRelations

View File

@ -17,6 +17,8 @@ export const readAccess: Access<User> = ({ req, id }) => {
} }
const superAdmin = isSuperAdmin(req.user) const superAdmin = isSuperAdmin(req.user)
if (superAdmin) return true;
const selectedTenant = getTenantFromCookie( const selectedTenant = getTenantFromCookie(
req.headers, req.headers,
getCollectionIDType({ payload: req.payload, collectionSlug: 'tenants' }), getCollectionIDType({ payload: req.payload, collectionSlug: 'tenants' }),
@ -35,10 +37,6 @@ export const readAccess: Access<User> = ({ req, id }) => {
} }
} }
if (superAdmin) {
return true
}
return { return {
or: [ or: [
{ {

View File

@ -28,7 +28,7 @@ const defaultTenantArrayField = tenantsArrayField({
{ {
name: 'roles', name: 'roles',
type: 'select', type: 'select',
defaultValue: ['tenant-viewer'], defaultValue: ['tenant-client'],
hasMany: true, hasMany: true,
options: ['tenant-admin', 'tenant-client', 'tenant-instructor'], options: ['tenant-admin', 'tenant-client', 'tenant-instructor'],
required: true, required: true,
@ -76,12 +76,6 @@ export const Users: CollectionConfig = {
}, },
index: true, index: true,
}, },
{
name: 'accessLevel',
type: 'number',
max: UserAccessLevel.FULL_ACCESS,
defaultValue: UserAccessLevel.GUEST,
},
{ {
...defaultTenantArrayField, ...defaultTenantArrayField,
admin: { admin: {
@ -89,6 +83,18 @@ export const Users: CollectionConfig = {
position: 'sidebar', position: 'sidebar',
}, },
}, },
{
name: 'clients',
type: 'join',
collection: 'instructorClientTenantRelations',
on: 'instructor',
admin: {
condition: (data) => {
const tenantRoles = data.tenants?.flatMap((t: any) => t.roles)
return tenantRoles.includes('tenant-instructor')
}
},
}
], ],
timestamps: true, timestamps: true,
hooks: { hooks: {

View File

@ -86,6 +86,7 @@ export interface Config {
habbits: Habbit; habbits: Habbit;
habbitEntries: HabbitEntry; habbitEntries: HabbitEntry;
schedules: Schedule; schedules: Schedule;
instructorClientTenantRelations: InstructorClientTenantRelation;
redirects: Redirect; redirects: Redirect;
forms: Form; forms: Form;
'form-submissions': FormSubmission; 'form-submissions': FormSubmission;
@ -95,7 +96,11 @@ export interface Config {
'payload-preferences': PayloadPreference; 'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration; 'payload-migrations': PayloadMigration;
}; };
collectionsJoins: {}; collectionsJoins: {
users: {
clients: 'instructorClientTenantRelations';
};
};
collectionsSelect: { collectionsSelect: {
pages: PagesSelect<false> | PagesSelect<true>; pages: PagesSelect<false> | PagesSelect<true>;
posts: PostsSelect<false> | PostsSelect<true>; posts: PostsSelect<false> | PostsSelect<true>;
@ -116,6 +121,7 @@ export interface Config {
habbits: HabbitsSelect<false> | HabbitsSelect<true>; habbits: HabbitsSelect<false> | HabbitsSelect<true>;
habbitEntries: HabbitEntriesSelect<false> | HabbitEntriesSelect<true>; habbitEntries: HabbitEntriesSelect<false> | HabbitEntriesSelect<true>;
schedules: SchedulesSelect<false> | SchedulesSelect<true>; schedules: SchedulesSelect<false> | SchedulesSelect<true>;
instructorClientTenantRelations: InstructorClientTenantRelationsSelect<false> | InstructorClientTenantRelationsSelect<true>;
redirects: RedirectsSelect<false> | RedirectsSelect<true>; redirects: RedirectsSelect<false> | RedirectsSelect<true>;
forms: FormsSelect<false> | FormsSelect<true>; forms: FormsSelect<false> | FormsSelect<true>;
'form-submissions': FormSubmissionsSelect<false> | FormSubmissionsSelect<true>; 'form-submissions': FormSubmissionsSelect<false> | FormSubmissionsSelect<true>;
@ -426,7 +432,6 @@ export interface User {
id: number; id: number;
roles?: ('full-access' | 'super-admin' | 'user' | 'guest')[] | null; roles?: ('full-access' | 'super-admin' | 'user' | 'guest')[] | null;
username?: string | null; username?: string | null;
accessLevel?: number | null;
tenants?: tenants?:
| { | {
tenant: number | Tenant; tenant: number | Tenant;
@ -434,6 +439,11 @@ export interface User {
id?: string | null; id?: string | null;
}[] }[]
| null; | null;
clients?: {
docs?: (number | InstructorClientTenantRelation)[];
hasNextPage?: boolean;
totalDocs?: number;
};
updatedAt: string; updatedAt: string;
createdAt: string; createdAt: string;
email: string; email: string;
@ -445,6 +455,19 @@ export interface User {
lockUntil?: string | null; lockUntil?: string | null;
password?: string | null; password?: string | null;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "instructorClientTenantRelations".
*/
export interface InstructorClientTenantRelation {
id: number;
displayName?: string | null;
instructor?: (number | null) | User;
client?: (number | null) | User;
tenant?: (number | null) | Tenant;
updatedAt: string;
createdAt: string;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "CallToActionBlock". * via the `definition` "CallToActionBlock".
@ -1434,6 +1457,10 @@ export interface PayloadLockedDocument {
relationTo: 'schedules'; relationTo: 'schedules';
value: number | Schedule; value: number | Schedule;
} | null) } | null)
| ({
relationTo: 'instructorClientTenantRelations';
value: number | InstructorClientTenantRelation;
} | null)
| ({ | ({
relationTo: 'redirects'; relationTo: 'redirects';
value: number | Redirect; value: number | Redirect;
@ -1783,7 +1810,6 @@ export interface CategoriesSelect<T extends boolean = true> {
export interface UsersSelect<T extends boolean = true> { export interface UsersSelect<T extends boolean = true> {
roles?: T; roles?: T;
username?: T; username?: T;
accessLevel?: T;
tenants?: tenants?:
| T | T
| { | {
@ -1791,6 +1817,7 @@ export interface UsersSelect<T extends boolean = true> {
roles?: T; roles?: T;
id?: T; id?: T;
}; };
clients?: T;
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
email?: T; email?: T;
@ -2030,6 +2057,18 @@ export interface SchedulesSelect<T extends boolean = true> {
updatedAt?: T; updatedAt?: T;
createdAt?: T; createdAt?: T;
} }
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "instructorClientTenantRelations_select".
*/
export interface InstructorClientTenantRelationsSelect<T extends boolean = true> {
displayName?: T;
instructor?: T;
client?: T;
tenant?: T;
updatedAt?: T;
createdAt?: T;
}
/** /**
* This interface was referenced by `Config`'s JSON-Schema * This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "redirects_select". * via the `definition` "redirects_select".

View File

@ -24,6 +24,7 @@ import { HabbitCategories } from './collections/Habbits/HabbitCategories'
import { Habbits } from './collections/Habbits/Habbits' import { Habbits } from './collections/Habbits/Habbits'
import { HabbitEntries } from './collections/Habbits/HabbitEntry' import { HabbitEntries } from './collections/Habbits/HabbitEntry'
import { Schedules } from './collections/Schedules' import { Schedules } from './collections/Schedules'
import InstructorClientTenantRelations from './collections/InstructorClientTenantRelations'
const filename = fileURLToPath(import.meta.url) const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename) const dirname = path.dirname(filename)
@ -72,7 +73,7 @@ export default buildConfig({
connectionString: process.env.DATABASE_URI || '', connectionString: process.env.DATABASE_URI || '',
}, },
}), }),
collections: [Pages, Posts, Media, Categories, Users, Tenants, Exercises, ExerciseTypes, MuscleGroups, Equipments, Workouts, WorkoutTypes, Ingredients, MealItems, Meals, HabbitCategories, Habbits, HabbitEntries, Schedules], collections: [Pages, Posts, Media, Categories, Users, Tenants, Exercises, ExerciseTypes, MuscleGroups, Equipments, Workouts, WorkoutTypes, Ingredients, MealItems, Meals, HabbitCategories, Habbits, HabbitEntries, Schedules, InstructorClientTenantRelations],
cors: [getServerSideURL()].filter(Boolean), cors: [getServerSideURL()].filter(Boolean),
globals: [Header, Footer], globals: [Header, Footer],
plugins: [ plugins: [

View File

@ -14,7 +14,6 @@ import { beforeSyncWithSearch } from '@/search/beforeSync'
import { Config, Page, Post, User } from '@/payload-types' import { Config, Page, Post, User } from '@/payload-types'
import { getServerSideURL } from '@/utilities/getURL' import { getServerSideURL } from '@/utilities/getURL'
import { UserAccessLevel } from '@/collections/Users'
import { isSuperAdmin } from '@/access/isSuperAdmin' import { isSuperAdmin } from '@/access/isSuperAdmin'
import { getUserTenantIDs } from '@/utilities/getUserTenantIds' import { getUserTenantIDs } from '@/utilities/getUserTenantIds'
@ -117,7 +116,7 @@ export const plugins: Plugin[] = [
tenantsArrayField: { tenantsArrayField: {
includeDefaultField: false, includeDefaultField: false,
}, },
userHasAccessToAllTenants: (user: User) => (user.accessLevel || 0) >= UserAccessLevel.SUPER_ADMIN, userHasAccessToAllTenants: (user: User) => isSuperAdmin(user),
tenantField: { tenantField: {
access: { access: {
read: () => true, read: () => true,
@ -127,7 +126,6 @@ export const plugins: Plugin[] = [
} }
return getUserTenantIDs(req.user).length > 0 return getUserTenantIDs(req.user).length > 0
}, },
create: () => true,
}, },
}, },
}), }),