mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-14 17:43:04 +00:00
- users/signature/subscription/consent: all operations Payload-admin-only (Payload's default allowed ANY authenticated user, incl. campaign JWTs via /api — privilege escalation + PII exposure) - post/tag/media/petition: public read kept, writes admin-only, readVersions admin-only on drafted collections - leader lockdown: staff gates on /campanha/demandas* and /campanha/planos*; removed the dead leader branch of the demand detail page - fix createLeaderSupporter: scope now asserted via the leader's engaged leadership municipalities (canReadMunicipality always denied leaders) - candidate alignment: supporter area, invite creation, and escalated demand decisions now treat candidate as unrestricted staff consistently - tests: collectionAccessLockdown.int.spec pins the lockdown; leased-consent helpers generalized (supporter keys); fixtures purge deletes residue invites before leaderships; stale leader-phone tests updated to the lockdown model; 'sem Praça' copy renamed to 'sem município'
133 lines
3.1 KiB
TypeScript
133 lines
3.1 KiB
TypeScript
import { normalizeFacebookPixelId } from '@/lib/facebookPixel'
|
|
import { payloadAdminOnly } from '@/utilities/campaignAccess'
|
|
import { revalidateDocumentById } from '@/utilities/documents'
|
|
import type { CollectionConfig } from 'payload'
|
|
|
|
const slug = 'petition'
|
|
|
|
export const Petition: CollectionConfig<typeof slug> = {
|
|
slug,
|
|
labels: {
|
|
singular: 'Abaixo-assinado',
|
|
plural: 'Abaixo-assinados',
|
|
},
|
|
admin: {
|
|
group: 'Abaixo-assinados',
|
|
useAsTitle: 'title',
|
|
livePreview: {
|
|
url: ({ data }) => `/abaixo-assinado/${data.id}`,
|
|
},
|
|
},
|
|
versions: {
|
|
drafts: {
|
|
schedulePublish: true,
|
|
},
|
|
maxPerDoc: 5,
|
|
},
|
|
access: {
|
|
read: () => true,
|
|
readVersions: payloadAdminOnly,
|
|
create: payloadAdminOnly,
|
|
update: payloadAdminOnly,
|
|
delete: payloadAdminOnly,
|
|
},
|
|
hooks: {
|
|
beforeChange: [
|
|
({ data }) => {
|
|
if (data?.tracking?.facebookPixelId != null) {
|
|
data.tracking.facebookPixelId = normalizeFacebookPixelId(data.tracking.facebookPixelId)
|
|
}
|
|
return data
|
|
},
|
|
],
|
|
afterChange: [
|
|
({ doc, previousDoc, operation }) => {
|
|
// Admin create view creates the initial draft during render.
|
|
// Skip cache invalidation only for that first draft creation.
|
|
if (operation === 'create' && doc._status !== 'published') return
|
|
|
|
revalidateDocumentById(slug, previousDoc?.id ?? doc.id)
|
|
},
|
|
],
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'id',
|
|
type: 'text',
|
|
label: 'Trecho Identificador da URL',
|
|
required: true,
|
|
},
|
|
{
|
|
name: 'title',
|
|
type: 'text',
|
|
label: 'Título',
|
|
required: true,
|
|
},
|
|
{
|
|
name: 'subtitle',
|
|
type: 'text',
|
|
label: 'Subtítulo',
|
|
required: true,
|
|
},
|
|
{
|
|
name: 'enabled',
|
|
type: 'checkbox',
|
|
label: 'Ativo',
|
|
required: true,
|
|
},
|
|
{
|
|
name: 'body',
|
|
type: 'richText',
|
|
label: 'Corpo',
|
|
required: true,
|
|
},
|
|
{
|
|
name: 'form',
|
|
type: 'group',
|
|
label: 'Formulário',
|
|
required: true,
|
|
fields: [
|
|
{
|
|
name: 'title',
|
|
type: 'text',
|
|
label: 'Título',
|
|
},
|
|
{
|
|
name: 'subtitle',
|
|
type: 'text',
|
|
label: 'Subtítulo',
|
|
},
|
|
{
|
|
name: 'consent',
|
|
type: 'relationship',
|
|
label: 'Consentimento',
|
|
relationTo: 'consent',
|
|
required: true,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: 'tracking',
|
|
type: 'group',
|
|
label: 'Rastreamento / Ads',
|
|
fields: [
|
|
{
|
|
name: 'facebookPixelId',
|
|
type: 'text',
|
|
label: 'ID do Pixel do Meta (Facebook)',
|
|
admin: {
|
|
description:
|
|
'Cole somente o ID numérico do Events Manager (ex.: 123456789012345), não o snippet HTML completo.',
|
|
},
|
|
validate: (value: string | null | undefined) => {
|
|
if (!value) return true
|
|
if (!normalizeFacebookPixelId(String(value))) {
|
|
return 'Informe somente o ID numérico do Pixel (5 a 20 dígitos), sem HTML ou script.'
|
|
}
|
|
return true
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
}
|