src/Controller/ApiController.php line 282

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\Brand;
  4. use App\Entity\Company;
  5. use App\Entity\Photo;
  6. use App\Entity\Product;
  7. use App\Entity\Project;
  8. use App\Entity\Record;
  9. use App\Entity\Shop;
  10. use App\Message\ImportFileMessage;
  11. use App\Service\FileService;
  12. use App\Service\TokenService;
  13. use App\Service\ZpriceHelper;
  14. use Aws\S3\Exception\S3Exception;
  15. use Doctrine\ORM\EntityManagerInterface;
  16. use Doctrine\ORM\NonUniqueResultException;
  17. use Doctrine\ORM\NoResultException;
  18. use Doctrine\Persistence\ManagerRegistry;
  19. use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
  20. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  21. use Symfony\Component\HttpFoundation\JsonResponse;
  22. use Symfony\Component\HttpFoundation\Request;
  23. use Symfony\Component\HttpFoundation\Response;
  24. use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
  25. use Symfony\Component\HttpKernel\KernelInterface;
  26. use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
  27. use Symfony\Component\Mailer\MailerInterface;
  28. use Symfony\Component\Messenger\MessageBusInterface;
  29. use Symfony\Component\Mime\Email;
  30. use Symfony\Component\Routing\Annotation\Route;
  31. use Symfony\Component\Serializer\SerializerInterface;
  32. class ApiController extends AbstractController
  33. {
  34.     public const PHOTO_PRICE_TYPE 1;
  35.     public const PHOTO_PRODUCT_TYPE 2;
  36.     public const PHOTO_OTHER_TYPE 3;
  37.     /**
  38.      * @Route("/api/updateCatalog", methods={"GET"})
  39.      *
  40.      * @throws JWTDecodeFailureException
  41.      */
  42.     public function updateCatalogAction(
  43.         ManagerRegistry $doctrine,
  44.         Request $request,
  45.         ZpriceHelper $zpriceHelper,
  46.         SerializerInterface $serializer,
  47.         TokenService $tokenService,
  48.     ): Response {
  49.         set_time_limit(0);
  50.         ini_set('memory_limit''1024M');
  51.         $zpriceHelper->roleChecker($doctrine'ROLE_RELEVEUR');
  52.         $project $tokenService->getProjectFromToken();
  53.         $manager $doctrine->getManager();
  54.         $query $manager->createQuery(
  55.             "SELECT p.name as articleName, b.name as brandName, p.ean, p.photo as image, p.photomini as imagemini
  56.             FROM App\Entity\Product as p
  57.             JOIN p.brand b
  58.             WHERE p.project = :project
  59.                 AND p.inCatalog = true"
  60.         );
  61.         $query->setParameter('project'$project);
  62.         $jsons = [];
  63.         $jsons[] = '"products":'.$serializer->serialize($query->getResult(), 'json');
  64.         $finalJson '{'.implode(','$jsons).'}';
  65.         $publicCsvDir 'tempcsv'
  66.             .DIRECTORY_SEPARATOR.md5($this->getUser()->getUserIdentifier().'zmpriceseeddir');
  67.         $tempCsvDir $this->getParameter('kernel.project_dir')
  68.             .DIRECTORY_SEPARATOR.'public'
  69.             .DIRECTORY_SEPARATOR.$publicCsvDir;
  70.         $filename md5($project->getName().time().'zmpriceseedfilename').'.json';
  71.         if (!is_dir($tempCsvDir)) {
  72.             mkdir($tempCsvDir0777true);
  73.         } else {
  74.             if (file_exists($tempCsvDir.DIRECTORY_SEPARATOR.$filename)) {
  75.                 unlink($tempCsvDir.DIRECTORY_SEPARATOR.$filename);
  76.             }
  77.         }
  78.         file_put_contents($tempCsvDir.DIRECTORY_SEPARATOR.$filename$finalJson);
  79.         return new JsonResponse([
  80.             'fileUrl' => $request->getSchemeAndHttpHost().'/'.$publicCsvDir.'/'.$filename,
  81.             'contentLength' => strlen($finalJson),
  82.         ]);
  83.     }
  84.     /**
  85.      * @throws \Exception
  86.      *
  87.      * @Route("api/pushRecord", methods={"POST"})
  88.      */
  89.     public function pushRecordAction(
  90.         ManagerRegistry $doctrine,
  91.         Request $request,
  92.         ZpriceHelper $zpriceHelper,
  93.         KernelInterface $kernel,
  94.     ): JsonResponse {
  95.         $zpriceHelper->roleChecker($doctrine'ROLE_RELEVEUR');
  96.         $content json_decode($request->getContent());
  97.         $contentHash md5($request->getContent());
  98.         if (!$content) {
  99.             throw $this->createNotFoundException('Unexpected data');
  100.         }
  101.         if (!empty($content->projectId)) {
  102.             $project $doctrine->getRepository(Project::class)->find($content->projectId);
  103.         }
  104.         if (empty($project)) {
  105.             throw $this->createNotFoundException('Project not found');
  106.         }
  107.         $existingRecord $doctrine->getRepository(Record::class)->findOneBy(['contentHash' => $contentHash]);
  108.         if ($existingRecord) {
  109.             return new JsonResponse(['success' => 'record already synced']);
  110.         }
  111.         if (!empty($content->shop->placeId)) {
  112.             $shop $doctrine->getRepository(Shop::class)->findOneBy([
  113.                 'placeId' => 'places/'.$content->shop->placeId]);
  114.             if (!$shop) {
  115.                 $shop = new Shop();
  116.                 $company $doctrine->getRepository(Company::class)->findOneBy(['name' => $content->shop->company]);
  117.                 if (!$company) {
  118.                     throw $this->createNotFoundException('Company not found');
  119.                 }
  120.                 $curl curl_init();
  121.                 curl_setopt_array($curl, [
  122.                     CURLOPT_URL => 'https://places.googleapis.com/v1/places/'
  123.                         .$content->shop->placeId,
  124.                     CURLOPT_RETURNTRANSFER => true,
  125.                     CURLOPT_ENCODING => '',
  126.                     CURLOPT_MAXREDIRS => 10,
  127.                     CURLOPT_TIMEOUT => 0,
  128.                     CURLOPT_FOLLOWLOCATION => true,
  129.                     CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  130.                     CURLOPT_CUSTOMREQUEST => 'GET',
  131.                     CURLOPT_HTTPHEADER => [
  132.                         'Content-Type: application/json',
  133.                         'X-Goog-Api-Key: '.$_ENV['GOOGLE_PLACES_API_KEY'],
  134.                         'X-Goog-FieldMask: name,formattedAddress,addressComponents,location,displayName,types',
  135.                     ],
  136.                 ]);
  137.                 $response curl_exec($curl);
  138.                 curl_close($curl);
  139.                 $gApiResponse json_decode($response);
  140.                 $shop->setCompany($company)
  141.                     ->setName($gApiResponse->displayName->text)
  142.                     ->setCity($this->getGApiAddressInfo($gApiResponse'locality'))
  143.                     ->setPostcode($this->getGApiAddressInfo($gApiResponse'postal_code'))
  144.                     ->setAddress($gApiResponse->formattedAddress)
  145.                     ->setLatitude($gApiResponse->location->latitude)
  146.                     ->setLongitude($gApiResponse->location->longitude)
  147.                     ->setSecteur(implode(','$gApiResponse->types))
  148.                     ->setPlaceId($gApiResponse->name);
  149.                 $doctrine->getManager()->persist($shop);
  150.             }
  151.         } else {
  152.             throw $this->createNotFoundException('Shop details missing');
  153.         }
  154.         if (!empty($content->product->ean)) {
  155.             $product $doctrine
  156.                 ->getRepository(Product::class)
  157.                 ->findOneBy(['ean' => $content->product->ean'project' => $project->getId()]);
  158.             if (!$product) {
  159.                 $zpriceHelper->roleChecker($doctrine'ROLE_RELEVEUR');
  160.                 $product = new Product();
  161.                 $product->setEan($content->product->ean)
  162.                     ->setProject($project)
  163.                     ->setInCatalog(false)
  164.                     ->setLastUpdate(new \DateTime('now'));
  165.                 if (!empty($content->product->brand)) {
  166.                     $brand $doctrine->getRepository(Brand::class)->find($content->product->brand);
  167.                     if (!$brand) {
  168.                         $brand $doctrine
  169.                             ->getRepository(Brand::class)
  170.                             ->findOneBy(['name' => $content->product->brand]);
  171.                     }
  172.                 } else {
  173.                     $brand $doctrine->getRepository(Brand::class)->findOneBy(['name' => 'Inconnue']);
  174.                     if (!$brand) {
  175.                         $brand = new Brand();
  176.                         $brand->setName('Inconnue');
  177.                         $brand->setLogo('');
  178.                     }
  179.                 }
  180.                 $product->setBrand($brand);
  181.                 if (!empty($content->product->name)) {
  182.                     $product->setName($content->product->name);
  183.                 }
  184.             }
  185.         } else {
  186.             throw $this->createNotFoundException('EAN missing');
  187.         }
  188.         $record = new Record();
  189.         $record->setProduct($product)
  190.             ->setRecordTime(\DateTimeImmutable::createFromFormat('Y-m-d H:i:s'$content->recordTime))
  191.             ->setSyncTime(new \DateTimeImmutable('now'))
  192.             ->setPrice($content->price)
  193.             ->setShop($shop)
  194.             ->setContentHash($contentHash)
  195.             ->setUser($this->getUser());
  196.         if (!empty($content->photos)) {
  197.             foreach ($content->photos as $photo) {
  198.                 try {
  199.                     $storedFile $zpriceHelper->storePhoto($photo);
  200.                     $directory $storedFile['directory'];
  201.                     $filename $storedFile['filename'];
  202.                     $photoUrl $storedFile['url'];
  203.                     if ($photoUrl) {
  204.                         $photoObj = new Photo();
  205.                         $photoObj->setFilename($directory.'/'.$filename);
  206.                         $photoObj->setPublicurl($photoUrl);
  207.                         $photoObj->setType($photo->type);
  208.                         $photoObj->setStockage('s3');
  209.                         switch ($photo->type) {
  210.                             case self::PHOTO_PRODUCT_TYPE:
  211.                                 $product->setPhoto($photoUrl);
  212.                                 break;
  213.                             default:
  214.                                 $record->addPhoto($photoObj);
  215.                                 break;
  216.                         }
  217.                     }
  218.                 } catch (S3Exception $e) {
  219.                     throw new \Exception('Image storage Exception : '.$e->getMessage());
  220.                 }
  221.             }
  222.         }
  223.         $manager $doctrine->getManager();
  224.         $manager->persist($record);
  225.         $manager->flush();
  226.         return new JsonResponse(['success' => 'record sync successful']);
  227.     }
  228.     public function getGApiAddressInfo($gApiResponse$type): string
  229.     {
  230.         foreach ($gApiResponse->addressComponents as $val) {
  231.             if (in_array($type$val->types)) {
  232.                 return $val->longText;
  233.             }
  234.         }
  235.         return '';
  236.     }
  237.     /**
  238.      * @throws NoResultException
  239.      * @throws NonUniqueResultException
  240.      * @throws TransportExceptionInterface|JWTDecodeFailureException
  241.      *
  242.      * @Route("/api/exportCSV", methods={"GET"})
  243.      */
  244.     public function exportCSVAction(
  245.         Request $request,
  246.         EntityManagerInterface $entityManager,
  247.         ZpriceHelper $zpriceHelper,
  248.         MailerInterface $mailer,
  249.     ): Response {
  250.         $user $this->getUser();
  251.         $dateStartStr $request->query->get('dateStart');
  252.         $dateEndStr $request->query->get('dateEnd');
  253.         $dateStart \DateTime::createFromFormat('Y-m-d H:i:s'$dateStartStr.' 00:00:00');
  254.         $dateEnd \DateTime::createFromFormat('Y-m-d H:i:s'$dateEndStr.' 23:59:59');
  255.         $query $entityManager
  256.             ->createQuery("
  257.                 SELECT 
  258.                     r.id, 
  259.                     u.email, 
  260.                     p.ean, 
  261.                     r.recordTime,
  262.                      s.name as shopname, 
  263.                      s.city as city, 
  264.                      s.postcode as postcode,
  265.                      r.price, 
  266.                      ph.publicurl as photo,
  267.                       p.name, 
  268.                       b.name as brand
  269.                 FROM App\Entity\Record r
  270.                 JOIN r.shop s
  271.                 JOIN r.product p
  272.                 JOIN r.photos ph
  273.                 JOIN r.user u
  274.                 JOIN p.brand b
  275.                 WHERE p.project = :projectId 
  276.                     AND r.recordTime >= :dateStart 
  277.                     AND r.recordTime <= :dateEnd 
  278.                     AND r.user = :user
  279.                     AND ph.type = 1
  280.             ")
  281.             ->setParameter('projectId'$zpriceHelper->getCurrentProject())
  282.             ->setParameter('dateStart'$dateStart)
  283.             ->setParameter('dateEnd'$dateEnd)
  284.             ->setParameter('user'$user);
  285.         $records $query->getResult();
  286.         $tempCsvDir $this->getParameter('kernel.project_dir').DIRECTORY_SEPARATOR
  287.             .'var'.DIRECTORY_SEPARATOR.'tempcsv';
  288.         if (!is_dir($tempCsvDir)) {
  289.             mkdir($tempCsvDir);
  290.         }
  291.         $now = new \DateTime('now');
  292.         $filename 'export-u'.$user->getId().'-'.$now->format('hmsdmy').'.csv';
  293.         $csvHandle fopen($tempCsvDir.DIRECTORY_SEPARATOR.$filename'w');
  294.         $headerList = [
  295.             'RECORD_ID',
  296.             'EAN',
  297.             'PRODUCT_NAME',
  298.             'VARIATION_NAME',
  299.             'BRAND',
  300.             'PRICE',
  301.             'SHOP',
  302.             'CITY',
  303.             'POSTCODE',
  304.             'DATE',
  305.             'EMAIL',
  306.             'RECORD_IMG',
  307.         ];
  308.         fputcsv($csvHandle$headerList';');
  309.         foreach ($records as $record) {
  310.             $names explode(' | '$record['name']);
  311.             $values = [];
  312.             $values[] = $record['id'];
  313.             $values[] = $record['ean'];
  314.             $values[] = $names[0];
  315.             $values[] = count($names) > $names[1] : '';
  316.             $values[] = $record['brand'];
  317.             $values[] = $this->formatEuro($record['price']);
  318.             $values[] = $record['shopname'];
  319.             $values[] = $record['city'];
  320.             $values[] = $record['postcode'];
  321.             $values[] = $record['recordTime']->format('d-m-Y');
  322.             $values[] = $record['email'];
  323.             $values[] = $record['photo'];
  324.             fputcsv($csvHandle$values';');
  325.         }
  326.         fclose($csvHandle);
  327.         $queryRecordedShop $entityManager
  328.             ->createQuery("
  329.                 SELECT COUNT(DISTINCT s.id) as shopCount
  330.                 FROM App\Entity\Record r
  331.                 JOIN r.shop s
  332.                 JOIN r.product p
  333.                 JOIN r.photos ph
  334.                 JOIN r.user u
  335.                 JOIN p.brand b
  336.                 WHERE p.project = :projectId 
  337.                     AND r.recordTime >= :dateStart 
  338.                     AND r.recordTime <= :dateEnd 
  339.                     AND r.user = :user
  340.                     AND ph.type = 1
  341.             ")
  342.             ->setParameter('projectId'$zpriceHelper->getCurrentProject())
  343.             ->setParameter('dateStart'$dateStart)
  344.             ->setParameter('dateEnd'$dateEnd)
  345.             ->setParameter('user'$user);
  346.         $shopCount $queryRecordedShop->getSingleScalarResult();
  347.         $email = (new Email())
  348.             ->from('[email protected]')
  349.             ->to($user->getUserIdentifier())
  350.             ->subject('ZPriceAuditor: Relevés du '.$dateStart->format('d/m/Y').' au '.$dateEnd->format('d/m/Y'))
  351.             ->html(
  352.                 '<h2>Votre relevé</h2>Vous trouverez en pièce jointe votre relevé pour la période du '
  353.                 .$dateStart->format('d/m/Y').' au '.$dateEnd->format('d/m/Y').' contenant '
  354.                 .count($records)." relevés pour $shopCount magasins"
  355.             )
  356.             ->attachFromPath($tempCsvDir.DIRECTORY_SEPARATOR.$filename);
  357.         $mailer->send($email);
  358.         unlink($tempCsvDir.DIRECTORY_SEPARATOR.$filename);
  359.         return new JsonResponse(['records' => count($records), 'recordedShops' => $shopCount]);
  360.     }
  361.     /**
  362.      * @Route("api/pushCatalog")
  363.      *
  364.      * @throws \Exception
  365.      */
  366.     public function pushCatalogAction(
  367.         Request $request,
  368.         ZpriceHelper $zpriceHelper,
  369.         ManagerRegistry $doctrine,
  370.         MessageBusInterface $messageBus,
  371.         FileService $fileService,
  372.     ): JsonResponse {
  373.         $zpriceHelper->roleChecker($doctrine'ROLE_ADMIN');
  374.         $fileService->tempcsvPurge();
  375.         $catalog $request->get('catalog');
  376.         if (!empty($catalog)) {
  377.             $tempdir $this->getParameter('kernel.project_dir').
  378.                 DIRECTORY_SEPARATOR.'var'.DIRECTORY_SEPARATOR.'tempcsv';
  379.             if (!is_dir($tempdir)) {
  380.                 mkdir($tempdir0777true);
  381.             }
  382.             $filename rand(300000000900000000000).'.csv';
  383.             $filepath $tempdir.DIRECTORY_SEPARATOR.$filename;
  384.             file_put_contents($filepath$catalog);
  385.             $msgBusReturn $messageBus->dispatch(
  386.                 new ImportFileMessage(
  387.                     $filepath,
  388.                     $zpriceHelper->getCurrentProject()->getId()
  389.                 )
  390.             );
  391.             /*
  392.              * @todo notif de complétion ou d'erreur ?
  393.              */
  394.             return new JsonResponse(['success' => 'Import will run in background -- '.$filepath]);
  395.         } else {
  396.             throw new NotAcceptableHttpException('Missing datas');
  397.         }
  398.     }
  399.     /**
  400.      * @Route("api/getPlaces")
  401.      *
  402.      * @throws JWTDecodeFailureException
  403.      */
  404.     public function getPlaces(
  405.         ManagerRegistry $doctrine,
  406.         Request $request,
  407.         ZpriceHelper $zpriceHelper,
  408.         KernelInterface $kernel,
  409.     ): JsonResponse {
  410.         $zpriceHelper->roleChecker($doctrine'ROLE_RELEVEUR');
  411.         $company $request->query->get('company');
  412.         $latitude $request->query->get('latitude');
  413.         $longitude $request->query->get('longitude');
  414.         if (empty($company) || empty($latitude) || empty($longitude)) {
  415.             throw $this->createAccessDeniedException();
  416.         }
  417.         $curl curl_init();
  418.         curl_setopt_array($curl, [
  419.             CURLOPT_URL => 'https://places.googleapis.com/v1/places:searchText',
  420.             CURLOPT_RETURNTRANSFER => true,
  421.             CURLOPT_ENCODING => '',
  422.             CURLOPT_MAXREDIRS => 10,
  423.             CURLOPT_TIMEOUT => 0,
  424.             CURLOPT_FOLLOWLOCATION => true,
  425.             CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
  426.             CURLOPT_CUSTOMREQUEST => 'POST',
  427.             CURLOPT_POSTFIELDS => '{
  428.                   "textQuery": "'.$company.'",
  429.                   "languageCode": "fr",
  430.                   "locationBias": {
  431.                     "circle" : {
  432.                         "center" : {
  433.                             "latitude": "'.$latitude.'",
  434.                             "longitude": "'.$longitude.'"
  435.                         }
  436.                     }
  437.                   }
  438.                 }',
  439.             CURLOPT_HTTPHEADER => [
  440.                 'Content-Type: application/json',
  441.                 'X-Goog-Api-Key: '.$_ENV['GOOGLE_PLACES_API_KEY'],
  442.                 'X-Goog-FieldMask: places.id,places.displayName,places.formattedAddress,places.location',
  443.             ],
  444.         ]);
  445.         $response json_decode(curl_exec($curl), true);
  446.         foreach ($response['places'] as $key => $place) {
  447.             $place['distance'] = number_format($zpriceHelper->getDistance(
  448.                 $place['location']['latitude'],
  449.                 $place['location']['longitude'],
  450.                 $latitude,
  451.                 $longitude
  452.             ), 2);
  453.             $response['places'][$key] = $place;
  454.             if ($place['distance'] > 30) {
  455.                 unset($response['places'][$key]);
  456.             }
  457.         }
  458.         usort($response['places'], function ($a$b) {
  459.             return $a['distance'] <=> $b['distance'];
  460.         });
  461.         return new JsonResponse($response);
  462.     }
  463.     public function formatEuro(float $amount): string
  464.     {
  465.         $formatter = new \NumberFormatter('fr_FR'\NumberFormatter::CURRENCY);
  466.         return $formatter->formatCurrency($amount'EUR');
  467.     }
  468. }