Phase 1 : endpoints API complets

- CoastalPoint → shortName Spot → /api/spots (API Platform)
- SargassumForecast → shortName Forecast → /api/forecasts
- ObservationController : GET /api/observations avec filtres
  bbox (PostGIS ST_Intersects), date, source + geometry ST_AsGeoJSON
- SpotScoreController : GET /api/spots/{id}/score avec ?at= optionnel
- FeedbackController : POST /api/feedback, anonyme, fingerprint SHA256
- CORS : autorisation radarsargasses971.com + localhost dev

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gwadaking
2026-04-01 02:50:06 -04:00
parent bf64340525
commit 4af5e62465
7 changed files with 289 additions and 13 deletions

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Controller;
use App\Entity\CoastalPoint;
use App\Entity\ImpactScore;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/api/spots', name: 'api_spots_')]
class SpotScoreController extends AbstractController
{
public function __construct(private EntityManagerInterface $em) {}
/**
* GET /api/spots/{id}/score
*
* Retourne le score courant pour un CoastalPoint.
* Paramètre optionnel : ?at=YYYY-MM-DDTHH:MM:SSZ (score à un instant donné)
*/
#[Route('/{id}/score', name: 'score', methods: ['GET'])]
public function score(string $id, Request $request): JsonResponse
{
$spot = $this->em->getRepository(CoastalPoint::class)->find($id);
if ($spot === null) {
return $this->json(['error' => 'Spot not found'], 404);
}
$qb = $this->em->getRepository(ImpactScore::class)
->createQueryBuilder('s')
->where('s.coastalPoint = :spot')
->setParameter('spot', $spot)
->orderBy('s.timestamp', 'DESC')
->setMaxResults(1);
$atParam = $request->query->get('at');
if ($atParam !== null) {
try {
$at = new \DateTimeImmutable($atParam);
$qb->andWhere('s.timestamp <= :at')->setParameter('at', $at);
} catch (\Exception) {
return $this->json(['error' => 'Invalid "at" parameter format'], 400);
}
}
$latest = $qb->getQuery()->getOneOrNullResult();
return $this->json([
'spotId' => (string) $spot->getId(),
'name' => $spot->getName(),
'type' => $spot->getType(),
'region' => $spot->getRegion(),
'score' => $latest ? [
'value' => $latest->getScore(),
'level' => $latest->getLevel(),
'distance' => $latest->getDistanceToNearestSargassum(),
'density' => $latest->getDensityEstimate(),
'trend' => $latest->getTrend(),
'computedAt' => $latest->getTimestamp()?->format(\DateTimeInterface::ATOM),
] : null,
]);
}
}