<?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;
}
#[Route('/client/marzban', name: 'app_client_marzban')]
public function index(
Request $request,
ServiceRepository $serviceRepository,
ServerRepository $serverRepository,
NotificationService $notificationService
): JsonResponse
{
$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 , 300));
$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'];
$usedTraffic = $userArray['used_traffic'] ?? 0;
// 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);
}
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
);
$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);
}
}
}