import type { AccessArgs, CollectionConfig } from 'payload' const isAdmin = (args: AccessArgs) => (args.req.user as { role?: string } | null | undefined)?.role === 'admin' export const CATEGORIES = ['وكلاء', 'نماذج', 'استرجاع', 'سلامة', 'أدوات', 'أعمال'] as const const Posts: CollectionConfig = { slug: 'posts', admin: { useAsTitle: 'title', defaultColumns: ['title', 'slug', 'category', 'access', 'status', 'publishedAt'], }, access: { admin: isAdmin, read: ({ req }) => { if (req.user?.role === 'admin') return true // Public: only published posts (per-doc filtering happens in the hook below) return { status: { equals: 'published' } } }, create: isAdmin, update: isAdmin, delete: isAdmin, }, fields: [ { name: 'title', type: 'text', required: true }, { name: 'slug', type: 'text', required: true, unique: true, index: true, admin: { description: 'Latin slug, used in /posts/[slug]' }, }, { name: 'dek', type: 'textarea', required: true, label: 'Standfirst' }, { name: 'access', type: 'select', required: true, defaultValue: 'free', options: [ { label: 'مجاني', value: 'free' }, { label: 'VIP', value: 'vip' }, ], }, { name: 'teaser', type: 'textarea', label: 'Teaser (free preview for VIP posts)', admin: { condition: (_data, siblingData: { access?: string } | undefined) => siblingData?.access === 'vip' }, validate: (value: unknown, { siblingData }: { siblingData?: { access?: string } }) => { if (siblingData?.access === 'vip' && (!value || String(value).trim() === '')) return 'Teaser is required for VIP posts' return true }, }, { name: 'content', type: 'richText', required: true, access: { // Never serve VIP body to non-VIP clients — defense in depth // (the paywall page also never queries it for them). read: ((args: { doc?: { access?: string }; req: { user?: { role?: string } | null } }) => { if (!args.doc) return true if (args.doc.access !== 'vip') return true return args.req.user?.role === 'vip' || args.req.user?.role === 'admin' }) as never, // field-permission signature is structurally compatible; TS can't infer it here }, }, { // Per-post work files. Deliberately not field-gated: ids/filenames are // harmless metadata — the bytes are gated by GET /api/files/[id] + // each file's own `access` (see collections/Files.ts). name: 'attachments', type: 'upload', relationTo: 'files', hasMany: true, label: 'ملفات العمل (VIP)', }, { name: 'category', type: 'select', required: true, options: CATEGORIES.map((c) => ({ label: c, value: c })), }, { name: 'level', type: 'select', required: true, defaultValue: 'مبتدئ', options: [ { label: 'مبتدئ', value: 'مبتدئ' }, { label: 'متوسط', value: 'متوسط' }, { label: 'متقدم', value: 'متقدم' }, ], }, { name: 'views', type: 'number', defaultValue: 0, admin: { readOnly: true, position: 'sidebar' } }, { name: 'likes', type: 'number', defaultValue: 0, admin: { readOnly: true, position: 'sidebar' } }, { name: 'cover', type: 'upload', relationTo: 'media', required: true }, { name: 'readingMinutes', type: 'number', required: true, defaultValue: 5 }, { name: 'publishedAt', type: 'date', admin: { position: 'sidebar' } }, { name: 'status', type: 'select', required: true, defaultValue: 'draft', options: [ { label: 'Draft', value: 'draft' }, { label: 'Published', value: 'published' }, ], }, ], } export default Posts