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:
79
backend/src/Controller/FeedbackController.php
Normal file
79
backend/src/Controller/FeedbackController.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\CoastalPoint;
|
||||
use App\Entity\UserFeedback;
|
||||
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/feedback', name: 'api_feedback_')]
|
||||
class FeedbackController extends AbstractController
|
||||
{
|
||||
public function __construct(private EntityManagerInterface $em) {}
|
||||
|
||||
/**
|
||||
* POST /api/feedback
|
||||
*
|
||||
* Corps attendu :
|
||||
* {
|
||||
* "lat": 14.65,
|
||||
* "lng": -61.05,
|
||||
* "hasSeaweed": true,
|
||||
* "density": "medium", // optionnel : low|medium|high
|
||||
* "coastalPointId": "uuid" // optionnel
|
||||
* }
|
||||
*/
|
||||
#[Route('', name: 'create', methods: ['POST'])]
|
||||
public function create(Request $request): JsonResponse
|
||||
{
|
||||
$data = json_decode($request->getContent(), true);
|
||||
|
||||
if (!is_array($data)) {
|
||||
return $this->json(['error' => 'Invalid JSON body'], 400);
|
||||
}
|
||||
|
||||
// Validation minimale
|
||||
if (!isset($data['lat'], $data['lng'], $data['hasSeaweed'])) {
|
||||
return $this->json(['error' => 'Missing required fields: lat, lng, hasSeaweed'], 422);
|
||||
}
|
||||
|
||||
$lat = filter_var($data['lat'], FILTER_VALIDATE_FLOAT);
|
||||
$lng = filter_var($data['lng'], FILTER_VALIDATE_FLOAT);
|
||||
|
||||
if ($lat === false || $lng === false) {
|
||||
return $this->json(['error' => 'lat and lng must be valid floats'], 422);
|
||||
}
|
||||
|
||||
if ($lat < -90 || $lat > 90 || $lng < -180 || $lng > 180) {
|
||||
return $this->json(['error' => 'lat/lng out of valid range'], 422);
|
||||
}
|
||||
|
||||
$feedback = new UserFeedback();
|
||||
$feedback->setLocation(sprintf('SRID=4326;POINT(%f %f)', $lng, $lat));
|
||||
$feedback->setHasSeaweed((bool) $data['hasSeaweed']);
|
||||
|
||||
if (isset($data['density']) && in_array($data['density'], ['low', 'medium', 'high'], true)) {
|
||||
$feedback->setDensity($data['density']);
|
||||
}
|
||||
|
||||
// Fingerprint anonyme : IP hachée (pas de données personnelles)
|
||||
$ip = $request->getClientIp() ?? 'unknown';
|
||||
$feedback->setSource(hash('sha256', $ip . date('Y-m-d')));
|
||||
|
||||
if (!empty($data['coastalPointId'])) {
|
||||
$spot = $this->em->getRepository(CoastalPoint::class)->find($data['coastalPointId']);
|
||||
if ($spot !== null) {
|
||||
$feedback->setCoastalPoint($spot);
|
||||
}
|
||||
}
|
||||
|
||||
$this->em->persist($feedback);
|
||||
$this->em->flush();
|
||||
|
||||
return $this->json(['id' => (string) $feedback->getId()], 201);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user