teqo-backup/src/collections/GoogleCalendarSync.ts
Francisco Solla 2f247d40a6 fix(C115): revisão /simplify — relógio com baseline de espelho, reopen sem re-cancelamento, hardening do webhook
- lastMirroredChangeAt (activity, migration): clock rule compara contra a
  última mudança espelhada, não updatedAt (task toggle não silencia edição
  pendente do Google)
- snapshot exclui ids que o motor deletou (removedEventIds separado do
  remoteEventIds): reopen pós-cancelamento não vira re-cancelamento fantasma
- cancelamento do Google respeita o relógio: cancel mais novo cancela; Teqo
  mais novo (reopen) limpa o trashed e recria
- escrita reversa sem req.user: autoria do registro correta + sem gate 403
  do deputado; hook de sync pula a própria escrita reversa (sem passada aninhada)
- canal: segredo rotaciona por criação; novo canal persistido antes do stop
  do antigo; identity fields read:false; create admin-only; leitura prefere
  linha configurada; pre-filter do webhook antes do bootstrap do Payload
- testes: all-day ida-e-volta, task-toggle, reopen, slug congelado, e2e dos
  caminhos 200, render tests do dialog/feed; asserções herméticas a
  atividades ambiente dos arquivos irmãos (DB compartilhado)
2026-08-11 22:42:05 -03:00

181 lines
5.2 KiB
TypeScript

import type { CollectionConfig } from 'payload'
import {
canCreateGoogleCalendarSync,
canDeleteGoogleCalendarSync,
canReadGoogleCalendarSync,
canReadGoogleCalendarSyncIdentityField,
canSetGoogleCalendarSyncConfigField,
canSetGoogleCalendarSyncDisabled,
canSetGoogleCalendarSyncSystemField,
canUpdateGoogleCalendarSync,
} from '@/utilities/access/googleCalendarSync'
import { googleCalendarSyncConfigHook } from '@/utilities/googleCalendarSyncHooks'
/**
* C114 — configuration and state of the campaign→Google calendar mirror
* (one row in practice, created/edited by Payload admin in ops). The service
* account private key never lives here — it is `GOOGLE_CALENDAR_SERVICE_ACCOUNT_KEY`
* in the environment. Changing `calendarId` (or re-enabling) re-runs the full
* reconciliation automatically via `googleCalendarSyncConfigHook` (D7).
*/
export const GoogleCalendarSync: CollectionConfig = {
slug: 'googleCalendarSync',
labels: {
singular: 'Sincronização Google Agenda',
plural: 'Sincronização Google Agenda',
},
admin: {
group: 'Campanha',
useAsTitle: 'calendarId',
defaultColumns: [
'calendarId',
'lastSyncedAt',
'lastSuccessAt',
'lastError',
'pushChannelExpiresAt',
],
},
access: {
create: canCreateGoogleCalendarSync,
read: canReadGoogleCalendarSync,
update: canUpdateGoogleCalendarSync,
delete: canDeleteGoogleCalendarSync,
},
hooks: {
afterChange: [googleCalendarSyncConfigHook],
},
fields: [
{
name: 'calendarId',
type: 'text',
label: 'ID do calendário (Google)',
index: true,
access: {
create: canSetGoogleCalendarSyncConfigField,
update: canSetGoogleCalendarSyncConfigField,
},
admin: {
description:
'O calendarId do calendário "Agenda da Campanha" na conta Google da campanha (ex.: c_…@group.calendar.google.com). O calendário precisa estar público no link e a service account com permissão de edição.',
},
},
{
name: 'disabledAt',
type: 'date',
label: 'Desativado em',
index: true,
access: {
create: canSetGoogleCalendarSyncDisabled,
update: canSetGoogleCalendarSyncDisabled,
},
admin: {
readOnly: true,
description: 'Enquanto preenchido, o Teqo não escreve no Google.',
},
},
{
name: 'lastSyncedAt',
type: 'date',
label: 'Última sincronização',
admin: { readOnly: true },
access: {
create: canSetGoogleCalendarSyncSystemField,
update: canSetGoogleCalendarSyncSystemField,
},
},
{
name: 'lastSuccessAt',
type: 'date',
label: 'Última sincronização com sucesso',
admin: { readOnly: true },
access: {
create: canSetGoogleCalendarSyncSystemField,
update: canSetGoogleCalendarSyncSystemField,
},
},
{
name: 'lastErrorAt',
type: 'date',
label: 'Último erro',
admin: { readOnly: true },
access: {
create: canSetGoogleCalendarSyncSystemField,
update: canSetGoogleCalendarSyncSystemField,
},
},
{
name: 'lastError',
type: 'textarea',
label: 'Mensagem do último erro',
admin: { readOnly: true },
access: {
create: canSetGoogleCalendarSyncSystemField,
update: canSetGoogleCalendarSyncSystemField,
},
},
{
name: 'pushChannelId',
type: 'text',
label: 'ID do canal de notificação (Google)',
admin: { readOnly: true },
access: {
create: canSetGoogleCalendarSyncSystemField,
read: canReadGoogleCalendarSyncIdentityField,
update: canSetGoogleCalendarSyncSystemField,
},
},
{
name: 'pushChannelResourceId',
type: 'text',
label: 'Resource ID do canal (Google)',
admin: { readOnly: true },
access: {
create: canSetGoogleCalendarSyncSystemField,
read: canReadGoogleCalendarSyncIdentityField,
update: canSetGoogleCalendarSyncSystemField,
},
},
{
name: 'pushChannelExpiresAt',
type: 'date',
label: 'Canal de notificação expira em',
admin: { readOnly: true },
access: {
create: canSetGoogleCalendarSyncSystemField,
update: canSetGoogleCalendarSyncSystemField,
},
},
{
name: 'pushChannelSecret',
type: 'text',
label: 'Segredo do webhook (URL)',
admin: { readOnly: true },
access: {
create: canSetGoogleCalendarSyncSystemField,
read: canReadGoogleCalendarSyncIdentityField,
update: canSetGoogleCalendarSyncSystemField,
},
},
{
name: 'pushChannelError',
type: 'textarea',
label: 'Erro do canal de notificação',
admin: { readOnly: true },
access: {
create: canSetGoogleCalendarSyncSystemField,
update: canSetGoogleCalendarSyncSystemField,
},
},
{
name: 'lastSeenEventIds',
type: 'json',
label: 'Eventos vistos na última sincronização',
admin: { readOnly: true },
access: {
create: canSetGoogleCalendarSyncSystemField,
update: canSetGoogleCalendarSyncSystemField,
},
},
],
}