feat: Phase 3 complete — push alerts, feedback scoring, S3 fallback, SPA routing, mobile CSS
- ImpactScoreService: implement getFeedbackBonus() (ST_Distance query on UserFeedback within 20km/6h) - ImpactScoreService: trigger PushNotificationService when score >= 70 - PushNotificationService: send VAPID WebPush to spot subscribers, clean expired subs - PushController: GET vapid-public-key, POST/DELETE subscribe with rate limiting - SentinelHubClient: add optional $collection param, add Sentinel-3 OLCI FAI evalscript (MCI) - IngestionService: add $collection param + HighCloudCoverageException for fallback logic - IngestionService: add ingestWithFallback() — tries S2, falls back to S3 on high cloud - IngestSentinelCommand: --source=auto (default) triggers ingestWithFallback - FeedbackController: rate limiting via apiFeedbackLimiter - Migration: push_subscription table - rate_limiter.yaml: api_read(120/min), api_feedback(10/min), api_push(5/min) - sw.js: service worker handling push events + notificationclick - usePushSubscription hook: subscribe/unsubscribe lifecycle with VAPID - SpotPanel: PushButton component integrated - SpotPanel.css + TimelineSlider.css: mobile responsive (bottom-sheet on small screens) - Caddyfile: SPA served at / with try_files fallback, sw.js served from root scope - vite.config.js: build outDir → backend/public (not /spa) - deploy/post-receive.sh: full deploy script (composer, npm build, migrations, cache, docker up) - docs/roadmap.md: all Phase 3 + transversal items marked done Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
34
backend/src/Command/GenerateVapidKeysCommand.php
Normal file
34
backend/src/Command/GenerateVapidKeysCommand.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use Minishlink\WebPush\VAPID;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:generate-vapid-keys',
|
||||
description: 'Génère les clés VAPID pour les notifications push',
|
||||
)]
|
||||
class GenerateVapidKeysCommand extends Command
|
||||
{
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$keys = VAPID::createVapidKeys();
|
||||
|
||||
$io->title('Clés VAPID générées');
|
||||
$io->text('Ajoutez ces lignes dans votre .env.local (ou secrets Docker) :');
|
||||
$io->newLine();
|
||||
$io->writeln('VAPID_PUBLIC_KEY=' . $keys['publicKey']);
|
||||
$io->writeln('VAPID_PRIVATE_KEY=' . $keys['privateKey']);
|
||||
$io->newLine();
|
||||
$io->warning('Ne committez JAMAIS la clé privée. Utilisez les secrets Docker ou .env.local (non commité).');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ class IngestSentinelCommand extends Command
|
||||
->addOption('zone', 'z', InputOption::VALUE_OPTIONAL,
|
||||
'Zone spécifique. Défaut : toutes les zones.', null)
|
||||
->addOption('source', 's', InputOption::VALUE_OPTIONAL,
|
||||
'Source satellite (Sentinel-2 ou Sentinel-3)', 'Sentinel-2');
|
||||
'Source satellite : Sentinel-2, Sentinel-3, ou auto (S2 avec fallback S3)', 'auto');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
@@ -71,7 +71,15 @@ class IngestSentinelCommand extends Command
|
||||
foreach ($zones as $name => $bbox) {
|
||||
$io->section("Zone : {$name}");
|
||||
try {
|
||||
$this->ingestionService->ingest($bbox, $date, $source);
|
||||
if ($source === 'auto') {
|
||||
$this->ingestionService->ingestWithFallback($bbox, $date);
|
||||
} else {
|
||||
$collection = match (strtolower($source)) {
|
||||
'sentinel-3' => 'sentinel-3-olci',
|
||||
default => 'sentinel-2-l2a',
|
||||
};
|
||||
$this->ingestionService->ingest($bbox, $date, $source, $collection);
|
||||
}
|
||||
$io->success("OK — {$name}");
|
||||
} catch (\Throwable $e) {
|
||||
$io->error("ÉCHEC — {$name} : " . $e->getMessage());
|
||||
|
||||
Reference in New Issue
Block a user