<?php
namespace App\Controller\Client;
use App\Entity\VPN\Service\Service;
use App\Entity\VPN\Service\ServiceUsage;
use App\Entity\VPN\V2ray\Server;
use App\Repository\VPN\Service\ServiceRepository;
use App\Repository\VPN\Service\ServiceUsageRepository;
use App\Repository\VPN\V2ray\ServerRepository;
use App\Service\Util\MarzbanNewAPI;
use App\Service\Util\NotificationService;
use App\Service\Util\ServersAnalyticsUsageService;
use App\Service\Util\Telegram;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Stopwatch\Stopwatch;
//class MarzbanController extends AbstractController
//{
// public $registry;
// private Telegram $telegram;
// public EntityManagerInterface $entityManager;
//
// public ServiceUsageRepository $serviceUsageRepository;
//
// public function __construct(Telegram $telegram, EntityManagerInterface $entityManager , ServiceUsageRepository $serviceUsageRepository)
// {
// $this->telegram = $telegram;
// $this->serviceUsageRepository = $serviceUsageRepository;
// $this->entityManager = $entityManager;
// }
//
//
//// Entity Manager Is Closed Problem
//// #[Route('/client/marzban', name: 'app_client_marzban')]
//// public function index(
//// Request $request,
//// ServiceRepository $serviceRepository,
//// ServerRepository $serverRepository,
//// NotificationService $notificationService
//// ): JsonResponse
//// {
////
////// return new JsonResponse('TEst', JsonResponse::HTTP_BAD_REQUEST);
////
////
//// $entityManager = $this->getDoctrine()->getManager();
//// $this->registry = $entityManager;
//// $now = new DateTime();
//// $currentHour = (int)$now->format('H');
//// $data = json_decode($request->getContent(), true);
//// if (json_last_error() !== JSON_ERROR_NONE || !isset($data['ip'], $data['list'])) {
//// return new JsonResponse('Invalid JSON or missing data', JsonResponse::HTTP_BAD_REQUEST);
//// }
////
//// sleep(random_int(60 , 500));
//// $stopwatch = new Stopwatch();
//// // Start the stopwatch
//// $stopwatch->start('duration');
//// $users = $data['list'];
//// $usersCount = count($users);
//// $ip = $data['ip'];
//// $totalServerUsage = 0;
//// foreach ($users as $key => $userItem) {
//// if ($userItem['used_traffic'] > 0) {
//// $totalServerUsage = $totalServerUsage + $userItem['used_traffic'];
//// }
//// }
//// $analytics = new ServersAnalyticsUsageService($ip, $this->entityManager);
//// $analytics->addServerUsage($ip, $totalServerUsage);
//// $server = $this->getServer($ip, $serverRepository);
//// if (!$server) {
//// $this->telegram->sendRawMessage($ip);
//// return new JsonResponse('Server not found', JsonResponse::HTTP_BAD_REQUEST);
//// }
//// $server->setLastScriptUpdate(new \DateTimeImmutable());
//// $this->entityManager->flush();
//// // Split users into batches of 50 to handle them in smaller transactions
//// $arrayData = array_chunk($users, 50);
//// foreach ($arrayData as $arrayDatum) {
//// try {
//// // Start a transaction for this batch
//// $entityManager->beginTransaction();
////
//// foreach ($arrayDatum as $userArray) {
//// try {
//// $username = $userArray['username'];
//// $calculationVolume = 1.2;
//// if ($server->getCountry()->getCode() === 'ir'){
//// $calculationVolume = 2;
//// }
//// foreach ($server->getProtocols() as $protocol) {
//// if ($protocol->getCountry() !== null && $protocol->getCountry()->getCode() === 'ir'){
//// $calculationVolume = 2;
//// }
//// }
//// $usedTraffic = $userArray['used_traffic'] ?? 0;
//// $usedTraffic = $usedTraffic * $calculationVolume;
////
////
////
////
////
//// // Only handle users with traffic usage
//// if ($usedTraffic > 0) {
//// $service = $this->findService($username,$serviceRepository);
//// if ($service) {
//// $this->handleServiceUsage($server, $service, $usedTraffic);
//// if (!$service->getExpireAt())
//// {
//// $now = new \DateTimeImmutable();
//// $expiredAt = $now->add(new \DateInterval("P{$service->getPlan()->getDays()}D"));
//// $service->setExpireAt($expiredAt);
//// $entityManager->flush();
//// }
//// if ($service->getPlan()->getServiceType() === 'unlimited' && $service->getCountry() !== $server->getCountry()->getCode()){
//// $this->removeServiceUser($server, $username);
//// }
//// }else{
//// $this->removeServiceUser($server, $username);
//// }
////
////
////
////
////
//// }
//// } catch (Exception $exception) {
//// // Log the exception without affecting the entire transaction
////// $this->telegram->exception($exception);
//// $notificationService->exception($exception);
//// }
//// }
////
//// // Commit the current transaction for the batch
//// $entityManager->flush();
//// $entityManager->commit();
////
//// // Clear the entity manager to free up memory
//// $entityManager->clear();
////
//// } catch (Exception $e) {
//// // Catch any exception and log it
////// $this->telegram->exception($e);
////$notificationService->exception($e);
//// // If EntityManager is closed due to an error, reset it
//// if (!$entityManager->isOpen()) {
//// $entityManager = $this->getDoctrine()->getManager();
//// }
//// } finally {
//// // Clear the entity manager to free up memory after each batch
//// $entityManager->clear();
//// }
//// }
////
//// // Stop the stopwatch and get the event duration
//// $event = $stopwatch->stop('duration');
////
//// // Get elapsed time in milliseconds
//// $duration = $event->getDuration(); // Time in milliseconds
////// $this->telegram->sendRawMessage(
////// 'Duration : '. $duration .' ms'.PHP_EOL.
////// 'Ip : '. $ip .PHP_EOL.
////// 'Users Trafical : '. count($users).PHP_EOL.
////// 'Total Users : '.$usersCount
//////
////// );
//// return new JsonResponse('Success', JsonResponse::HTTP_OK);
////
////
//// }
//
//
//
//
// #[Route('/client/marzban', name: 'app_client_marzban')]
// public function index(
// Request $request,
// ServiceRepository $serviceRepository,
// ServerRepository $serverRepository,
// NotificationService $notificationService
// ): JsonResponse
// {
// $now = new DateTime();
// $data = json_decode($request->getContent(), true);
// if (json_last_error() !== JSON_ERROR_NONE || !isset($data['ip'], $data['list'])) {
// return new JsonResponse('Invalid JSON or missing data', JsonResponse::HTTP_BAD_REQUEST);
// }
// sleep(random_int(100, 500));
// $users = $data['list'];
// $usersCount = count($users);
// $ip = $data['ip'];
// $totalServerUsage = array_reduce($users, function ($carry, $user) {
// return $carry + ($user['used_traffic'] ?? 0);
// }, 0);
//
// $analytics = new ServersAnalyticsUsageService($ip, $this->entityManager);
// $analytics->addServerUsage($ip, $totalServerUsage);
// $server = $this->getServer($ip, $serverRepository);
// if (!$server) {
// $this->telegram->sendRawMessage(
// 'این سرور داره کار میکنه ولی تو پنل نیست که بشه حجم به سرویس ها اضافه بشه : '.
// $ip);
//// return new JsonResponse('Server not found', JsonResponse::HTTP_BAD_REQUEST);
// }
//
// $server?->setLastScriptUpdate(new \DateTimeImmutable());
// $this->ensureEntityManagerOpen();
// $this->flush();
//
// $arrayData = array_chunk($users, 50);
// foreach ($arrayData as $arrayDatum) {
// try {
// $this->ensureEntityManagerOpen();
// $this->entityManager->beginTransaction();
//
// foreach ($arrayDatum as $userArray) {
// try {
// $username = $userArray['username'];
// $usedTraffic = ($userArray['used_traffic'] ?? 0);
//// $calculationVolume = 1.2;
// $calculationVolume = 2;
// if ($server->getCountry()->getCode() === 'ir') {
// $calculationVolume = 2;
// }
//
// foreach ($server->getProtocols() as $protocol) {
// if ($protocol->getCountry()?->getCode() === 'ir') {
// $calculationVolume = 2;
// }
// }
//
// $usedTraffic *= $calculationVolume;
//
// if ($usedTraffic > 0) {
// $service = $this->findService($username, $serviceRepository);
// if ($service) {
// $this->handleServiceUsage($server, $service, $usedTraffic);
// if (!$service->getExpireAt()) {
// $expiredAt = (new \DateTimeImmutable())->add(new \DateInterval("P{$service->getPlan()->getDays()}D"));
// $service->setExpireAt($expiredAt);
// $this->ensureEntityManagerOpen();
// $this->flush();
// }
// if ($service->getPlan()->getServiceType() === 'unlimited' &&
// $service->getCountry() !== $server->getCountry()->getCode()) {
// $this->removeServiceUser($server, $username);
// }
// } else {
// $this->removeServiceUser($server, $username);
// }
// }
//
// } catch (Exception $exception) {
// $notificationService->exception($exception);
// }
// }
//
// $this->ensureEntityManagerOpen();
// $this->flush();
// $this->entityManager->commit();
//
// } catch (Exception $e) {
// $notificationService->exception($e);
// if (!$this->entityManager->isOpen()) {
// $this->entityManager = $this->getDoctrine()->getManager();
// }
// } finally {
// $this->ensureEntityManagerOpen();
// $this->entityManager->clear();
// }
// }
// return new JsonResponse('Success', JsonResponse::HTTP_OK);
// }
//
//
// private function getServer(
// string $ip,
// ServerRepository $v2rayMultiServerRepository,
// ): Server|null
// {
// return $v2rayMultiServerRepository->findOneBy(['ip' => $ip, 'active' => 1, 'deletedAt' => null]);
// }
//
// private function findService(
// string $username,
// ServiceRepository $v2rayMultiServiceRepository,
// ): Service|null
// {
// return $v2rayMultiServiceRepository->findOneBy(['clientId' => $username]);
// }
//
// private function handleServiceUsage(Server $server, Service $service, int $usedTraffic): void
// {
// if ($usedTraffic > 0) {
// $trafficToAdd = $usedTraffic;
// $service->setV2rayUsage($service->getV2rayUsage() + $trafficToAdd);
// $now = new \DateTime();
// $todayUsageObject = $this->serviceUsageRepository->findOneBy(['service' => $service , 'date' => $now]);
// if (!$todayUsageObject) {
// $todayUsageObject = new ServiceUsage();
// $todayUsageObject->setService($service);
// $todayUsageObject->setDate(new \DateTime());
// $this->entityManager->persist($todayUsageObject);
// }
// $todayUsageObject->setV2rayUsage(
// $todayUsageObject->getV2rayUsage() +
// (int)$trafficToAdd
// );
//
//// TODO : check
//
//// $this->entityManager->flush();
// if ((!$service->isEnabled() || !$service->isActive())) {
// $this->removeServiceUser($server, $service);
// }
//
// }
// }
//
//
// private function removeServiceUser(Server $server, $service): void
// {
// $apiObject = new MarzbanNewAPI($server);
// if ($service instanceof Service){
// $apiObject->removeUser($service->getClientId());
// }else{
// $apiObject->removeUser($service);
// }
// }
//
// private function ensureEntityManagerOpen(): void
// {
// if (!$this->entityManager->isOpen()) {
// $this->entityManager = $this->getDoctrine()->getManager();
// }
// }
//
//
// public function flush()
// {
// try {
// $this->entityManager->flush();
// } catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException $e) {
// $this->logDoctrineException($e);
// $this->resetEntityManager(); // 👈 این مهمه
// } catch (\Doctrine\DBAL\Exception\ConstraintViolationException $e) {
// $this->logDoctrineException($e);
// $this->resetEntityManager();
// } catch (\Doctrine\ORM\ORMException $e) {
// $this->logDoctrineException($e);
// $this->resetEntityManager();
// } catch (\Exception $e) {
// $this->logDoctrineException($e);
// $this->resetEntityManager();
// }
// }
//
// private function logDoctrineException(\Exception $e): void
// {
// $this->telegram->sendRawMessage(
// "❌ Doctrine Error: " . $e->getMessage() . "\n\n" .
// "File: " . $e->getFile() . "\nLine: " . $e->getLine() . "\n\n" .
// "Trace:\n" . substr($e->getTraceAsString(), 0, 1000)
// );
// }
//
// private function resetEntityManager(): void
// {
// if (!$this->entityManager->isOpen()) {
// $this->entityManager = $this->getDoctrine()->getManager();
// }
// }
//
//}
class MarzbanController extends AbstractController
{
private Telegram $telegram;
private EntityManagerInterface $entityManager;
private ServiceUsageRepository $serviceUsageRepository;
public function __construct(Telegram $telegram, EntityManagerInterface $entityManager, ServiceUsageRepository $serviceUsageRepository)
{
$this->telegram = $telegram;
$this->entityManager = $entityManager;
$this->serviceUsageRepository = $serviceUsageRepository;
}
#[Route('/client/marzban', name: 'app_client_marzban')]
public function index(Request $request, ServiceRepository $serviceRepository, ServerRepository $serverRepository, NotificationService $notificationService): JsonResponse
{
$data = json_decode($request->getContent(), true);
if (json_last_error() !== JSON_ERROR_NONE || !isset($data['ip'], $data['list'])) {
return new JsonResponse('Invalid JSON or missing data', JsonResponse::HTTP_BAD_REQUEST);
}
sleep(random_int(100, 500));
$users = $data['list'];
$ip = $data['ip'];
$totalServerUsage = array_reduce($users, fn($carry, $user) => $carry + ($user['used_traffic'] ?? 0), 0);
$analytics = new ServersAnalyticsUsageService($ip, $this->entityManager);
$analytics->addServerUsage($ip, $totalServerUsage);
$server = $this->getServer($ip, $serverRepository);
if (!$server) {
$this->telegram->sendRawMessage("سرور پیدا نشد: $ip");
}
$server?->setLastScriptUpdate(new \DateTimeImmutable());
$this->safeFlush();
foreach (array_chunk($users, 50) as $batch) {
try {
$this->ensureEntityManagerOpen();
$this->entityManager->beginTransaction();
foreach ($batch as $user) {
try {
$username = $user['username'] ?? null;
$usedTraffic = ($user['used_traffic'] ?? 0) * 2;
if ($usedTraffic <= 0 || !$username) continue;
$service = $this->findService($username, $serviceRepository);
if ($service) {
$this->handleServiceUsage($server, $service, $usedTraffic);
if (!$service->getExpireAt()) {
$expiredAt = (new \DateTimeImmutable())->add(new \DateInterval("P{$service->getPlan()->getDays()}D"));
$service->setExpireAt($expiredAt);
$this->safeFlush();
}
if ($service->getPlan()->getServiceType() === 'unlimited' && $service->getCountry() !== $server->getCountry()->getCode()) {
$this->removeServiceUser($server, $username);
}
} else {
$this->removeServiceUser($server, $username);
}
} catch (Exception $e) {
$notificationService->exception($e);
}
}
$this->safeFlush();
if ($this->entityManager->getConnection()->isTransactionActive()) {
$this->entityManager->commit();
}
} catch (Exception $e) {
$notificationService->exception($e);
if ($this->entityManager->getConnection()->isTransactionActive()) {
$this->entityManager->rollback();
}
$this->resetEntityManager();
} finally {
$this->entityManager->clear();
}
}
return new JsonResponse('Success', JsonResponse::HTTP_OK);
}
private function getServer(string $ip, ServerRepository $repo): ?Server
{
return $repo->findOneBy(['ip' => $ip, 'active' => 1, 'deletedAt' => null]);
}
private function findService(string $username, ServiceRepository $repo): ?Service
{
return $repo->findOneBy(['clientId' => $username]);
}
private function handleServiceUsage(Server $server, Service $service, int $usedTraffic): void
{
$service->setV2rayUsage($service->getV2rayUsage() + $usedTraffic);
$today = new \DateTime();
$usage = $this->serviceUsageRepository->findOneBy(['service' => $service, 'date' => $today]);
if (!$usage) {
$usage = new ServiceUsage();
$usage->setService($service);
$usage->setDate($today);
$this->entityManager->persist($usage);
}
$usage->setV2rayUsage($usage->getV2rayUsage() + $usedTraffic);
if (!$service->isEnabled() || !$service->isActive()) {
$this->removeServiceUser($server, $service);
}
}
private function removeServiceUser(Server $server, $service): void
{
$api = new MarzbanNewAPI($server);
$clientId = $service instanceof Service ? $service->getClientId() : $service;
$api->removeUser($clientId);
}
private function ensureEntityManagerOpen(): void
{
if (!$this->entityManager->isOpen()) {
$this->resetEntityManager();
}
}
private function safeFlush(): void
{
try {
$this->entityManager->flush();
} catch (\Throwable $e) {
$this->logDoctrineException($e);
if ($this->entityManager->getConnection()->isTransactionActive()) {
$this->entityManager->rollback();
}
$this->resetEntityManager();
}
}
private function resetEntityManager(): void
{
if (!$this->entityManager->isOpen()) {
$this->entityManager = $this->getDoctrine()->getManager();
}
}
private function logDoctrineException(\Throwable $e): void
{
$this->telegram->sendRawMessage(
"🚨 Doctrine Exception 🚨\n" .
"📛 Message: " . $e->getMessage() . "\n" .
"📁 File: " . $e->getFile() . "\n" .
"📍 Line: " . $e->getLine() . "\n" .
"🧵 Trace: " . substr($e->getTraceAsString(), 0, 2000)
);
}
}