<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use App\Entity\Menu;
use App\Entity\User;
use App\Entity\UserToken;
use App\Entity\Notification;
use App\Entity\MassiveNotification;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use App\Service\AppApiService;
use App\Service\FirebaseService;
use App\Service\NotificationService;
use App\Entity\AppPersonalAccount;
use App\Entity\MediaObject;
use App\Entity\AppCounterReading;
use App\Entity\DataReception;
use App\Entity\Accounts;
use App\Entity\ServiceOrderHistory;
use Symfony\Component\HttpKernel\KernelInterface;
use Psr\Log\LoggerInterface;
use Sentry;
use App\Repository\NotificationRepository;
use App\Message\NotificationMessage;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpStamp;
use Symfony\Component\Messenger\Stamp\SentStamp;
use Symfony\Component\Messenger\Exception\TransportExceptionInterface;
use Symfony\Component\Security\Core\Security;
class MassiveNotificationController extends AbstractController
{
public $datetime;
public $entityManager;
public $passwordEncoder;
public $appApiService;
public $firebaseService;
public $notificationService;
public $notifications;
private $kernel;
private LoggerInterface $logger;
private $bus;
private Security $security;
public function __construct(
?\DateTimeInterface $datetime,
EntityManagerInterface $entityManager,
AppApiService $appApiService,
KernelInterface $kernel,
FirebaseService $firebaseService,
NotificationService $notificationService,
LoggerInterface $logger,
NotificationRepository $notifications,
MessageBusInterface $bus,
Security $security
){
$this->datetime = $datetime;
$this->entityManager = $entityManager;
$this->appApiService = $appApiService;
$this->firebaseService = $firebaseService;
$this->notificationService = $notificationService;
$this->kernel = $kernel;
$this->logger = $logger;
$this->notifications = $notifications;
$this->bus = $bus;
$this->security = $security;
}
public function valid($requestFilds, $requiredFilds){
$errors = '';
foreach($requiredFilds as $f){
if(!isset($requestFilds[$f]))
$errors .= " $f";
}
return $errors;
}
private function customAuth($request){
$validUser = $_ENV['MY_API_USER'];
$validPassword = $_ENV['MY_API_PASSWORD'];
$login = $request->server->get('PHP_AUTH_USER');
$password = $request->server->get('PHP_AUTH_PW');
if ($login !== $validUser || $password !== $validPassword) {
return false;
}
return true;
}
// #[Route('/api/massive/notification-create', name: 'massive_create_notfication', methods: ['POST'])]
// public function massive_create_notfication(Request $request)
// {
// $user = $this->security->getUser();
// $data = json_decode($request->getContent(), true);
// // if( $this->)
// echo '<pre>'; print_r($data);
// $massiveNotification = new \App\Entity\MassiveNotification();
// $massiveNotification->setName('Масове сповіщення '.date('Y-m-d H:i:s'));
// $massiveNotification->setTitle($data['title']);
// $massiveNotification->setBody($data['body']);
// $massiveNotification->setType('i'));
// echo 'ok'; die;
// }
private function searchIdByToken( $targetToken, $data) {
$foundId = null;
foreach ($data as $id => $tokens) { // $data = твій великий Array
if (!is_array($tokens)) continue;
if (in_array($targetToken, $tokens, true)) { // true = строгe порівняння
$foundId = $id;
break;
}
}
return $foundId;
}
#[Route('/api/send-notfication-masive', name: 'send_notfication_masive', methods: ['GET'])]
public function send_notfication_masive(Request $request)
{
$send = 0;
$err = 0;
/** Масовані розсилки розділені ns_msg_id, бо в них однакові тексти, тому шукаю перший ліпший і потім всі інші по ns_msg_id */
$MassiveNotification = $this->entityManager->getRepository(MassiveNotification::class)->findOneBy(['status' => 'new']);
// $firstNotification = $this->entityManager->getRepository(Notification::class)->findOneBy(['status' => 'new_all', 'allUsers' => true, 'massiveNotification' => $MassiveNotification]);
if($MassiveNotification){
file_put_contents('/var/www/symfony_docker/var/log/MassiveNotification.log', $MassiveNotification->getId() . " NOTIFICATIONS Massive ************************" . date('H:i:s') . "\n", FILE_APPEND);
$notifications = $this->entityManager->getRepository(Notification::class)->findBy(['status' => 'new_all', 'massiveNotification' => $MassiveNotification]);
if(count($notifications) == 0){
file_put_contents('/var/www/symfony_docker/var/log/MassiveNotification.log', $MassiveNotification->getId() . date('H:i:s') . "Немає нових сповіщень для розсилки\n", FILE_APPEND);
$MassiveNotification->setStatus('error');
$MassiveNotification->setDescription('Немає нових сповіщень для розсилки');
$this->entityManager->persist($MassiveNotification);
$this->entityManager->flush();
$response = new JsonResponse();
$response->setData(['send' => 0, 'err' => 0, 'error' => 'Немає нових сповіщень для розсилки']);
return $response;
}
file_put_contents('/var/www/symfony_docker/var/log/MassiveNotification.log', "MassiveNotification ID: " . $MassiveNotification->getId() . date('H:i:s') . " Кількість сповіщень: " . count($notifications) . "\n", FILE_APPEND);
// echo count($notifications);
// die;
$unikTokensNotificationsId = [];
$unikTokens = [];
if($notifications){
// Витягуємо всі ID
$notificationIds = array_map(fn($n) => $n->getId(), $notifications);
// Масове оновлення статусу та дати
$this->entityManager->createQuery(
'UPDATE App\Entity\Notification n
SET n.status = :newStatus,
n.dateSend = :now
WHERE n.id IN (:ids)'
)
->setParameter('newStatus', 'in_progress')
->setParameter('now', new \DateTime())
->setParameter('ids', $notificationIds)
->execute();
file_put_contents('/var/www/symfony_docker/var/log/MassiveNotification.log', "MassiveNotification ID: " . $MassiveNotification->getId() . date('H:i:s') . " Поставили статус in_progress: " . count($notificationIds) . "\n", FILE_APPEND);
/** Шукаю ключі Fcm */
foreach($notifications as $notification){
$send = 0;
$user = $notification->getUsers();
// $tokensArr = [];
if($user){
$device_id = $user->getDeviceId();
$tokens = $this->entityManager->getRepository(UserToken::class)->findBy(['users' => $user, 'active' => true]);
if(empty($tokens)){
$notification->setStatus('not_token');
$notification->setError($notification->getError() . "\n" . date('Y-m-d H:i:s') . "Не вказані токени користувача");
$this->entityManager->persist($notification);
continue;
}
$search_dublicate_tokens = [];
foreach($tokens as $token){
if(empty($token->getTokenFcm())){
$token->setActive(false);
$this->entityManager->persist($token);
continue;
}
$unikTokensNotificationsId[$notification->getId()][]= $token->getTokenFcm();
$unikTokens[]= $token->getTokenFcm();
// file_put_contents('/var/www/symfony_docker/var/log/errorrrrrr.log', "st - " . date('H:i:s') . "\n", FILE_APPEND);
}
}else{
$notification->setStatus('error');
$notification->setError($notification->getError() . "\n" . date('Y-m-d H:i:s') . "Користувач не знайдений");
$this->entityManager->persist($notification);
continue;
}
}
$this->entityManager->flush();
}else{
$rez = ['rez' => false, 'error' => "Незнайдено нових сповіщень!"];
}
/** Беру тільки унікальні ключі */
$unikTokens = array_unique($unikTokens) ;
if(count($unikTokens) > 0){
file_put_contents('/var/www/symfony_docker/var/log/MassiveNotification.log', "MassiveNotification ID: " . $MassiveNotification->getId() . date('H:i:s') . " Унікальних токенів для розсилки: " . count($unikTokens) . "\n", FILE_APPEND);
/** Розділяю по чергах 450 бо там ліміт 500*/
$chunks = array_chunk($unikTokens, 450);
/** йду почергах */
foreach($chunks as $key => $chunkToken){
file_put_contents('/var/www/symfony_docker/var/log/MassiveNotification.log', "MassiveNotification ID: " . $MassiveNotification->getId() . date('H:i:s') . " Обробка черги номер: " . ($key + 1) . "\n", FILE_APPEND);
$resultJson = $this->firebaseService->massiveTokensSendCurl([
'tokens' => $chunkToken,
'title' => $MassiveNotification->getTitle(),
'body' => $MassiveNotification->getBody(),
'type' => 'text',
]);
// file_put_contents('/var/www/symfony_docker/var/log/MassiveNotification.log', "MassiveNotification ID: " . $MassiveNotification->getId() . date('H:i:s') . " Результат розсилки: " . $resultJson . "\n", FILE_APPEND);
$results = json_decode($resultJson, true);
/** Обробка результатів */
$errorsIds = [];
$successIds = [];
foreach($results['Responses'] as $i => $res){
/** Шукаю id всіх відправлених і дані всіх не відправлених */
if(!$res['Success']){
$t = $chunkToken[$i];
$idN = $this->searchIdByToken($t, $unikTokensNotificationsId);
$errorsIds[] = $idN;
$errors[$idN] = [
'id_notification' => $idN,
'token' => $t,
'error' => $res['Error']
];
}else{
$t = $chunkToken[$i];
$idN = $this->searchIdByToken($t, $unikTokensNotificationsId);
$successIds[] = $idN;
}
}
if(count($successIds) > 0){
/** Оновлю всі відправленні */
// Масове оновлення статусу та дати
$this->entityManager->createQuery(
'UPDATE App\Entity\Notification n
SET n.status = :newStatus
WHERE n.id IN (:ids)'
)
->setParameter('newStatus', 'send')
->setParameter('ids', $successIds)
->execute();
}
if(count($errorsIds) > 0){
/** Обробляю всі з помилками */
foreach($notifications as $notification){
if(in_array($notification->getId(), $errorsIds)){
$notification->setStatus('error');
$errorInfo = $errors[$notification->getId()];
$notification->setError($notification->getError() . "\n" . date('Y-m-d H:i:s') . $errorInfo['token'] . "\n" . $errorInfo['error']);
$this->entityManager->persist($notification);
}
}
$this->entityManager->flush();
}
file_put_contents('/var/www/symfony_docker/var/log/MassiveNotification.log', "MassiveNotification ID: " . $MassiveNotification->getId() . date('H:i:s') . " Черга номер: " . ($key + 1) . " Відправлено: " . count($successIds) . " Помилок: " . count($errorsIds) . "\n", FILE_APPEND);
}
/** Оновлюю статус масового сповіщення */
$MassiveNotification->setStatus('send');
$MassiveNotification->setDescription('Розсилка завершена. Відправлено ' . count($successIds) . ' сповіщень, з помилками ' . count($errorsIds));
$this->entityManager->persist($MassiveNotification);
$this->entityManager->flush();
file_put_contents('/var/www/symfony_docker/var/log/MassiveNotification.log', "MassiveNotification ID: " . $MassiveNotification->getId() . date('H:i:s') . " Розсилка завершена. Відправлено: " . count($successIds) . " Помилок: " . count($errorsIds) . "\n", FILE_APPEND);
}else{
$MassiveNotification->setStatus('error');
$MassiveNotification->setDescription('Немає токенів для розсилки');
$this->entityManager->persist($MassiveNotification);
$this->entityManager->flush();
}
$response = new JsonResponse();
$response->setData(['send' => count($successIds), 'err' => count($errorsIds)]);
file_put_contents('/var/www/symfony_docker/var/log/errorrrrrr.log', "***********END*************" . date('H:i:s') . "\n", FILE_APPEND);
return $response;
}else{
$response = new JsonResponse();
$response->setData(['send' => 0, 'err' => 0, 'error' => 'Нема масових сповіщень']);
return $response;
}
}
/**
* шукаю сповіщення які зависли в in_progress
*/
#[Route('/api/check-notfication-masive', name: 'check_notfication_masive', methods: ['GET'])]
public function check_notfication_masive(Request $request)
{
$now = new \DateTime();
$interval = new \DateInterval('PT5M'); // 5 хвилин
$thresholdTime = (clone $now)->sub($interval);
/** шукаю масове сповіщення яке в процесі */
$notification = $this->entityManager->getRepository(Notification::class)->findOneBy(['status' => 'in_progress', 'allUsers' => true]);
if(! $notification) {
$response = new JsonResponse();
$response->setData(['status' => 'No in_progress notifications found']);
return $response;
}
/** Перевіряю чи дата відправки більше ніж 5 хвилин тому */
if($notification->getDateSend() > $thresholdTime) {
$response = new JsonResponse();
$response->setData(['status' => 'No stuck notifications found']);
return $response;
}
/** Оновлюю статус масового сповіщення */
$notificationMassive = $notification->getMassiveNotification();
$notificationMassive->setStatus('new');
// $notifications = $this->entityManager->getRepository(Notification::class)->findBy(['status' => 'in_progress']);
$notifications = $this->entityManager->getRepository(Notification::class)->findBy(['status' => 'in_progress', 'massiveNotification' => $notificationMassive]);
if($notifications){
// Витягуємо всі ID
$notificationIds = array_map(fn($n) => $n->getId(), $notifications);
// Масове оновлення статусу та дати
$this->entityManager->createQuery(
'UPDATE App\Entity\Notification n
SET n.status = :newStatus,
n.dateSend = :now,
n.error = CONCAT(n.error, :now_str)
WHERE n.id IN (:ids)'
)
->setParameter('newStatus', 'new_all')
->setParameter('now', new \DateTime())
->setParameter('ids', $notificationIds)
->setParameter('now_str', "\n" . date('Y-m-d H:i:s') . " - Перевідправлено через зависання")
->execute();
}
$this->entityManager->persist($notificationMassive);
$this->entityManager->flush();
$response = new JsonResponse();
$response->setData(['status' => 'Check completed']);
return $response;
}
/** Додавання масових сповіщень в чергу RABBIT з статусом "new_all" */
#[Route('/api/add-notification-massive-in-queue', name: 'add_notification_massive_in_queue', methods: ['GET'])]
public function add_notification_massive_in_queue(Request $request)
{
/** Перевірка чи не запущено крон */
if(file_exists('/var/www/symfony_docker/var/log/notificationMassiveCron.log')){
$logCron = file_get_contents('/var/www/symfony_docker/var/log/notificationMassiveCron.log');
if(!empty( $logCron )){
$fileContents = json_decode($logCron, true);
$lastRunTime = isset($fileContents['start']) ? strtotime($fileContents['start']) : 0;
$currentTime = time();
if( $fileContents['cron'] == 'Start' ){
// Якщо останній запуск був менше ніж 5 хвилин тому, завершуємо виконання
echo "$currentTime - $lastRunTime = " . ($currentTime - $lastRunTime);
if (($currentTime - $lastRunTime) > 300) {
$logCron = json_encode([
'cron' => 'Stop',
'stop' => date('Y-m-d H:i:s'),
'info' => 'The cron job was run less than 5 minutes ago. Exiting to prevent overlap.'
]);
file_put_contents('/var/www/symfony_docker/var/log/notificationMassiveCron.log', $logCron);
}
var_dump($logCron);
die;
}
}
}
file_put_contents('/var/www/symfony_docker/var/log/notificationMassiveCron.log', json_encode([
'cron' => 'Start',
'start' => date('Y-m-d H:i:s'),
'info' => 'Start notification cron'
]));
// file_put_contents('/var/www/symfony_docker/var/log/notificationCron.log');
$send = 0;
$err = 0;
/** Масовані розсилки розділені ns_msg_id, бо в них однакові тексти, тому шукаю перший ліпший і потім всі інші по ns_msg_id */
$MassiveNotification = $this->entityManager->getRepository(MassiveNotification::class)->findOneBy(['status' => 'new']);
// $firstNotification = $this->entityManager->getRepository(Notification::class)->findOneBy(['status' => 'new_all', 'allUsers' => true, 'massiveNotification' => $MassiveNotification]);
if($MassiveNotification){
file_put_contents('/var/www/symfony_docker/var/log/notificationMassiveAddInQueue.log', $MassiveNotification->getId() . " NOTIFICATIONS Massive ************************" . date('H:i:s') . "\n", FILE_APPEND);
$notifications = $this->entityManager->createQueryBuilder()
->select('n.id')
->from(Notification::class, 'n')
->where('n.status = :status')
->andWhere('n.massiveNotification = :mn')
->setParameter('status', 'new_all')
->setParameter('mn', $MassiveNotification)
->getQuery()
->getSingleColumnResult();
if(count($notifications) == 0){
file_put_contents('/var/www/symfony_docker/var/log/notificationMassiveAddInQueue.log', $MassiveNotification->getId() . date('H:i:s') . "Немає нових сповіщень для розсилки\n", FILE_APPEND);
$MassiveNotification->setStatus('send');
$MassiveNotification->setDescription('Немає нових сповіщень для розсилки');
$this->entityManager->persist($MassiveNotification);
$this->entityManager->flush();
// $logCron = json_encode([
// 'cron' => 'Stop',
// 'stop' => date('Y-m-d H:i:s'),
// 'info' => 'Notification cron complete'
// ]);
// file_put_contents('/var/www/symfony_docker/var/log/notificationMassiveCron.log', $logCron);
$response = new JsonResponse();
$response->setData(['send' => 0, 'err' => 0, 'error' => 'Немає нових сповіщень для розсилки']);
return $response;
}
file_put_contents('/var/www/symfony_docker/var/log/notificationMassiveAddInQueue.log', "MassiveNotification ID: " . $MassiveNotification->getId() . date('H:i:s') . " Кількість сповіщень: " . count($notifications) . "\n", FILE_APPEND);
// $notifications =
$notificationsChunk = array_chunk($notifications, 1000);
// echo '<pre>';
foreach($notificationsChunk as $notification){
$send = 0;
$priority = 1;
file_put_contents('/var/www/symfony_docker/var/log/notificationMassiveAddInQueue.log', "#{$MassiveNotification->getId()} add to 1000items rabbitmq - " . json_encode($notification) . date('H:i:s.u') . "\n", FILE_APPEND);
$queued = false;
$error = null;
try {
// print_r($notification);
$env = $this->bus->dispatch(new NotificationMessage($MassiveNotification->getId(), $notification, 'send-notification-massive', $priority));
// Якщо повідомлення надіслано у транспорт — з’являться SentStamp-и
$sentStamps = $env->all(SentStamp::class);
if (!empty($sentStamps)) {
$send++;
file_put_contents('/var/www/symfony_docker/var/log/notificationMassiveAddInQueue.log', "#{$MassiveNotification->getId()} 1000items rabbitmq-success " . date('H:i:s.u') . "\n", FILE_APPEND);
$queued = true;
// для логів можна подивитись куди саме:
$targets = array_map(fn(SentStamp $s) => $s->getSenderAlias() ?? $s->getTransportName(), $sentStamps);
file_put_contents('/var/www/symfony_docker/var/log/notificationMassiveAddInQueue.log', 'Sent to: '.implode(', ', $targets).PHP_EOL, FILE_APPEND);
} else {
$err++;
// це означає, що роутінг не відправив у транспорт (наприклад, sync bus)
$queued = false;
$error = 'No SentStamp: message was not routed to a transport';
file_put_contents('/var/www/symfony_docker/var/log/notificationMassiveAddInQueue.log', "#{$MassiveNotification->getId()} rabbitmq-error " . date('H:i:s.u') . " - No SentStamp: message was not routed to a transport\n", FILE_APPEND);
}
} catch (TransportExceptionInterface $e) {
// Помилки транспорту/брокера (у т.ч. коли confirm_publish=true і брокер не підтвердив)
$queued = false;
$error = $e->getMessage();
$err++;
file_put_contents('/var/www/symfony_docker/var/log/notificationMassiveAddInQueue.log', "#{$MassiveNotification->getId()} rabbitmq-TransportExceptionInterface " . date('H:i:s.u') . " - " . $e->getMessage() . "\n", FILE_APPEND);
} catch (\Throwable $e) {
// будь-яка інша помилка
$queued = false;
$error = $e->getMessage();
$err++;
file_put_contents('/var/www/symfony_docker/var/log/notificationMassiveAddInQueue.log', "#{$MassiveNotification->getId()} rabbitmq-Throwable " . date('H:i:s.u') . " - " . $e->getMessage() . "\n", FILE_APPEND);
}
}
$MassiveNotification->setStatus('send');
$MassiveNotification->setDescription('Розсилка в чергу завершена. Додано в чергу ' . ($send + $err) . ' сповіщень, з помилками ' . $err);
$this->entityManager->persist($MassiveNotification);
$this->entityManager->flush();
// die;
}
$logCron = json_encode([
'cron' => 'Stop',
'stop' => date('Y-m-d H:i:s'),
'info' => 'Notification cron complete'
]);
file_put_contents('/var/www/symfony_docker/var/log/notificationMassiveCron.log', $logCron);
$response = new JsonResponse();
$response->setData(['send' => $send, 'err' => $err]);
return $response;
}
}