Files
fitme3070.pt/database/migration_20260926_trial_leads.sql
T
andreandClaude Opus 5.5 04c8c83e66 feat: aulas experimentais guardadas, anti-spam partilhado nos formulários, audit das escritas
- Pedido de aula experimental passa a ficar em trial_leads (por ginásio)
  ANTES do email: sem SMTP o visitante já não vê «erro» e o pedido não se
  perde. Gestão → Aulas experimentais: estados, notas, CSV (auditado),
  apagar para a lixeira; aviso «sem email» quando o aviso não saiu.
- Aula experimental, inscrição e eventos usam o anti-spam do platform-tools
  (honeypot, carimbo assinado com tempo mínimo, prova de JS, limite por IP,
  pontuação de conteúdo) e o bloqueio de IP, em vez de honeypots locais.
  Recusas ficam em blocked_submissions; resposta igual à de sucesso.
  Falha aberto se o módulo rebentar.
- Checkbox de consentimento da aula experimental passa a ser guardada.
- Audit das escritas de conteúdo: criar/editar equipa, modalidades,
  eventos, mensalidades, pedidos de inscrição, galerias, horário (incl.
  arrastar no calendário), horas, contactos e ginásios, com antes/depois
  campo a campo (audit_row/audit_change). Exportação CSV de inscrições
  auditada.
- .table-responsive em todas as tabelas do backoffice.
- Migração: database/migration_20260926_trial_leads.sql (só cria tabelas).

Testado numa cópia da BD de produção: 63/63 verificações HTTP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:33:24 +01:00

52 lines
2.6 KiB
SQL

-- ─── Pedidos de aula experimental + registo do anti-spam ────────────────────
-- Até aqui o pedido de aula experimental só existia como email: sem SMTP (ou
-- com o SMTP em baixo) o visitante via «erro» e o pedido perdia-se. Agora fica
-- guardado primeiro, como as inscrições, e o email é só um aviso.
--
-- blocked_submissions: onde o anti-spam partilhado (platform-tools,
-- functions_antispam_forms.php) guarda o que recusou — um falso positivo é uma
-- lead verdadeira, e tem de se poder ler. Esquema igual ao do dance.
--
-- Só cria tabelas. Safe to re-run.
CREATE TABLE IF NOT EXISTS `trial_leads` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`gym_id` INT NOT NULL DEFAULT 1,
`name` VARCHAR(150) NOT NULL,
`email` VARCHAR(190) NOT NULL DEFAULT '',
`phone` VARCHAR(40) NOT NULL DEFAULT '',
`preferred_date` DATE NULL DEFAULT NULL,
`period` VARCHAR(40) NOT NULL DEFAULT '',
`message` TEXT NULL,
`consent` TINYINT(1) NOT NULL DEFAULT 0,
`consent_text` VARCHAR(255) NOT NULL DEFAULT '',
`ip` VARCHAR(45) NOT NULL DEFAULT '',
`user_agent` VARCHAR(255) NOT NULL DEFAULT '',
`status` VARCHAR(20) NOT NULL DEFAULT 'novo',
`admin_notes` TEXT NULL,
`email_sent` TINYINT(1) NOT NULL DEFAULT 0,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`updated_by` INT UNSIGNED NULL DEFAULT NULL,
`deleted_at` DATETIME NULL DEFAULT NULL,
`deleted_by` INT UNSIGNED NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_trial_leads_gym` (`gym_id`, `deleted_at`, `status`),
KEY `idx_trial_leads_ip` (`ip`, `created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `blocked_submissions` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`form` VARCHAR(40) NOT NULL COMMENT 'trial, inscricao, evento',
`reason` VARCHAR(190) NOT NULL COMMENT 'Regra que travou, ou os sinais somados',
`score` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Pontuação de conteúdo; 0 nas recusas diretas',
`ip` VARCHAR(45) NOT NULL DEFAULT '',
`user_agent` VARCHAR(250) NOT NULL DEFAULT '',
`payload` TEXT DEFAULT NULL COMMENT 'JSON do que foi submetido, sem o token CSRF',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_bs_form` (`form`),
KEY `idx_bs_ip` (`ip`),
KEY `idx_bs_created` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;