From e002125805c6088190862b8d8354ede7601f5583 Mon Sep 17 00:00:00 2001 From: tobias Date: Sun, 23 Jun 2024 10:23:51 +0200 Subject: [PATCH] Add collections & access functions from paystro --- src/app/(payload)/access/isAdmin.ts | 42 ++++++++ src/app/(payload)/access/isEditor.ts | 10 ++ src/app/(payload)/access/isUser.ts | 10 ++ src/app/(payload)/collections/Authors.ts | 47 +++++++++ src/app/(payload)/collections/Media.ts | 23 +++++ src/app/(payload)/collections/Posts.ts | 124 +++++++++++++++++++++++ src/app/(payload)/collections/Users.ts | 40 ++++++++ 7 files changed, 296 insertions(+) create mode 100644 src/app/(payload)/access/isAdmin.ts create mode 100644 src/app/(payload)/access/isEditor.ts create mode 100644 src/app/(payload)/access/isUser.ts create mode 100644 src/app/(payload)/collections/Authors.ts create mode 100644 src/app/(payload)/collections/Media.ts create mode 100644 src/app/(payload)/collections/Posts.ts create mode 100644 src/app/(payload)/collections/Users.ts diff --git a/src/app/(payload)/access/isAdmin.ts b/src/app/(payload)/access/isAdmin.ts new file mode 100644 index 0000000..bcbb7f3 --- /dev/null +++ b/src/app/(payload)/access/isAdmin.ts @@ -0,0 +1,42 @@ +import { Access, FieldAccess } from 'payload/types' +import type { User } from 'types/payload-types' + +export const isAdmin = ({ req: { user } }) => { + if (user && user.roles?.includes('admin')) { + return true + } + + return false +} + +export const isAdminOrCreatedBy = ({ req: { user } }) => { + if (user && user.role === 'admin') { + return true + } + + if (user) { + return { + createdBy: { + equals: user.id, + }, + } + } + + return false +} + +export const isAdminOrSelf = ({ req: { user } }) => { + if (user) { + if (user.roles?.includes('admin')) { + return true + } + + // Non-admin: can only access themselves + return { + id: { + equals: user.id, + }, + } + } + return false +} diff --git a/src/app/(payload)/access/isEditor.ts b/src/app/(payload)/access/isEditor.ts new file mode 100644 index 0000000..2ce8d71 --- /dev/null +++ b/src/app/(payload)/access/isEditor.ts @@ -0,0 +1,10 @@ +import { Access, FieldAccess } from 'payload/types' +import type { User } from 'types/payload-types' + +export const isEditor = ({ req: { user } }) => { + if (user?.roles?.some((role) => ['editor', 'admin'].includes(role))) { + return true + } + + return false +} diff --git a/src/app/(payload)/access/isUser.ts b/src/app/(payload)/access/isUser.ts new file mode 100644 index 0000000..7c33d30 --- /dev/null +++ b/src/app/(payload)/access/isUser.ts @@ -0,0 +1,10 @@ +import { Access, FieldAccess } from 'payload/types' +import type { User } from 'types/payload-types' + +export const isUser = ({ req: { user } }) => { + if (user?.roles?.some((role) => ['user', 'editor', 'admin'].includes(role))) { + return true + } + + return false +} diff --git a/src/app/(payload)/collections/Authors.ts b/src/app/(payload)/collections/Authors.ts new file mode 100644 index 0000000..a1f08c1 --- /dev/null +++ b/src/app/(payload)/collections/Authors.ts @@ -0,0 +1,47 @@ +import { CollectionConfig } from 'payload/types' +import { isEditor } from '@payload/access/isEditor' +import { isUser } from '@payload/access/isUser' + +const Authors: CollectionConfig = { + slug: 'authors', + admin: { + defaultColumns: ['name'], + useAsTitle: 'name', + }, + access: { + //TODO: Author can CRUD own post + create: isUser, + read: () => true, + update: isEditor, + delete: isEditor, + }, + fields: [ + { + name: 'avatar', + type: 'upload', + relationTo: 'media', + required: true, + }, + { + name: 'name', + type: 'text', + required: true, + }, + { + name: 'bio', + type: 'text', + required: false, + }, + + { + name: 'user', + type: 'upload', + relationTo: 'users', + admin: { + description: 'The selected user will be able to edit this author', + }, + }, + ], +} + +export default Authors diff --git a/src/app/(payload)/collections/Media.ts b/src/app/(payload)/collections/Media.ts new file mode 100644 index 0000000..f775a7a --- /dev/null +++ b/src/app/(payload)/collections/Media.ts @@ -0,0 +1,23 @@ +import { isUser } from '@payload/access/isUser' +import { CollectionConfig } from 'payload/types' + +export const Media: CollectionConfig = { + slug: 'media', + admin: {}, + access: { + read: (): boolean => true, + create: isUser, + update: isUser, + delete: isUser, + }, + upload: true, + fields: [ + { + name: 'alt', + type: 'text', + required: true, + }, + ], +} + +export default Media diff --git a/src/app/(payload)/collections/Posts.ts b/src/app/(payload)/collections/Posts.ts new file mode 100644 index 0000000..313b47e --- /dev/null +++ b/src/app/(payload)/collections/Posts.ts @@ -0,0 +1,124 @@ +import { CollectionConfig } from 'payload/types' +import { isEditor } from '@payload/access/isEditor' +import { isUser } from '@payload/access/isUser' + +const Posts: CollectionConfig = { + slug: 'posts', + versions: true, + admin: { + defaultColumns: ['title', 'author', 'status'], + useAsTitle: 'title', + }, + access: { + //TODO: Author can CRUD own post + create: isUser, + read: () => true, + update: isEditor, + delete: isEditor, + }, + hooks: { + afterChange: [ + async () => { + console.log(process.env.TOKEN) + + try { + process.env.NODE_ENV !== 'development' && + console.log( + await fetch(`${process.env.DRONE_URL}/api/repos/${process.env.REPOSITORY}/builds`, { + method: 'POST', + headers: { + Authorization: `Bearer ${process.env.TOKEN}`, + }, + }), + ) + } catch (e) { + console.log(e) + } + }, + ], + }, + fields: [ + { + name: 'title', + type: 'text', + required: true, + }, + { + name: 'summary', + type: 'text', + required: false, + }, + { + name: 'publishedDate', + type: 'date', + required: false, + admin: { + position: 'sidebar', + }, + }, + { + name: 'thumbnail', + type: 'upload', + relationTo: 'media', + required: true, + }, + /* { + name: 'content', + type: 'richText', + editor: slateEditor({ + admin: { + elements: ['h2', 'h3', 'h4', 'link', 'ol', 'ul', 'upload', 'blockquote', 'indent'], + leaves: ['bold', 'italic', 'underline', 'strikethrough'], + upload: { + collections: { + media: { + fields: [ + { + name: 'image', + type: 'upload', + relationTo: 'media', + required: true, + }, + ], + }, + }, + }, + }, + }), + }, */ + { + name: 'author', + //TODO: Add active user as default + type: 'relationship', + relationTo: 'authors', + admin: { + position: 'sidebar', + }, + }, + { + name: 'status', + type: 'select', + required: true, + options: [ + { + value: 'draft', + label: 'Draft', + }, + { + value: 'published', + label: 'Published', + }, + { + value: 'archived', + label: 'Archived', + }, + ], + defaultValue: 'draft', + admin: { + position: 'sidebar', + }, + }, + ], +} + +export default Posts diff --git a/src/app/(payload)/collections/Users.ts b/src/app/(payload)/collections/Users.ts new file mode 100644 index 0000000..d586be3 --- /dev/null +++ b/src/app/(payload)/collections/Users.ts @@ -0,0 +1,40 @@ +import { CollectionConfig } from 'payload/types' +import { isAdmin, isAdminOrSelf } from '@payload/access/isAdmin' + +const Users: CollectionConfig = { + slug: 'users', + auth: true, + admin: { + defaultColumns: ['roles', 'email'], + useAsTitle: 'email', + }, + access: { + create: isAdmin, + read: isAdminOrSelf, + update: isAdminOrSelf, + delete: isAdmin, + }, + fields: [ + { + name: 'roles', + type: 'select', + options: [ + { label: 'Admin', value: 'admin' }, //CRUD, role creation + { label: 'Editor', value: 'editor' }, //CRUD + { label: 'User', value: 'user' }, //cRud, CRUD own entries + ], + required: true, + defaultValue: 'user', + // JWT so that role is accessible from 'req.user' + saveToJWT: true, + hasMany: true, + access: { + create: isAdmin, + read: () => true, + update: isAdmin, + }, + }, + ], +} + +export default Users