feat: cache DNS report + purge EmailTracking + crons mis a jour
src/Controller/DnsReportController.php:
- Injection du pool cache dns_infra_cache via #[Autowire]
- Les resultats des checks sont caches avec la cle dns_infra_check_{token}
pendant 1 heure (3600s) pour eviter de rappeler toutes les APIs
(Cloudflare, AWS SES, Mailcow, RDAP, dig) a chaque rechargement
- La date du rapport est stockee dans le cache au format ISO 8601
config/packages/packages/cache.yaml:
- Nouveau pool dns_infra_cache sur Redis avec default_lifetime 3600s
src/Command/PurgeEmailTrackingCommand.php (nouveau):
- Commande app:email-tracking:purge qui supprime les EmailTracking
dont sentAt est anterieur au seuil (90 jours par defaut)
- Option --days pour changer la retention (ex: --days=30)
- Utilise une requete DQL DELETE pour performance
ansible/deploy.yml.disabled:
- Nouveau cron "crm-siteconseil email-tracking purge": tous les jours
a 5h du matin, supprime les EmailTracking de plus de 90 jours
docker/cron/entrypoint.sh:
- Liste complete des taches cron mise a jour avec:
- */5 min: expire-pending, infra:snapshot
- */15 min: services:check
- toutes les heures: monitor:messenger
- toutes les 2h: dns:check
- toutes les 6h: stripe:sync
- 3h: meilisearch consistency
- 4h: attestations clean
- 5h: email-tracking purge
- 6h: cloudflare clean
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -272,6 +272,14 @@
|
|||||||
job: "docker compose -f /var/www/crm-siteconseil/docker-compose-prod.yml exec -T php php bin/console app:dns:check --env=prod >> /var/log/crm-siteconseil-dns-check.log 2>&1"
|
job: "docker compose -f /var/www/crm-siteconseil/docker-compose-prod.yml exec -T php php bin/console app:dns:check --env=prod >> /var/log/crm-siteconseil-dns-check.log 2>&1"
|
||||||
user: bot
|
user: bot
|
||||||
|
|
||||||
|
- name: Configure email tracking purge cron (daily at 5am)
|
||||||
|
cron:
|
||||||
|
name: "crm-siteconseil email-tracking purge"
|
||||||
|
minute: "0"
|
||||||
|
hour: "5"
|
||||||
|
job: "docker compose -f /var/www/crm-siteconseil/docker-compose-prod.yml exec -T php php bin/console app:email-tracking:purge --env=prod >> /var/log/crm-siteconseil-email-purge.log 2>&1"
|
||||||
|
user: bot
|
||||||
|
|
||||||
- name: Configure Cloudflare acme-challenge cleanup cron (daily at 6am)
|
- name: Configure Cloudflare acme-challenge cleanup cron (daily at 6am)
|
||||||
cron:
|
cron:
|
||||||
name: "crm-siteconseil cloudflare clean"
|
name: "crm-siteconseil cloudflare clean"
|
||||||
|
|||||||
@@ -17,3 +17,6 @@ framework:
|
|||||||
adapter: cache.adapter.redis
|
adapter: cache.adapter.redis
|
||||||
vite_cache_pool:
|
vite_cache_pool:
|
||||||
adapter: cache.adapter.redis
|
adapter: cache.adapter.redis
|
||||||
|
dns_infra_cache:
|
||||||
|
adapter: cache.adapter.redis
|
||||||
|
default_lifetime: 3600
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
echo "=== CRM SITECONSEIL Cron ==="
|
echo "=== CRM SITECONSEIL Cron ==="
|
||||||
echo "Registered tasks:"
|
echo "Registered tasks:"
|
||||||
echo " - */5 * * * * app:orders:expire-pending"
|
echo " - */5 * * * * app:orders:expire-pending"
|
||||||
echo " - 0 * * * * app:monitor:messenger"
|
echo " - 0 * * * * app:monitor:messenger"
|
||||||
echo " - 0 3 * * * app:meilisearch:check-consistency --fix"
|
echo " - 0 */2 * * app:dns:check"
|
||||||
echo " - 0 4 * * * app:attestations:clean"
|
echo " - 0 3 * * * app:meilisearch:check-consistency --fix"
|
||||||
echo " - */15 * * * app:services:check"
|
echo " - 0 4 * * * app:attestations:clean"
|
||||||
|
echo " - 0 5 * * * app:email-tracking:purge"
|
||||||
|
echo " - 0 6 * * * app:cloudflare:clean"
|
||||||
|
echo " - 0 */6 * * app:stripe:sync"
|
||||||
|
echo " - */5 * * * * app:infra:snapshot"
|
||||||
|
echo " - */15 * * * * app:services:check"
|
||||||
echo "===================="
|
echo "===================="
|
||||||
exec cron -f
|
exec cron -f
|
||||||
|
|||||||
52
src/Command/PurgeEmailTrackingCommand.php
Normal file
52
src/Command/PurgeEmailTrackingCommand.php
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Command;
|
||||||
|
|
||||||
|
use App\Repository\EmailTrackingRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
|
||||||
|
#[AsCommand(
|
||||||
|
name: 'app:email-tracking:purge',
|
||||||
|
description: 'Supprime les enregistrements EmailTracking de plus de 90 jours',
|
||||||
|
)]
|
||||||
|
class PurgeEmailTrackingCommand extends Command
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private EmailTrackingRepository $repository,
|
||||||
|
private EntityManagerInterface $em,
|
||||||
|
) {
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->addOption('days', null, InputOption::VALUE_REQUIRED, 'Nombre de jours de retention', '90');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$io = new SymfonyStyle($input, $output);
|
||||||
|
$days = (int) $input->getOption('days');
|
||||||
|
$threshold = new \DateTimeImmutable("-{$days} days");
|
||||||
|
|
||||||
|
$io->title("Purge EmailTracking (> $days jours)");
|
||||||
|
$io->text('Seuil : '.$threshold->format('d/m/Y H:i:s'));
|
||||||
|
|
||||||
|
$qb = $this->em->createQueryBuilder()
|
||||||
|
->delete('App\Entity\EmailTracking', 'e')
|
||||||
|
->where('e.sentAt < :threshold')
|
||||||
|
->setParameter('threshold', $threshold);
|
||||||
|
|
||||||
|
$deleted = $qb->getQuery()->execute();
|
||||||
|
|
||||||
|
$io->success("$deleted enregistrement(s) supprime(s).");
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,9 @@ use App\Service\MailcowService;
|
|||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||||
|
use Symfony\Contracts\Cache\CacheInterface;
|
||||||
|
use Symfony\Contracts\Cache\ItemInterface;
|
||||||
|
|
||||||
class DnsReportController extends AbstractController
|
class DnsReportController extends AbstractController
|
||||||
{
|
{
|
||||||
@@ -28,6 +31,7 @@ class DnsReportController extends AbstractController
|
|||||||
CloudflareService $cloudflare,
|
CloudflareService $cloudflare,
|
||||||
MailcowService $mailcow,
|
MailcowService $mailcow,
|
||||||
EmailTrackingRepository $emailTrackingRepository,
|
EmailTrackingRepository $emailTrackingRepository,
|
||||||
|
#[Autowire(service: 'dns_infra_cache')] CacheInterface $cache,
|
||||||
): Response {
|
): Response {
|
||||||
$tracking = $emailTrackingRepository->findOneBy(['messageId' => $token]);
|
$tracking = $emailTrackingRepository->findOneBy(['messageId' => $token]);
|
||||||
|
|
||||||
@@ -35,43 +39,57 @@ class DnsReportController extends AbstractController
|
|||||||
throw $this->createNotFoundException('Rapport introuvable.');
|
throw $this->createNotFoundException('Rapport introuvable.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$errors = [];
|
$cacheKey = 'dns_infra_check_'.$token;
|
||||||
$warnings = [];
|
|
||||||
$successes = [];
|
|
||||||
$domainResults = [];
|
|
||||||
|
|
||||||
$cfRecordsByDomain = $this->loadCloudflareRecords($cloudflare);
|
$data = $cache->get($cacheKey, function (ItemInterface $item) use ($dnsCheck, $awsSes, $cloudflare, $mailcow): array {
|
||||||
|
$item->expiresAfter(3600); // 1 heure
|
||||||
|
|
||||||
foreach (self::DOMAINS as $domain) {
|
$errors = [];
|
||||||
$checks = [];
|
$warnings = [];
|
||||||
$cfRecords = $cfRecordsByDomain[$domain] ?? [];
|
$successes = [];
|
||||||
|
$domainResults = [];
|
||||||
|
|
||||||
$dnsCheck->checkSpf($domain, $checks, $errors, $warnings, $successes);
|
$cfRecordsByDomain = $this->loadCloudflareRecords($cloudflare);
|
||||||
$this->enrichWithCloudflare($checks, $domain, 'SPF', 'TXT', $cfRecords);
|
|
||||||
|
|
||||||
$dnsCheck->checkDmarc($domain, $checks, $errors, $successes);
|
foreach (self::DOMAINS as $domain) {
|
||||||
$this->enrichWithCloudflare($checks, '_dmarc.'.$domain, 'DMARC', 'TXT', $cfRecords);
|
$checks = [];
|
||||||
|
$cfRecords = $cfRecordsByDomain[$domain] ?? [];
|
||||||
|
|
||||||
$dnsCheck->checkMx($domain, self::EXPECTED_MX[$domain] ?? '', $checks, $errors, $successes);
|
$dnsCheck->checkSpf($domain, $checks, $errors, $warnings, $successes);
|
||||||
$this->enrichWithCloudflare($checks, $domain, 'MX', 'MX', $cfRecords);
|
$this->enrichWithCloudflare($checks, $domain, 'SPF', 'TXT', $cfRecords);
|
||||||
|
|
||||||
$dnsCheck->checkBounce($domain, $checks, $errors, $warnings, $successes);
|
$dnsCheck->checkDmarc($domain, $checks, $errors, $successes);
|
||||||
$this->enrichLastCheck($checks, 'bounce.'.$domain, 'MX', $cfRecords);
|
$this->enrichWithCloudflare($checks, '_dmarc.'.$domain, 'DMARC', 'TXT', $cfRecords);
|
||||||
|
|
||||||
$dnsCheck->checkWhois($domain, $checks, $errors, $warnings, $successes);
|
$dnsCheck->checkMx($domain, self::EXPECTED_MX[$domain] ?? '', $checks, $errors, $successes);
|
||||||
|
$this->enrichWithCloudflare($checks, $domain, 'MX', 'MX', $cfRecords);
|
||||||
|
|
||||||
$this->checkAwsSes($domain, $awsSes, $dnsCheck, $checks, $errors, $successes, $cfRecords);
|
$dnsCheck->checkBounce($domain, $checks, $errors, $warnings, $successes);
|
||||||
$this->checkMailcow($domain, $mailcow, $dnsCheck, $checks, $errors, $warnings, $successes, $cfRecords);
|
$this->enrichLastCheck($checks, 'bounce.'.$domain, 'MX', $cfRecords);
|
||||||
|
|
||||||
$domainResults[] = ['domain' => $domain, 'checks' => $checks];
|
$dnsCheck->checkWhois($domain, $checks, $errors, $warnings, $successes);
|
||||||
}
|
|
||||||
|
$this->checkAwsSes($domain, $awsSes, $dnsCheck, $checks, $errors, $successes, $cfRecords);
|
||||||
|
$this->checkMailcow($domain, $mailcow, $dnsCheck, $checks, $errors, $warnings, $successes, $cfRecords);
|
||||||
|
|
||||||
|
$domainResults[] = ['domain' => $domain, 'checks' => $checks];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'domainResults' => $domainResults,
|
||||||
|
'errors' => $errors,
|
||||||
|
'warnings' => $warnings,
|
||||||
|
'successes' => $successes,
|
||||||
|
'date' => (new \DateTimeImmutable())->format('c'),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
return $this->render('dns_report/index.html.twig', [
|
return $this->render('dns_report/index.html.twig', [
|
||||||
'domainResults' => $domainResults,
|
'domainResults' => $data['domainResults'],
|
||||||
'errors' => $errors,
|
'errors' => $data['errors'],
|
||||||
'warnings' => $warnings,
|
'warnings' => $data['warnings'],
|
||||||
'successes' => $successes,
|
'successes' => $data['successes'],
|
||||||
'date' => new \DateTimeImmutable(),
|
'date' => new \DateTimeImmutable($data['date']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user