src/Controller/ApiController.php line 390

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use Bundles\Orders\Entity\OrderFx;
  4. use Symfony\Component\HttpFoundation\Request;
  5. use Symfony\Component\HttpFoundation\JsonResponse;
  6. use App\Entity\User;
  7. use App\Entity\Orders;
  8. use App\Entity\CurrencyPair;
  9. use App\Service\Matcher;
  10. use Psr\Log\LoggerInterface;
  11. use Symfony\Component\Cache\Adapter\AdapterInterface;
  12. use Psr\Cache\CacheItemPoolInterface;
  13. class ApiController extends ParentController {
  14. /** @var array */
  15. protected $errors = [];
  16. protected $data = ['status' => 'success'];
  17. protected $status = 'success';
  18. /** @var User */
  19. protected $user;
  20. protected $helper;
  21. protected $order;
  22. const EXPOSURE_UP = 1;
  23. const EXPOSURE_DOWN = 2;
  24. protected $inputOrder = [
  25. 'currency_pair',
  26. 'amount',
  27. 'exposure_direction',
  28. 'date_start',
  29. 'date_stop',
  30. 'add_periods',
  31. ];
  32. protected $inputFilters = [
  33. 'start_date_differs' => ['type' => 'int', 'limit' => 15],
  34. 'stop_date_differs' => ['type' => 'int', 'limit' => 15],
  35. ];
  36. protected $currencyPair;
  37. protected $amount;
  38. protected $exposure;
  39. protected $dateStart;
  40. protected $dateStop;
  41. public function getOrders(Request $request, LoggerInterface $dbLogger) {
  42. if (!$this->validUser($request)) {
  43. return $this->getResponse();
  44. }
  45. $dbLogger->info('getOrders', ['userId' => $this->user->getCustomer()->getId()]);
  46. /** @var OrderFx[] $orders */
  47. $orders = $this->getDoctrine()->getRepository(OrderFx::class)->findBy(['customer' => $this->user->getCustomer()->getId(), 'active' => true]);
  48. if (!empty($orders)) {
  49. $this->setData($orders);
  50. }
  51. return $this->getResponse();
  52. }
  53. public function getOrder(Request $request) {
  54. if (!$this->validUser($request)) {
  55. return $this->getResponse();
  56. }
  57. $orderPublicId = (int)$request->query->get('order_id');
  58. if (empty($orderPublicId)) {
  59. $this->setError('order id is empty');
  60. return $this->getResponse();
  61. }
  62. $filter = ['customer' => $this->user->getCustomer()->getId(), 'publicId' => $orderPublicId, 'active' => true];
  63. /** @var OrderFx $orderRecord */
  64. $orderRecord = $this->getDoctrine()->getRepository(OrderFx::class)->findOneBy($filter);
  65. if (empty($orderRecord)) {
  66. $this->setError('The order info was not found');
  67. return $this->getResponse();
  68. }
  69. $this->setData([$orderRecord]);
  70. return $this->getResponse();
  71. }
  72. public function putOrder(Request $request) {
  73. if (!$this->validUser($request)) {
  74. return $this->getResponse();
  75. }
  76. if (empty($request->query->all())) {
  77. $this->setError('No input data');
  78. return $this->getResponse();
  79. }
  80. $this->inputOrder[] = 'id'; //papildomai privalomas orderio id
  81. foreach ($this->inputOrder as $key) {
  82. $value = $request->query->get($key);
  83. if (empty($value)) {
  84. $this->setError('Required param '.$key.' is empty');
  85. }
  86. $this->data[$key] = strtoupper($value);
  87. }
  88. if (!empty($this->getErrors()) || !$this->isDataValid()) {
  89. return $this->getResponse();
  90. }
  91. $order = $this->order; //isDataValid() istato irasa, jei ji randa patikrinimo metu.
  92. $order->setCurrencyPair($this->currencyPair);
  93. $order->setAmount($this->amount);
  94. $order->setCurrency($this->exposure);
  95. $order->setDateStart($this->dateStart);
  96. $order->setDateStop($this->dateStop);
  97. $order->setEdited(date_create());
  98. $this->getDoctrine()->getManager()->persist($order);
  99. $this->getDoctrine()->getManager()->flush();
  100. $this->data = ['status' => 'success', 'id' => $order->getPublicId()];
  101. return $this->getResponse();
  102. }
  103. public function deleteOrder(Request $request) {
  104. if (!$this->validUser($request)) {
  105. return $this->getResponse();
  106. }
  107. if (empty($request->query->all())) {
  108. $this->setError('No input data');
  109. return $this->getResponse();
  110. }
  111. $this->inputOrder = ['id']; //privalomas tik orderio id
  112. foreach ($this->inputOrder as $key) {
  113. $value = $request->query->get($key);
  114. if (empty($value)) {
  115. $this->setError('Required param '.$key.' is empty');
  116. }
  117. $this->data[$key] = strtoupper($value);
  118. }
  119. if (!empty($this->data['id'])) {
  120. $filter = ['customer' => $this->user->getCustomer()->getId(), 'publicId' => (int)$this->data['id'], 'active' => true];
  121. /** @var OrderFx $orderRecord */
  122. $this->order = $this->getDoctrine()->getRepository(OrderFx::class)->findOneBy($filter);
  123. if (empty($this->order)) {
  124. $this->setError('Order by Id '.$this->data['id'].' is not find in your active order list');
  125. }
  126. }
  127. if (!empty($this->getErrors())) {
  128. return $this->getResponse();
  129. }
  130. $order = $this->order;
  131. $order->setActive(false);
  132. $order->setEdited(date_create());
  133. $this->getDoctrine()->getManager()->persist($order);
  134. $this->getDoctrine()->getManager()->flush();
  135. $this->data = ['status' => 'success', 'deleted_id' => $order->getPublicId()];
  136. return $this->getResponse();
  137. }
  138. public function postOrder(Request $request) {
  139. if (!$this->validUser($request)) {
  140. return $this->getResponse();
  141. }
  142. if (empty($request->query->all())) {
  143. $this->setError('No input data');
  144. return $this->getResponse();
  145. }
  146. foreach ($this->inputOrder as $key) {
  147. $value = $request->query->get($key);
  148. if (empty($value)) {
  149. $this->setError('Required param '.$key.' is empty');
  150. }
  151. $this->data[$key] = strtoupper($value);
  152. }
  153. if (!$this->isDataValid()) {
  154. return $this->getResponse();
  155. }
  156. $order = new OrderFx();
  157. $order->setCustomer($this->user->getCustomer());
  158. $order->setCurrencyPair($this->currencyPair);
  159. $order->setAmountBuy($this->amount);
  160. $order->setCurrency($this->exposure);
  161. $order->setCreated($this->dateStart);
  162. $order->setDateMaturity($this->dateStop);
  163. $order->setCreated(date_create());
  164. // $nextPublicId = $this->getDoctrine()->getRepository(OrderFx::class)->getNextPublicId($order->getCustomer());
  165. // $order->setPublicId($nextPublicId);
  166. // $validator = new \App\Validators\Orders();
  167. // $errors = $validator->validate($order);
  168. // if (empty($errors)) {
  169. $this->getDoctrine()->getManager()->persist($order);
  170. $this->getDoctrine()->getManager()->flush();
  171. $this->data = ['status' => 'success'];//, 'id' => $order->getPublicId()];
  172. // }
  173. return $this->getResponse();
  174. }
  175. public function getLiquidity(LoggerInterface $ordersLogger, CacheItemPoolInterface $cache, Request $request) {
  176. if (!$this->validUser($request)) {
  177. return $this->getResponse();
  178. }
  179. foreach ($this->inputOrder as $key) {
  180. $value = $request->query->get($key);
  181. if (empty($value)) {
  182. $this->setError('Required param '.$key.' is empty');
  183. }
  184. $this->data[$key] = strtoupper($value);
  185. }
  186. $this->data['filter'] = [];
  187. foreach ($this->inputFilters as $key => $params) {
  188. $value = $request->query->get($key);
  189. if (isset($value)) {
  190. if (!empty($params['type']) && $params['type'] == 'int') {
  191. $value = (int)$value;
  192. }
  193. if (!empty($params['limit']) && ($value < 0 || $value > $params['limit'])) {
  194. $this->setError('Filter param '.$key.' value is out of limits (0-'.$params['limit'].')');
  195. } else {
  196. $this->data['filter'][$key] = $value;
  197. }
  198. }
  199. }
  200. if (!$this->isDataValid()) {
  201. return $this->getResponse();
  202. }
  203. $periodsForCheck = $this->getPeriodsForCheck($request);
  204. if (!empty($periodsForCheck)) {
  205. foreach ($periodsForCheck as $period) {
  206. //dump($period);
  207. $order = new OrderFx();
  208. $order->setCustomer($this->user->getCustomer());
  209. $order->setCurrencyPair($this->currencyPair);
  210. $order->setAmount($this->amount);
  211. $order->setCurrency($this->exposure);
  212. $order->setDateStart($period['dateStart']);
  213. $order->setDateStop($period['dateStop']);
  214. try {
  215. $matcher = new Matcher(
  216. $this->container,
  217. $this->getDoctrine(),
  218. $cache,
  219. $ordersLogger,
  220. $this->config,
  221. $this->user,
  222. [$order],
  223. $this->data['filter']);
  224. $matches = $matcher->start(false);
  225. $distribution = [];
  226. $rate = 1;
  227. if (!empty($matches['orders'][0]['distribution'])) {
  228. $distribution = $matches['orders'][0]['distribution'];
  229. $rate = $matches['orders'][0]['rate'];
  230. }
  231. $coveredSumTotal = 0;
  232. $coveredSumInternal = 0;
  233. foreach ($distribution as $forwardType) {
  234. foreach ($forwardType as $item) {
  235. $coveredSumTotal += $item['amount'];
  236. if ($item['entity']->getCustomer()->getId() == $this->user->getCustomer()->getId()) {
  237. $coveredSumInternal += $item['amount'];
  238. }
  239. }
  240. }
  241. $coveredSumTotal = round(($coveredSumTotal * (float)$rate));
  242. if ($coveredSumTotal > $this->amount) {
  243. $coveredSumTotal = $this->amount;
  244. }
  245. $coverage = [
  246. 'amount' => [
  247. 'internal' => round($coveredSumInternal),
  248. 'external' => round($coveredSumTotal - $coveredSumInternal),
  249. 'total' => $coveredSumTotal,
  250. 'currency' => $order->getCurrencyPair()->getCurrencyNameByNumber($this->exposure),
  251. ],
  252. 'percentage' => [
  253. 'internal' => round($coveredSumInternal / $this->amount * 100, 1),
  254. 'external' => round(($coveredSumTotal - $coveredSumInternal) / $this->amount * 100, 1),
  255. 'total' => round($coveredSumTotal / $this->amount * 100, 1),
  256. ],
  257. ];
  258. $period = $order->getDateStart()->format('Y-m-d').' - '.$order->getDateStop()->format('Y-m-d');
  259. $this->data['coverage'][$period] = $coverage;
  260. } catch
  261. (\Exception $e) {
  262. $this->setError('internal error. '.$e->getMessage());
  263. }
  264. }
  265. }
  266. return $this->getResponse();
  267. }
  268. public function getCurrencyPairs(Request $request) {
  269. if (!$this->validUser($request)) {
  270. return $this->getResponse();
  271. }
  272. $this->helper = $this->get('helper');
  273. /** @var OrderFx[] $orders */
  274. $orders = $this->getDoctrine()->getRepository(OrderFx::class)->findBy(['active' => 1]);
  275. if (empty($orders)) {
  276. $this->setError('No any currency pair was found');
  277. return $this->getResponse();
  278. }
  279. $pairs = [];
  280. foreach ($orders as $order) {
  281. $pair = str_replace('/', '', $order->getCurrencyPair()->getName());
  282. if (!in_array($pair, $pairs)) {
  283. $pairs[] = $pair;
  284. }
  285. }
  286. sort($pairs);
  287. $this->data['currency_pairs'] = $pairs;
  288. return $this->getResponse();
  289. }
  290. protected function isDataValid() {
  291. if (!empty($this->data['id'])) {
  292. $filter = ['customer' => $this->user->getCustomer()->getId(), 'publicId' => (int)$this->data['id'], 'active' => true];
  293. /** @var OrderFx $orderRecord */
  294. $this->order = $this->getDoctrine()->getRepository(OrderFx::class)->findOneBy($filter);
  295. if (empty($this->order)) {
  296. $this->setError('Order by Id '.$this->data['id'].' is not found in your active order list');
  297. }
  298. }
  299. $currencies = str_split($this->data['currency_pair'], 3);
  300. if (count($currencies) != 2) {
  301. $this->setError('Wrong currency pair format: '.$this->data['currency_pair'].'. example: EURUSD, GBPUSD');
  302. return $this->getResponse();
  303. }
  304. $currencyPairName = $currencies[0].'/'.$currencies[1];
  305. $this->currencyPair = $this->getDoctrine()->getRepository(CurrencyPair::class)->findOneBy(['name' => $currencyPairName]);
  306. if (empty($this->currencyPair)) {
  307. $this->setError('Currency pair '.$this->data['currency_pair'].' is not supported');
  308. return $this->getResponse();
  309. }
  310. $this->exposure = self::EXPOSURE_UP;
  311. if (!in_array($this->data['exposure_direction'], ['UP', 'DOWN'])) {
  312. $this->setError('exposure_direction value allowed: "UP", "DOWN"');
  313. return $this->getResponse();
  314. }
  315. if ($this->data['exposure_direction'] == 'DOWN') {
  316. $this->exposure = self::EXPOSURE_DOWN;
  317. }
  318. $this->dateStart = date_create($this->data['date_start']);
  319. $this->dateStop = date_create($this->data['date_stop']);
  320. // if (date_create() > $this->dateStart) {
  321. // // $this->setError('Contract order start date must be in future');
  322. // }
  323. $dayOfWeekOfStartDate = $this->dateStart->format('w');
  324. if ($dayOfWeekOfStartDate != 5) {
  325. $this->setError('Order start date weekday is allowed only on fridays. Choose nearest friday date and repeat request');
  326. }
  327. // if (date_create() > $this->dateStop) {
  328. // // $this->setError('Contract order finish date must be in future');
  329. // }
  330. if ($this->dateStart >= $this->dateStop) {
  331. $this->setError('Contract order finish date must be later than start date');
  332. }
  333. $dayOfWeekOfStopDate = $this->dateStop->format('w');
  334. if ($dayOfWeekOfStopDate != 5) {
  335. $this->setError('Order stop date weekday is allowed only on fridays. Choose nearest friday date and repeat request');
  336. }
  337. $this->amount = (int)$this->data['amount'];
  338. if ($this->amount < 10000 || $this->amount > (1000000000)) {
  339. $this->setError('Amount value is out of limits (10 000 - 1 000 000 000)');
  340. }
  341. return empty($this->getErrors());
  342. }
  343. public function pageNotFound() {
  344. $this->setError('Request unknown. Check api endpoint and try again');
  345. return $this->getResponse(404);
  346. }
  347. public function apiDocs() {
  348. return $this->redirect('/api/doc/index.html');
  349. }
  350. protected function validUser(Request $request) {
  351. // look for header "Authorization: Bearer <token>"
  352. if (!$request->headers->has('Authorization')
  353. && 0 !== strpos($request->headers->get('Authorization'), 'Bearer ')) {
  354. $this->setError('Invalid authorization info');
  355. return false;
  356. }
  357. // skip beyond "Bearer "
  358. $authorizationHeader = $request->headers->get('Authorization');
  359. $apiKey = substr($authorizationHeader, 7);
  360. $ip = $request->getClientIp();
  361. /** @var User $user */
  362. $this->user = $this->getDoctrine()->getRepository(User::class)->findOneBy(['apiKey' => $apiKey]);
  363. if (empty($this->user)) {
  364. $this->setError('Invalid apiKey');
  365. return false;
  366. }
  367. if (strpos($this->user->getAllowedIps(), $ip) === false) {
  368. $this->setError('For this apiKey is not allowed IP: '.$ip);
  369. return false;
  370. }
  371. return true;
  372. }
  373. protected function setData($orders) {
  374. foreach ($orders as $order) {
  375. $order = [
  376. 'id' => $order->getPublicId(),
  377. 'currency_pair' => str_replace('/', '', $order->getCurrencyPair()->getName()),
  378. 'amount' => $order->getAmount(),
  379. 'amount_currency' => $order->getCurrencyPair()->getCurrencyNameByNumber(1),
  380. 'exposure_direction' => ($order->getCurrency() > 1 ? 'UP' : 'DOWN'),
  381. 'date_start' => $order->getDateStart()->format('Y-m-d H:i'),
  382. 'date_stop' => $order->getDateStop()->format('Y-m-d H:i'),
  383. 'covered' => $order->getReserveAmount(),
  384. ];
  385. $this->data['orders'][] = $order;
  386. }
  387. }
  388. protected function getResponse($status = 200) {
  389. if (!empty($this->getErrors())) {
  390. $this->data['status'] = 'error';
  391. $this->data['errors'] = $this->getErrors();
  392. }
  393. return new JsonResponse($this->data, $status);
  394. }
  395. protected function setError($error) {
  396. return $this->errors[] = $error;
  397. }
  398. protected function getErrors() {
  399. return $this->errors;
  400. }
  401. protected function getPeriodsForCheck($request) {
  402. $addPeriods = $request->query->get('add_periods');
  403. if (empty($addPeriods)) {
  404. return [
  405. [
  406. 'dateStart' => $this->dateStart,
  407. 'dateStop' => $this->dateStop,
  408. ],
  409. ];
  410. }
  411. $periods = [];
  412. $dateStartString = $this->dateStart->format('Y-m-d');
  413. $dateStopString = $this->dateStop->format('Y-m-d');
  414. $dateStartBeforeWeek = date_create($dateStartString.' -1 week');
  415. if ($dateStartBeforeWeek > date_create()) {
  416. $periods[] = [
  417. 'dateStart' => $dateStartBeforeWeek,
  418. 'dateStop' => date_create($dateStopString.' -1 week'),
  419. ];
  420. }
  421. $periods[] = [
  422. 'dateStart' => $this->dateStart,
  423. 'dateStop' => $this->dateStop,
  424. ];
  425. $periods[] = [
  426. 'dateStart' => $this->dateStart,
  427. 'dateStop' => date_create($dateStopString.' +1 week'),
  428. ];
  429. $periods[] = [
  430. 'dateStart' => date_create($dateStartString.' +1 week'),
  431. 'dateStop' => $this->dateStop,
  432. ];
  433. $periods[] = [
  434. 'dateStart' => date_create($dateStartString.' +1 week'),
  435. 'dateStop' => date_create($dateStopString.' +1 week'),
  436. ];
  437. if (count($periods) < 3) {
  438. $periods[] = [
  439. 'dateStart' => date_create($dateStartString.' +2 week'),
  440. 'dateStop' => date_create($dateStopString.' +2 week'),
  441. ];
  442. }
  443. return $periods;
  444. }
  445. }