bundles/VehicleIngestBundle/Controller/CampaignController.php line 56

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace VehicleIngestBundle\Controller;
  4. use Pimcore\Controller\FrontendController;
  5. use Symfony\Component\HttpFoundation\Request;
  6. use Symfony\Component\HttpFoundation\Response;
  7. use Symfony\Component\Messenger\MessageBusInterface;
  8. use Symfony\Component\Routing\Annotation\Route;
  9. use VehicleIngestBundle\Message\IngestCampaignMessage;
  10. use VehicleIngestBundle\Service\ErrorResponseFactory;
  11. /**
  12.  * Accept-and-enqueue only, mirroring LeasingController: this controller's only job is to accept
  13.  * automedia's campaign push and hand it off to Messenger; the actual persistence
  14.  * (campaignNames/campaignName + the `Kampagne` facet) happens asynchronously in
  15.  * IngestCampaignMessageHandler - kept off the request path for the same reason as
  16.  * leasing/images (a slow/delayed campaign push must never block the automedia exporter's ack).
  17.  *
  18.  * ## Path and namespace
  19.  *
  20.  * Confirmed against automedia's integration guide ("Delivering campaigns"):
  21.  *
  22.  *     PUT /campaign-api/sellers/{sellerId}/ads/{adId}/campaigns
  23.  *
  24.  * Campaigns are not part of the mobile.de Seller API - its OpenAPI schema
  25.  * (Resources/openapi/mobile-seller-api.json) has no `campaigns` sub-resource at all - so they get
  26.  * a namespace of their own, deliberately separate from `/leasing-api` "so you can route on the
  27.  * prefix and implement one extension without the other". Everything under `/seller-api` is what
  28.  * mobile.de specifies and nothing else.
  29.  *
  30.  * The path was previously inferred (under /seller-api, mirroring the leasing sub-resource) from a
  31.  * single sample payload. It was wrong, and every campaign push would have 404ed.
  32.  *
  33.  * PUT is the only method: the body is the complete list, so every state including "no campaigns"
  34.  * is expressible, and there is nothing for a DELETE to do. The body is a JSON array of
  35.  * {id,name,validFrom,validUntil} objects.
  36.  *
  37.  * The same campaigns also arrive inside the ad body as the `campaignIds` and `campaign`
  38.  * attributes ("All three always carry the same content"), which AdFactory already accepts - this
  39.  * endpoint is the sub-resource variant, not the only channel.
  40.  */
  41. class CampaignController extends FrontendController
  42. {
  43.     public function __construct(
  44.         private MessageBusInterface $bus,
  45.         private ErrorResponseFactory $errors,
  46.     ) {
  47.     }
  48.     /**
  49.      * @Route("/campaign-api/sellers/{sellerId}/ads/{adId}/campaigns", name="campaign_api_ad_campaigns_put", methods={"PUT"}, requirements={"adId"="\d+"})
  50.      */
  51.     public function putCampaignsAction(Request $requeststring $adId): Response
  52.     {
  53.         $data json_decode($request->getContent(), true);
  54.         // Must decode to a JSON array (a list), not an object/scalar - the real payload is
  55.         // `[{id,name,...}, ...]`, not `{...}`. array_is_list([]) is true, so an empty array still
  56.         // passes, which matters: it is the body that means "no campaigns, clear what you hold"
  57.         // (see IngestCampaignMessageHandler).
  58.         if (!is_array($data) || !array_is_list($data)) {
  59.             return $this->errors->keyed('body');
  60.         }
  61.         $this->bus->dispatch(new IngestCampaignMessage($adId$data));
  62.         return new Response(''Response::HTTP_OK);
  63.     }
  64. }