fix: user roles

This commit is contained in:
Benno Tielen 2025-03-12 15:08:13 +01:00
parent 9b674343f8
commit 02cd209493
15 changed files with 146 additions and 88 deletions

View file

@ -1,5 +1,5 @@
import { CollectionConfig } from 'payload' import { CollectionConfig } from 'payload'
import { isAdminOrEmployee } from '@/collections/access/admin' import { hide, isAdminOrEmployee } from '@/collections/access/admin'
import { nextSunday } from '@/utils/sunday' import { nextSunday } from '@/utils/sunday'
export const Announcements: CollectionConfig = { export const Announcements: CollectionConfig = {
@ -46,6 +46,9 @@ export const Announcements: CollectionConfig = {
required: true required: true
} }
], ],
admin: {
hidden: hide
},
access: { access: {
read: () => true, read: () => true,
create: isAdminOrEmployee(), create: isAdminOrEmployee(),

View file

@ -1,5 +1,5 @@
import { CollectionConfig } from 'payload' import { CollectionConfig } from 'payload'
import { isAdminOrEmployee } from '@/collections/access/admin' import { hide, isAdminOrEmployee } from '@/collections/access/admin'
import { ParagraphBlock } from '@/collections/blocks/Paragraph' import { ParagraphBlock } from '@/collections/blocks/Paragraph'
import { DocumentBlock } from '@/collections/blocks/Document' import { DocumentBlock } from '@/collections/blocks/Document'
import { ContactformBlock } from '@/collections/blocks/Contactform' import { ContactformBlock } from '@/collections/blocks/Contactform'
@ -62,7 +62,8 @@ export const Blog: CollectionConfig = {
}, },
], ],
admin: { admin: {
useAsTitle: 'title' useAsTitle: 'title',
hidden: hide
}, },
access: { access: {
read: () => true, read: () => true,

View file

@ -1,5 +1,5 @@
import { CollectionConfig } from 'payload' import { CollectionConfig } from 'payload'
import { isAdminOrEmployee } from '@/collections/access/admin' import { hide, isAdminOrEmployee } from '@/collections/access/admin'
export const Churches: CollectionConfig = { export const Churches: CollectionConfig = {
slug: 'church', slug: 'church',
@ -31,6 +31,7 @@ export const Churches: CollectionConfig = {
], ],
admin: { admin: {
useAsTitle: 'name', useAsTitle: 'name',
hidden: hide
}, },
access: { access: {
read: () => true, read: () => true,

View file

@ -1,5 +1,5 @@
import { CollectionConfig } from 'payload' import { CollectionConfig } from 'payload'
import { isAdminOrEmployee } from '@/collections/access/admin' import { hide, isAdminOrEmployee } from '@/collections/access/admin'
export const ContactPerson: CollectionConfig = { export const ContactPerson: CollectionConfig = {
slug: 'contactPerson', slug: 'contactPerson',
@ -38,7 +38,8 @@ export const ContactPerson: CollectionConfig = {
} }
], ],
admin: { admin: {
useAsTitle: "name" useAsTitle: "name",
hidden: hide
}, },
access: { access: {
read: () => true, read: () => true,

View file

@ -1,4 +1,5 @@
import type { CollectionConfig } from 'payload' import type { CollectionConfig } from 'payload'
import { hide, isAdminOrEmployee } from '@/collections/access/admin'
export const Documents: CollectionConfig = { export const Documents: CollectionConfig = {
slug: 'documents', slug: 'documents',
@ -12,8 +13,13 @@ export const Documents: CollectionConfig = {
}, },
access: { access: {
read: () => true, read: () => true,
update: isAdminOrEmployee(),
delete: isAdminOrEmployee()
}, },
fields: [], fields: [],
admin: {
hidden: hide
},
upload: { upload: {
mimeTypes: ['application/pdf'] mimeTypes: ['application/pdf']
}, },

View file

@ -1,5 +1,6 @@
import { CollectionConfig } from 'payload' import { CollectionConfig } from 'payload'
import { isAdminOrEmployee } from '@/collections/access/admin' import { stringify } from 'qs-esm'
import { Event, Group, User } from '@/payload-types'
export const Events: CollectionConfig = { export const Events: CollectionConfig = {
slug: 'event', slug: 'event',
@ -12,14 +13,6 @@ export const Events: CollectionConfig = {
}, },
}, },
fields: [ fields: [
{
name: 'photo',
label: {
de: 'Foto',
},
type: 'upload',
relationTo: 'media',
},
{ {
name: 'title', name: 'title',
type: 'text', type: 'text',
@ -67,7 +60,7 @@ export const Events: CollectionConfig = {
return 'You are not allowed to do this' return 'You are not allowed to do this'
} }
if (user.roles === 'user' && value) { if (user.roles === 'user' && value && value.length > 0) {
return 'Sie sind nur erlaubt Veranstaltungen für Gruppen zu erstellen.' return 'Sie sind nur erlaubt Veranstaltungen für Gruppen zu erstellen.'
} }
@ -82,7 +75,20 @@ export const Events: CollectionConfig = {
label: { label: {
de: 'Gruppe', de: 'Gruppe',
}, },
validate: (value: any, options: { req: { user: any } }) => { access: {
update: ({req: { user}, data}) => {
if(user && (user.roles == "admin" || user.roles =="employee")) {
return true
}
if(hasGroup(user, data)) {
return true
}
return false
}
},
validate: (value, options: { req: { user: any } }) => {
let user = options.req.user let user = options.req.user
if (!user) { if (!user) {
@ -90,8 +96,16 @@ export const Events: CollectionConfig = {
} }
if (user.roles === 'user') { if (user.roles === 'user') {
if (!user.groups || (user.groups && !user.groups.includes(value))) { if(!Array.isArray(value) || value.length === 0) {
return 'Sie können nur Veranstaltungen für Ihre eigene Gruppen erstellen!' return 'Sie müssen die Veranstaltung verknüpfen mit ihrer Gruppe.'
}
if(!Array.isArray(user.groups) || user.groups.length === 0) {
return "Sie sind kein Mitglied einer Gruppe, und können deswegen keine Veranstaltung erstellen."
}
if(!value.every(id => user.groups.includes(id))) {
return "Sie sind nur berechtigt Veranstaltungen für ihrer Gruppe zu erstellen"
} }
} }
@ -131,8 +145,19 @@ export const Events: CollectionConfig = {
de: "Anmeldelink" de: "Anmeldelink"
} }
}, },
{
name: 'photo',
label: {
de: 'Foto',
},
type: 'upload',
relationTo: 'media',
},
{ {
name: 'flyer', name: 'flyer',
label: {
de: "Flyer (PDF)"
},
type: 'upload', type: 'upload',
relationTo: 'documents' relationTo: 'documents'
}, },
@ -160,6 +185,70 @@ export const Events: CollectionConfig = {
}, },
access: { access: {
read: () => true, read: () => true,
delete: isAdminOrEmployee(), // admins and employees can delete, others only if they are member of the group
delete: async ({ req: { user }, id }) => {
if (!user) {
return false
}
if(user.roles === 'admin' || user.roles === 'employee')
return true
if(typeof id !== 'string') {
return false
}
const event = await fetchEvent(id)
if (hasGroup(user, event)) {
return true
}
return false
},
}, },
} }
/**
* Check if we have
* - a user
* - data with groups
* - the user is member of one of the groups
*
* @param user
* @param data
*/
const hasGroup = (user: null | User , data: Partial<any> | undefined) => {
return user
&& user.roles === 'user'
&& data
&& Array.isArray(data.group)
&& data.group.length > 0
&& data.group.some((group: string | Group) => {
if (!Array.isArray(user.groups)) {
return false;
}
if (typeof group === "string")
return user.groups.includes(group)
else
return user.groups.includes(group.id)
})
}
/**
* Fetch event
* @param id
*/
const fetchEvent = async (id: string): Promise<undefined|Event> => {
const stringifiedQuery = stringify(
{
select: {
group: true,
},
},
{ addQueryPrefix: true },
)
const res = await fetch(`http://localhost:3000/api/event/${id}${stringifiedQuery}`);
if (!res.ok) return undefined
return res.json()
}

View file

@ -1,5 +1,5 @@
import { CollectionConfig } from 'payload' import { CollectionConfig } from 'payload'
import { isAdminOrEmployee } from '@/collections/access/admin' import { hide, isAdminOrEmployee } from '@/collections/access/admin'
export const Highlight: CollectionConfig = { export const Highlight: CollectionConfig = {
slug: 'highlight', slug: 'highlight',
@ -62,7 +62,8 @@ export const Highlight: CollectionConfig = {
} }
], ],
admin: { admin: {
useAsTitle: 'text' useAsTitle: 'text',
hidden: hide
}, },
access: { access: {
read: () => true, read: () => true,

View file

@ -1,5 +1,5 @@
import { CollectionConfig } from 'payload' import { CollectionConfig } from 'payload'
import { isAdminOrEmployee } from '@/collections/access/admin' import { hide, isAdminOrEmployee } from '@/collections/access/admin'
import { nextSunday } from '@/utils/sunday' import { nextSunday } from '@/utils/sunday'
export const LiturgicalCalendar: CollectionConfig = { export const LiturgicalCalendar: CollectionConfig = {
@ -46,6 +46,9 @@ export const LiturgicalCalendar: CollectionConfig = {
required: true required: true
} }
], ],
admin: {
hidden: hide
},
access: { access: {
read: () => true, read: () => true,
create: isAdminOrEmployee(), create: isAdminOrEmployee(),

View file

@ -1,10 +1,12 @@
import type { CollectionConfig } from 'payload' import type { CollectionConfig } from 'payload'
import { Media as MediaType } from '../payload-types' import { isAdminOrEmployee } from '@/collections/access/admin'
export const Media: CollectionConfig = { export const Media: CollectionConfig = {
slug: 'media', slug: 'media',
access: { access: {
read: () => true, read: () => true,
update: isAdminOrEmployee(),
delete: isAdminOrEmployee()
}, },
admin: { admin: {
listSearchableFields: ['search'] listSearchableFields: ['search']

View file

@ -1,5 +1,5 @@
import { CollectionConfig } from 'payload' import { CollectionConfig } from 'payload'
import { isAdmin, isAdminOrEmployee } from '@/collections/access/admin' import { hide, isAdmin, isAdminOrEmployee } from '@/collections/access/admin'
export const Parish: CollectionConfig = { export const Parish: CollectionConfig = {
slug: 'parish', slug: 'parish',
@ -131,6 +131,7 @@ export const Parish: CollectionConfig = {
], ],
admin: { admin: {
useAsTitle: 'name', useAsTitle: 'name',
hidden: hide
}, },
access: { access: {
read: () => true, read: () => true,

View file

@ -1,5 +1,5 @@
import { CollectionConfig } from 'payload' import { CollectionConfig } from 'payload'
import { isAdminOrEmployee } from '@/collections/access/admin' import { hide, isAdminOrEmployee } from '@/collections/access/admin'
export const PopesPrayerIntentions: CollectionConfig = { export const PopesPrayerIntentions: CollectionConfig = {
slug: 'popePrayerIntentions', slug: 'popePrayerIntentions',
@ -100,6 +100,7 @@ export const PopesPrayerIntentions: CollectionConfig = {
], ],
admin: { admin: {
useAsTitle: 'title', useAsTitle: 'title',
hidden: hide
}, },
access: { access: {
read: () => true, read: () => true,

View file

@ -1,5 +1,5 @@
import type { CollectionConfig } from 'payload' import type { CollectionConfig } from 'payload'
import { isAdminOrEmployee } from '@/collections/access/admin' import { isAdmin, isAdminOrEmployee } from '@/collections/access/admin'
export const Users: CollectionConfig = { export const Users: CollectionConfig = {
slug: 'users', slug: 'users',
@ -69,8 +69,9 @@ export const Users: CollectionConfig = {
}, },
], ],
access: { access: {
read: isAdminOrEmployee(),
create: isAdminOrEmployee(), create: isAdminOrEmployee(),
update: isAdminOrEmployee(), update: isAdminOrEmployee(),
delete: isAdminOrEmployee(), delete: isAdmin(),
}, },
} }

View file

@ -1,5 +1,5 @@
import { CollectionConfig } from 'payload' import { CollectionConfig } from 'payload'
import { isAdminOrEmployee } from '@/collections/access/admin' import { hide, isAdminOrEmployee } from '@/collections/access/admin'
export const Worship: CollectionConfig = { export const Worship: CollectionConfig = {
slug: 'worship', slug: 'worship',
@ -101,7 +101,8 @@ export const Worship: CollectionConfig = {
], ],
admin: { admin: {
defaultColumns: ["date", 'location', 'type', 'celebrant'], defaultColumns: ["date", 'location', 'type', 'celebrant'],
listSearchableFields: ['date', 'location'] listSearchableFields: ['date', 'location'],
hidden: hide
}, },
access: { access: {
read: () => true, read: () => true,

View file

@ -1,4 +1,4 @@
import type { Access } from 'payload' import type { Access, ClientUser } from 'payload'
export const isAdmin = export const isAdmin =
(): Access => (): Access =>
@ -15,3 +15,5 @@ export const isAdminOrEmployee =
return user.roles === 'admin' || user.roles === 'employee' return user.roles === 'admin' || user.roles === 'employee'
} }
export const hide = (args: { user: ClientUser }) => args.user && args.user.roles === "user"

View file

@ -6,65 +6,10 @@
* and re-run `payload generate:types` to regenerate this file. * and re-run `payload generate:types` to regenerate this file.
*/ */
/**
* Supported timezones in IANA format.
*
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "supportedTimezones".
*/
export type SupportedTimezones =
| 'Pacific/Midway'
| 'Pacific/Niue'
| 'Pacific/Honolulu'
| 'Pacific/Rarotonga'
| 'America/Anchorage'
| 'Pacific/Gambier'
| 'America/Los_Angeles'
| 'America/Tijuana'
| 'America/Denver'
| 'America/Phoenix'
| 'America/Chicago'
| 'America/Guatemala'
| 'America/New_York'
| 'America/Bogota'
| 'America/Caracas'
| 'America/Santiago'
| 'America/Buenos_Aires'
| 'America/Sao_Paulo'
| 'Atlantic/South_Georgia'
| 'Atlantic/Azores'
| 'Atlantic/Cape_Verde'
| 'Europe/London'
| 'Europe/Berlin'
| 'Africa/Lagos'
| 'Europe/Athens'
| 'Africa/Cairo'
| 'Europe/Moscow'
| 'Asia/Riyadh'
| 'Asia/Dubai'
| 'Asia/Baku'
| 'Asia/Karachi'
| 'Asia/Tashkent'
| 'Asia/Calcutta'
| 'Asia/Dhaka'
| 'Asia/Almaty'
| 'Asia/Jakarta'
| 'Asia/Bangkok'
| 'Asia/Shanghai'
| 'Asia/Singapore'
| 'Asia/Tokyo'
| 'Asia/Seoul'
| 'Australia/Sydney'
| 'Pacific/Guam'
| 'Pacific/Noumea'
| 'Pacific/Auckland'
| 'Pacific/Fiji';
export interface Config { export interface Config {
auth: { auth: {
users: UserAuthOperations; users: UserAuthOperations;
}; };
blocks: {};
collections: { collections: {
parish: Parish; parish: Parish;
church: Church; church: Church;
@ -399,7 +344,6 @@ export interface Highlight {
*/ */
export interface Event { export interface Event {
id: string; id: string;
photo?: (string | null) | Media;
title: string; title: string;
date: string; date: string;
location: string | Location; location: string | Location;
@ -409,6 +353,7 @@ export interface Event {
shortDescription: string; shortDescription: string;
description: string; description: string;
rsvpLink?: string | null; rsvpLink?: string | null;
photo?: (string | null) | Media;
flyer?: (string | null) | Document; flyer?: (string | null) | Document;
cancelled: boolean; cancelled: boolean;
isRecurring: boolean; isRecurring: boolean;
@ -814,7 +759,6 @@ export interface HighlightSelect<T extends boolean = true> {
* via the `definition` "event_select". * via the `definition` "event_select".
*/ */
export interface EventSelect<T extends boolean = true> { export interface EventSelect<T extends boolean = true> {
photo?: T;
title?: T; title?: T;
date?: T; date?: T;
location?: T; location?: T;
@ -824,6 +768,7 @@ export interface EventSelect<T extends boolean = true> {
shortDescription?: T; shortDescription?: T;
description?: T; description?: T;
rsvpLink?: T; rsvpLink?: T;
photo?: T;
flyer?: T; flyer?: T;
cancelled?: T; cancelled?: T;
isRecurring?: T; isRecurring?: T;