1 | <?php |
2 | |
3 | namespace App\Components\Moloco; |
4 | |
5 | use App\DTO\RangDateDTO; |
6 | use App\Entity\MolocoAccount; |
7 | use Doctrine\ORM\EntityManagerInterface; |
8 | use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; |
9 | use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; |
10 | use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; |
11 | use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; |
12 | use Symfony\Contracts\HttpClient\HttpClientInterface; |
13 | |
14 | class MolocoAdClient |
15 | { |
16 | private HttpClientInterface $httpClient; |
17 | |
18 | private EntityManagerInterface $em; |
19 | |
20 | private $accessToken; |
21 | |
22 | private int $tries = 5; |
23 | |
24 | public const ENDPOINT = 'https://api.moloco.cloud'; |
25 | |
26 | private const DATE_FORMAT = 'Y-m-d'; |
27 | public const FORMAT = 'json'; |
28 | |
29 | public const STATUS_ACCEPTED = 'ACCEPTED'; |
30 | public const STATUS_READY = 'READY'; |
31 | |
32 | public function __construct(HttpClientInterface $httpClient, EntityManagerInterface $em) |
33 | { |
34 | $this->httpClient = $httpClient; |
35 | $this->em = $em; |
36 | } |
37 | |
38 | public function getAccessToken(AuthRequest $request) |
39 | { |
40 | if ($this->accessToken) { |
41 | return $this->accessToken; |
42 | } |
43 | |
44 | $account = $request->getAccount(); |
45 | |
46 | if ($account->getTokenUpdate() |
47 | && $account->getTokenUpdate()->add(\DateInterval::createFromDateString('+1 hour'))->getTimestamp() > time() |
48 | ) { |
49 | return $this->accessToken = $account->getToken(); |
50 | } |
51 | |
52 | $response = $this->httpClient->request( |
53 | $request->getMethod(), |
54 | $request->getRoute(), |
55 | [ |
56 | 'json' => $request->getParams(), |
57 | 'headers' => ['Accept' => 'application/json'], |
58 | ] |
59 | ); |
60 | |
61 | if (200 === $response->getStatusCode()) { |
62 | $content = $response->toArray(); |
63 | |
64 | $token = $content['token']; |
65 | |
66 | $account->setToken($token); |
67 | $account->setTokenUpdate(new \DateTime()); |
68 | |
69 | $this->em->persist($account); |
70 | $this->em->flush(); |
71 | |
72 | return $this->accessToken = $account->getToken(); |
73 | } |
74 | |
75 | throw new \RuntimeException('Moloco: get access token fail'); |
76 | } |
77 | |
78 | public function getData(MolocoAccount $account, string $statusURI) |
79 | { |
80 | $accessToken = $this->getAccessToken(new AuthRequest($account)); |
81 | |
82 | $response = $this->httpClient->request( |
83 | 'GET', |
84 | $statusURI, |
85 | [ |
86 | 'headers' => [ |
87 | 'Accept' => 'application/json', |
88 | 'Authorization' => 'Bearer ' . $accessToken, |
89 | ], |
90 | ] |
91 | ); |
92 | |
93 | if (200 === $response->getStatusCode()) { |
94 | $content = $response->toArray(); |
95 | |
96 | if ($content['status'] === self::STATUS_READY) { |
97 | return $this->httpClient->request('GET', $content['location_json'])->toArray(); |
98 | } |
99 | |
100 | if (!$this->tries) { |
101 | throw new \Exception('Moloco: report not ready'); |
102 | } |
103 | |
104 | $this->tries--; |
105 | |
106 | sleep(20); |
107 | |
108 | return $this->getData($account, $statusURI); |
109 | } |
110 | |
111 | throw new \RuntimeException('Moloco: get data fail. Code: ' . $response->getStatusCode()); |
112 | } |
113 | |
114 | public function makeReport(MolocoAccount $account, RangDateDTO $rangDateDTO): string |
115 | { |
116 | try { |
117 | $accessToken = $this->getAccessToken(new AuthRequest($account)); |
118 | |
119 | $response = $this->httpClient->request( |
120 | 'POST', |
121 | self::ENDPOINT . '/cm/v1/reports', |
122 | [ |
123 | 'json' => [ |
124 | 'ad_account_id' => $account->getAdAccountId(), |
125 | 'date_range' => [ |
126 | 'start' => $rangDateDTO->getSince()->format(self::DATE_FORMAT), |
127 | 'end' => $rangDateDTO->getSince()->format(self::DATE_FORMAT), |
128 | ], |
129 | 'dimensions' => ['APP_OR_SITE', 'CAMPAIGN'] |
130 | ], |
131 | 'headers' => [ |
132 | 'Accept' => 'application/json', |
133 | 'Authorization' => 'Bearer ' . $accessToken, |
134 | ], |
135 | ] |
136 | ); |
137 | |
138 | if (202 === $response->getStatusCode()) { |
139 | $content = $response->toArray(); |
140 | |
141 | return $content['status']; |
142 | } |
143 | } catch (\Exception | ClientExceptionInterface | RedirectionExceptionInterface | ServerExceptionInterface | TransportExceptionInterface $e) { |
144 | throw new \RuntimeException($e->getMessage()); |
145 | } |
146 | |
147 | throw new \RuntimeException('Response status: ' . $response->getStatusCode()); |
148 | } |
149 | |
150 | public function getExpenses(MolocoAccount $account, RangDateDTO $rangDateDTO) |
151 | { |
152 | try { |
153 | $reportURI = $this->makeReport($account, $rangDateDTO); |
154 | |
155 | return $this->getData($account, $reportURI); |
156 | } catch (\Exception | ClientExceptionInterface | RedirectionExceptionInterface | ServerExceptionInterface | TransportExceptionInterface $e) { |
157 | throw new \RuntimeException($e->getMessage()); |
158 | } |
159 | } |
160 | } |