From f645c01ac0a8242eff1a80199f6ebe83f54bf571 Mon Sep 17 00:00:00 2001 From: Benno Tielen Date: Fri, 23 Aug 2024 16:20:50 +0200 Subject: [PATCH] feature: new collections --- src/collections/Churches.ts | 4 + src/collections/Employees.ts | 62 +++++ src/collections/Events.ts | 122 ++++++++++ src/collections/Groups.ts | 66 ++++++ src/collections/Parish.ts | 91 +++++++ src/collections/Testimony.ts | 4 + src/collections/Users.ts | 59 ++++- src/collections/Worship.ts | 4 + src/collections/access/admin.ts | 17 ++ src/components/HomeBanner/HomeBanner.tsx | 2 +- src/payload-types.ts | 287 +++++++++++++++-------- src/payload.config.ts | 16 +- 12 files changed, 626 insertions(+), 108 deletions(-) create mode 100644 src/collections/Employees.ts create mode 100644 src/collections/Events.ts create mode 100644 src/collections/Groups.ts create mode 100644 src/collections/Parish.ts create mode 100644 src/collections/access/admin.ts diff --git a/src/collections/Churches.ts b/src/collections/Churches.ts index 40d05ac..80d0bfb 100644 --- a/src/collections/Churches.ts +++ b/src/collections/Churches.ts @@ -1,4 +1,5 @@ import { CollectionConfig } from 'payload' +import { isAdminOrEmployee } from '@/collections/access/admin' export const Churches: CollectionConfig = { slug: 'church', @@ -33,5 +34,8 @@ export const Churches: CollectionConfig = { }, access: { read: () => true, + create: isAdminOrEmployee(), + update: isAdminOrEmployee(), + delete: isAdminOrEmployee(), }, } diff --git a/src/collections/Employees.ts b/src/collections/Employees.ts new file mode 100644 index 0000000..198c843 --- /dev/null +++ b/src/collections/Employees.ts @@ -0,0 +1,62 @@ +import { CollectionConfig } from 'payload' +import { isAdminOrEmployee } from '@/collections/access/admin' + +export const Employees: CollectionConfig = { + slug: 'employees', + labels: { + singular: { + de: 'Mitarbeiter', + }, + plural: 'Mitarbeiter', + }, + fields: [ + { + name: 'photo', + label: { + de: 'Foto', + }, + type: 'upload', + relationTo: 'media', + }, + { + name: 'name', + type: 'text', + label: { + de: 'Name', + }, + required: true, + }, + { + name: 'occupation', + type: 'text', + label: { + de: 'Tätigkeit', + }, + required: true, + }, + { + name: 'email', + type: 'email', + label: { + de: 'Email', + }, + }, + { + name: 'telephone', + type: 'text', + label: { + de: 'Telefon', + }, + required: false, + }, + ], + admin: { + useAsTitle: 'name', + }, + access: { + read: () => true, + create: isAdminOrEmployee(), + update: isAdminOrEmployee(), + delete: isAdminOrEmployee(), + }, +} diff --git a/src/collections/Events.ts b/src/collections/Events.ts new file mode 100644 index 0000000..1d23c63 --- /dev/null +++ b/src/collections/Events.ts @@ -0,0 +1,122 @@ +import { CollectionConfig } from 'payload' +import { isAdminOrEmployee } from '@/collections/access/admin' + +export const Events: CollectionConfig = { + slug: 'event', + labels: { + singular: { + de: 'Veranstaltung', + }, + plural: { + de: 'Veranstaltungen', + }, + }, + fields: [ + { + name: 'photo', + label: { + de: 'Foto', + }, + type: 'upload', + relationTo: 'media', + }, + { + name: 'title', + type: 'text', + required: true, + label: { + de: 'Titel', + }, + }, + { + name: 'datum', + type: 'date', + required: true, + label: { + de: 'Datum', + }, + admin: { + date: { + pickerAppearance: 'dayAndTime', + }, + }, + }, + { + name: 'parish', + type: 'relationship', + relationTo: 'parish', + hasMany: true, + label: { + de: 'Gemeinde', + }, + validate: (value, options) => { + let user = options.req.user + + if (!user) { + return 'You are not allowed to do this' + } + + if (user.roles === 'user' && value) { + return 'Sie sind nur erlaubt Veranstaltungen für Gruppen zu erstellen.' + } + + return true + }, + }, + { + name: 'group', + type: 'relationship', + relationTo: 'group', + label: { + de: 'Gruppe', + }, + validate: (value, options) => { + let user = options.req.user + + if (!user) { + return 'You are not allowed to do this' + } + + if (user.roles === 'user') { + if (!user.groups || (user.groups && !user.groups.includes(value))) { + return 'Sie können nur Veranstaltungen für Ihre eigene Gruppen erstellen!' + } + } + + return true + }, + }, + { + name: 'shortDescription', + type: 'textarea', + required: true, + label: { + de: 'Kurzumschreibung', + }, + }, + { + name: 'description', + type: 'richText', + required: true, + label: { + de: 'Umschreibung', + }, + }, + { + name: 'cancelled', + type: 'checkbox', + required: true, + label: { + de: 'Abgesagt', + }, + defaultValue: false, + }, + ], + admin: { + useAsTitle: 'title', + }, + access: { + read: () => true, + delete: isAdminOrEmployee(), + }, +} diff --git a/src/collections/Groups.ts b/src/collections/Groups.ts new file mode 100644 index 0000000..96f7d2d --- /dev/null +++ b/src/collections/Groups.ts @@ -0,0 +1,66 @@ +import { CollectionConfig } from 'payload' +import { isAdminOrEmployee } from '@/collections/access/admin' + +export const Groups: CollectionConfig = { + slug: 'group', + labels: { + singular: { + de: 'Gruppe', + }, + plural: { + de: 'Gruppen', + }, + }, + fields: [ + { + name: 'photo', + label: { + de: 'Foto', + }, + type: 'upload', + relationTo: 'media', + }, + { + name: 'name', + type: 'text', + label: { + de: 'Name', + }, + required: true, + }, + { + name: 'description', + type: 'textarea', + label: { + de: 'Umschreibung', + }, + required: true, + }, + ], + admin: { + useAsTitle: 'name', + }, + access: { + read: () => true, + create: isAdminOrEmployee(), + update: ({ req, id }) => { + if (!req.user) { + return false + } + + if ( + req.user.roles === 'user' && + id && + req.user.groups?.find( + (group) => + group === id || (typeof group === 'object' && group.id === id), + ) === undefined + ) { + return false + } + + return true + }, + delete: isAdminOrEmployee(), + }, +} diff --git a/src/collections/Parish.ts b/src/collections/Parish.ts new file mode 100644 index 0000000..ed75a88 --- /dev/null +++ b/src/collections/Parish.ts @@ -0,0 +1,91 @@ +import { CollectionConfig } from 'payload' +import { isAdmin, isAdminOrEmployee } from '@/collections/access/admin' + +export const Parish: CollectionConfig = { + slug: 'parish', + labels: { + singular: { + de: 'Gemeinde', + }, + plural: { + de: 'Gemeinden', + }, + }, + fields: [ + { + name: 'name', + label: { + de: 'Name', + }, + type: 'text', + required: true, + }, + { + name: 'churches', + label: { + de: 'Kirchengebäuden', + }, + type: 'relationship', + relationTo: 'church', + hasMany: true, + required: true, + admin: { + allowCreate: false, + }, + }, + { + name: 'employees', + label: { + de: 'Mitarbeiter', + }, + type: 'relationship', + relationTo: 'employees', + hasMany: true, + required: true, + }, + { + name: 'contact', + label: { + de: 'Kontaktinformation', + }, + type: 'textarea', + required: true, + admin: { + rows: 10, + }, + }, + { + name: 'title', + label: { + de: 'Titel', + }, + type: 'text', + required: true, + }, + { + name: 'description', + label: { + de: 'Umschreibung', + }, + type: 'textarea', + required: true, + admin: { + rows: 15, + }, + }, + { + name: 'photo', + type: 'upload', + relationTo: 'media', + }, + ], + admin: { + useAsTitle: 'name', + }, + access: { + read: () => true, + create: isAdmin(), + update: isAdminOrEmployee(), + delete: isAdmin(), + }, +} diff --git a/src/collections/Testimony.ts b/src/collections/Testimony.ts index 9c399b2..c90d8be 100644 --- a/src/collections/Testimony.ts +++ b/src/collections/Testimony.ts @@ -1,4 +1,5 @@ import { CollectionConfig } from 'payload' +import { isAdminOrEmployee } from '@/collections/access/admin' export const Testimony: CollectionConfig = { slug: 'testimony', @@ -52,5 +53,8 @@ export const Testimony: CollectionConfig = { ], access: { read: () => true, + create: isAdminOrEmployee(), + update: isAdminOrEmployee(), + delete: isAdminOrEmployee(), }, } diff --git a/src/collections/Users.ts b/src/collections/Users.ts index c683d0e..28bbe76 100644 --- a/src/collections/Users.ts +++ b/src/collections/Users.ts @@ -1,4 +1,5 @@ import type { CollectionConfig } from 'payload' +import { isAdminOrEmployee } from '@/collections/access/admin' export const Users: CollectionConfig = { slug: 'users', @@ -7,7 +8,61 @@ export const Users: CollectionConfig = { }, auth: true, fields: [ - // Email added by default - // Add more fields as needed + { + name: 'name', + label: { + de: 'Name', + }, + type: 'text', + required: true, + defaultValue: '', + }, + { + name: 'roles', + label: { + de: 'Rolle', + }, + type: 'select', + required: true, + options: [ + { + value: 'user', + label: 'Gläubiger', + }, + { + value: 'employee', + label: 'Mitarbeiter', + }, + { + value: 'admin', + label: 'Administrator', + }, + ], + defaultValue: 'user', + access: { + // Do not allow to upgrade role + create: ({ req, data }) => { + return !(req.user?.roles !== 'admin' && data?.roles === 'admin') + }, + update: ({ req, data }) => { + return !(req.user?.roles !== 'admin' && data?.roles === 'admin') + }, + }, + }, + { + name: 'groups', + label: { + de: 'Mitgliedschaft', + }, + type: 'relationship', + relationTo: 'group', + hasMany: true, + maxDepth: 0, + }, ], + access: { + create: isAdminOrEmployee(), + update: isAdminOrEmployee(), + delete: isAdminOrEmployee(), + }, } diff --git a/src/collections/Worship.ts b/src/collections/Worship.ts index c85d89f..ad88476 100644 --- a/src/collections/Worship.ts +++ b/src/collections/Worship.ts @@ -1,4 +1,5 @@ import { CollectionConfig } from 'payload' +import { isAdminOrEmployee } from '@/collections/access/admin' export const Worship: CollectionConfig = { slug: 'worship', @@ -82,5 +83,8 @@ export const Worship: CollectionConfig = { ], access: { read: () => true, + create: isAdminOrEmployee(), + update: isAdminOrEmployee(), + delete: isAdminOrEmployee(), }, } diff --git a/src/collections/access/admin.ts b/src/collections/access/admin.ts new file mode 100644 index 0000000..847fe88 --- /dev/null +++ b/src/collections/access/admin.ts @@ -0,0 +1,17 @@ +import type { Access } from 'payload' + +export const isAdmin = + (): Access => + ({ req: { user } }) => { + return user?.roles === 'admin' + } + +export const isAdminOrEmployee = + (): Access => + ({ req: { user } }) => { + if (!user) { + return false + } + + return user.roles === 'admin' || user.roles === 'employee' + } diff --git a/src/components/HomeBanner/HomeBanner.tsx b/src/components/HomeBanner/HomeBanner.tsx index 94872f8..6d2c377 100644 --- a/src/components/HomeBanner/HomeBanner.tsx +++ b/src/components/HomeBanner/HomeBanner.tsx @@ -69,7 +69,7 @@ export const HomeBanner = forwardRef( context.shadowBlur = 10 context.shadowColor = 'white' - setTimeout(() => drawStar(context), i * 100); + setTimeout(() => drawStar(context), i * 100) } } }, [drawStar, stars]) diff --git a/src/payload-types.ts b/src/payload-types.ts index f504fe2..82471b1 100644 --- a/src/payload-types.ts +++ b/src/payload-types.ts @@ -8,162 +8,241 @@ export interface Config { auth: { - users: UserAuthOperations; - }; + users: UserAuthOperations + } collections: { - users: User; - media: Media; - worship: Worship; - church: Church; - testimony: Testimony; - 'payload-preferences': PayloadPreference; - 'payload-migrations': PayloadMigration; - }; + parish: Parish + church: Church + worship: Worship + event: Event + group: Group + employees: Employee + testimony: Testimony + users: User + media: Media + 'payload-preferences': PayloadPreference + 'payload-migrations': PayloadMigration + } db: { - defaultIDType: string; - }; - globals: {}; - locale: null; + defaultIDType: string + } + globals: {} + locale: null user: User & { - collection: 'users'; - }; + collection: 'users' + } } export interface UserAuthOperations { forgotPassword: { - email: string; - password: string; - }; + email: string + password: string + } login: { - email: string; - password: string; - }; + email: string + password: string + } registerFirstUser: { - email: string; - password: string; - }; + email: string + password: string + } unlock: { - email: string; - password: string; - }; + email: string + password: string + } } /** * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "users". + * via the `definition` "parish". */ -export interface User { - id: string; - updatedAt: string; - createdAt: string; - email: string; - resetPasswordToken?: string | null; - resetPasswordExpiration?: string | null; - salt?: string | null; - hash?: string | null; - loginAttempts?: number | null; - lockUntil?: string | null; - password?: string | null; -} -/** - * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "media". - */ -export interface Media { - id: string; - alt: string; - updatedAt: string; - createdAt: string; - url?: string | null; - thumbnailURL?: string | null; - filename?: string | null; - mimeType?: string | null; - filesize?: number | null; - width?: number | null; - height?: number | null; - focalX?: number | null; - focalY?: number | null; -} -/** - * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "worship". - */ -export interface Worship { - id: string; - date: string; - location: string | Church; - type: 'MASS' | 'FAMILY' | 'WORD'; - cancelled: boolean; - title?: string | null; - description?: string | null; - updatedAt: string; - createdAt: string; +export interface Parish { + id: string + name: string + churches: (string | Church)[] + employees: (string | Employee)[] + contact: string + title: string + description: string + photo?: string | Media | null + updatedAt: string + createdAt: string } /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "church". */ export interface Church { - id: string; - name: string; - address: string; - updatedAt: string; - createdAt: string; + id: string + name: string + address: string + updatedAt: string + createdAt: string +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "employees". + */ +export interface Employee { + id: string + photo?: string | Media | null + name: string + occupation: string + email?: string | null + telephone?: string | null + updatedAt: string + createdAt: string +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "media". + */ +export interface Media { + id: string + alt: string + updatedAt: string + createdAt: string + url?: string | null + thumbnailURL?: string | null + filename?: string | null + mimeType?: string | null + filesize?: number | null + width?: number | null + height?: number | null + focalX?: number | null + focalY?: number | null +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "worship". + */ +export interface Worship { + id: string + date: string + location: string | Church + type: 'MASS' | 'FAMILY' | 'WORD' + cancelled: boolean + title?: string | null + description?: string | null + updatedAt: string + createdAt: string +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "event". + */ +export interface Event { + id: string + photo?: string | Media | null + title: string + datum: string + parish?: (string | Parish)[] | null + group?: (string | null) | Group + shortDescription: string + description: { + 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 + } + cancelled: boolean + updatedAt: string + createdAt: string +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "group". + */ +export interface Group { + id: string + photo?: string | Media | null + name: string + description: string + updatedAt: string + createdAt: string } /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "testimony". */ export interface Testimony { - id: string; - testimony: string; - name: string; - occupation?: string | null; - category: 'EUCHARIST'; - updatedAt: string; - createdAt: string; + id: string + testimony: string + name: string + occupation?: string | null + category: 'EUCHARIST' + updatedAt: string + createdAt: string +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "users". + */ +export interface User { + id: string + name: string + roles: 'user' | 'employee' | 'admin' + groups?: (string | Group)[] | null + updatedAt: string + createdAt: string + email: string + resetPasswordToken?: string | null + resetPasswordExpiration?: string | null + salt?: string | null + hash?: string | null + loginAttempts?: number | null + lockUntil?: string | null + password?: string | null } /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-preferences". */ export interface PayloadPreference { - id: string; + id: string user: { - relationTo: 'users'; - value: string | User; - }; - key?: string | null; + relationTo: 'users' + value: string | User + } + key?: string | null value?: | { - [k: string]: unknown; + [k: string]: unknown } | unknown[] | string | number | boolean - | null; - updatedAt: string; - createdAt: string; + | null + updatedAt: string + createdAt: string } /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-migrations". */ export interface PayloadMigration { - id: string; - name?: string | null; - batch?: number | null; - updatedAt: string; - createdAt: string; + id: string + name?: string | null + batch?: number | null + updatedAt: string + createdAt: string } /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "auth". */ export interface Auth { - [k: string]: unknown; + [k: string]: unknown } - declare module 'payload' { export interface GeneratedTypes extends Config {} -} \ No newline at end of file +} diff --git a/src/payload.config.ts b/src/payload.config.ts index f66a0a9..1956813 100644 --- a/src/payload.config.ts +++ b/src/payload.config.ts @@ -12,6 +12,10 @@ import { Worship } from '@/collections/Worship' import { Churches } from '@/collections/Churches' import { de } from '@payloadcms/translations/languages/de' import { Testimony } from '@/collections/Testimony' +import { Parish } from '@/collections/Parish' +import { Employees } from '@/collections/Employees' +import { Groups } from '@/collections/Groups' +import { Events } from '@/collections/Events' const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) @@ -20,7 +24,17 @@ export default buildConfig({ admin: { user: Users.slug, }, - collections: [Users, Media, Worship, Churches, Testimony], + collections: [ + Parish, + Churches, + Worship, + Events, + Groups, + Employees, + Testimony, + Users, + Media, + ], editor: lexicalEditor(), secret: process.env.PAYLOAD_SECRET || '', typescript: {