From 84861b5e572b5f184705b05ada105bb3237e81a7 Mon Sep 17 00:00:00 2001 From: Juan Pablo Vial Date: Fri, 26 Jul 2024 23:15:48 -0400 Subject: [PATCH] Cli user --- app/resources/routes/96_admin.php | 10 +++ app/resources/routes/admin/users.php | 6 ++ app/resources/routes/api/admin.php | 13 +++ app/resources/routes/api/admin/users.php | 6 ++ app/resources/routes/api/login.php | 4 + app/resources/routes/api/money.php | 4 + app/resources/views/admin/users.blade.php | 100 ++++++++++++++++++++++ app/setup/setups/middlewares.php | 1 + app/setup/setups/services.php | 5 +- app/src/Controller/API/Admin/Users.php | 30 +++++++ app/src/Controller/API/Login.php | 33 +++++++ app/src/Controller/API/Money.php | 26 ++++++ app/src/Controller/Admin/Users.php | 21 +++++ app/src/Service/Login.php | 40 +++++++-- app/src/Service/UF.php | 28 +++++- cli/common/Alias/Application.php | 6 +- cli/composer.json | 6 +- cli/resources/commands/money.php | 1 + cli/setup/settings/folders.php | 4 + cli/setup/setups/client.php | 22 ++++- cli/src/Command/Full.php | 1 + cli/src/Command/Money/UF/Update.php | 47 ++++++++++ cli/src/Service/Login.php | 59 +++++++++++++ redisinsight.compose.yml | 2 +- 24 files changed, 457 insertions(+), 18 deletions(-) create mode 100644 app/resources/routes/96_admin.php create mode 100644 app/resources/routes/admin/users.php create mode 100644 app/resources/routes/api/admin.php create mode 100644 app/resources/routes/api/admin/users.php create mode 100644 app/resources/routes/api/login.php create mode 100644 app/resources/views/admin/users.blade.php create mode 100644 app/src/Controller/API/Admin/Users.php create mode 100644 app/src/Controller/API/Login.php create mode 100644 app/src/Controller/Admin/Users.php create mode 100644 cli/src/Command/Money/UF/Update.php create mode 100644 cli/src/Service/Login.php diff --git a/app/resources/routes/96_admin.php b/app/resources/routes/96_admin.php new file mode 100644 index 0000000..4a79cb3 --- /dev/null +++ b/app/resources/routes/96_admin.php @@ -0,0 +1,10 @@ +group('/admin', function($app) { + $files = new FilesystemIterator(implode(DIRECTORY_SEPARATOR, [__DIR__, 'admin'])); + foreach ($files as $file) { + if ($file->isDir()) { + continue; + } + include_once $file->getRealPath(); + } +}); diff --git a/app/resources/routes/admin/users.php b/app/resources/routes/admin/users.php new file mode 100644 index 0000000..3e8fdb3 --- /dev/null +++ b/app/resources/routes/admin/users.php @@ -0,0 +1,6 @@ +group('/users', function($app) { + $app->get('[/]', Users::class); +}); diff --git a/app/resources/routes/api/admin.php b/app/resources/routes/api/admin.php new file mode 100644 index 0000000..263bc31 --- /dev/null +++ b/app/resources/routes/api/admin.php @@ -0,0 +1,13 @@ +group('/admin', function($app) { + $folder = implode(DIRECTORY_SEPARATOR, [__DIR__, 'admin']); + if (file_exists($folder)) { + $files = new FilesystemIterator($folder); + foreach ($files as $file) { + if ($file->isDir()) { + continue; + } + include_once $file->getRealPath(); + } + } +}); diff --git a/app/resources/routes/api/admin/users.php b/app/resources/routes/api/admin/users.php new file mode 100644 index 0000000..b05b43d --- /dev/null +++ b/app/resources/routes/api/admin/users.php @@ -0,0 +1,6 @@ +group('/users', function($app) { + $app->post('/add[/]', Users::class . ':add'); +}); diff --git a/app/resources/routes/api/login.php b/app/resources/routes/api/login.php new file mode 100644 index 0000000..698aced --- /dev/null +++ b/app/resources/routes/api/login.php @@ -0,0 +1,4 @@ +post('/login[/]', Login::class); diff --git a/app/resources/routes/api/money.php b/app/resources/routes/api/money.php index 87f6b68..88ba886 100644 --- a/app/resources/routes/api/money.php +++ b/app/resources/routes/api/money.php @@ -4,6 +4,10 @@ use Incoviba\Controller\API\Money; $app->group('/money', function($app) { $app->post('/ipc[/]', [Money::class, 'ipc']); $app->post('/uf[/]', [Money::class, 'uf']); + $app->group('/ufs', function($app) { + $app->post('[/]', [Money::class, 'updateUfs']); + $app->get('[/]', [Money::class, 'ufs']); + }); $app->post('/many[/]', [Money::class, 'getMany']); $app->post('[/]', [Money::class, 'get']); }); diff --git a/app/resources/views/admin/users.blade.php b/app/resources/views/admin/users.blade.php new file mode 100644 index 0000000..cf98844 --- /dev/null +++ b/app/resources/views/admin/users.blade.php @@ -0,0 +1,100 @@ +@extends('layout.base') + +@section('page_content') +
+

Usuarios

+ + + + + + + + + @foreach($users as $user) + + + + + @endforeach + +
Nombre + +
{{ $user->name }} + + +
+
+ +@endsection + +@include('layout.body.scripts.cryptojs') + +@push('page_scripts') + +@endpush diff --git a/app/setup/setups/middlewares.php b/app/setup/setups/middlewares.php index 17417f7..7072d60 100644 --- a/app/setup/setups/middlewares.php +++ b/app/setup/setups/middlewares.php @@ -15,6 +15,7 @@ return [ return new Incoviba\Middleware\API( $container->get(Psr\Http\Message\ResponseFactoryInterface::class), $container->get(Incoviba\Service\Login::class), + $container->get(Psr\Log\LoggerInterface::class), $container->get('API_KEY') ); } diff --git a/app/setup/setups/services.php b/app/setup/setups/services.php index 0c14f39..db60239 100644 --- a/app/setup/setups/services.php +++ b/app/setup/setups/services.php @@ -6,6 +6,7 @@ return [ Incoviba\Service\Login::class => function(ContainerInterface $container) { return new Incoviba\Service\Login( $container->get(Incoviba\Repository\Login::class), + $container->get(Incoviba\Repository\User::class), $container->get('COOKIE_NAME'), $container->get('MAX_LOGIN_HOURS'), $container->has('COOKIE_DOMAIN') ? $container->get('COOKIE_DOMAIN') : '', @@ -72,8 +73,8 @@ return [ ) ->register('xlsx', Incoviba\Service\Informe\Excel::class); }, - \Incoviba\Service\Contabilidad\Informe\Tesoreria\Output\Excel::class => function(ContainerInterface $container) { - return new \Incoviba\Service\Contabilidad\Informe\Tesoreria\Output\Excel( + Incoviba\Service\Contabilidad\Informe\Tesoreria\Output\Excel::class => function(ContainerInterface $container) { + return new Incoviba\Service\Contabilidad\Informe\Tesoreria\Output\Excel( $container->get(Psr\Log\LoggerInterface::class), $container->get('folders')->get('informes'), $container->get(Incoviba\Service\UF::class), diff --git a/app/src/Controller/API/Admin/Users.php b/app/src/Controller/API/Admin/Users.php new file mode 100644 index 0000000..10b2b17 --- /dev/null +++ b/app/src/Controller/API/Admin/Users.php @@ -0,0 +1,30 @@ +getParsedBody(); + $output = [ + 'input' => array_filter($body, fn($key) => $key !== 'password', ARRAY_FILTER_USE_KEY), + 'success' => false, + 'user' => null + ]; + try { + $user = $loginService->addUser($body); + $output['success'] = true; + $output['user'] = $user; + } catch (EmptyResult) {} + return $this->withJson($response, $output); + } +} diff --git a/app/src/Controller/API/Login.php b/app/src/Controller/API/Login.php new file mode 100644 index 0000000..b7ebeac --- /dev/null +++ b/app/src/Controller/API/Login.php @@ -0,0 +1,33 @@ +getParsedBody(); + $output = [ + 'username' => $body['username'], + ]; + try { + $user = $userRepository->fetchByName($body['username']); + if ($user->validate($body['password'])) { + $loginService->login($user); + $output['token'] = $loginService->getToken(); + } + } catch (EmptyResult) {} + return $this->withJson($response, $output); + } +} diff --git a/app/src/Controller/API/Money.php b/app/src/Controller/API/Money.php index 9041f58..a13d14a 100644 --- a/app/src/Controller/API/Money.php +++ b/app/src/Controller/API/Money.php @@ -9,6 +9,7 @@ use Incoviba\Controller\withRedis; use Incoviba\Service; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Log\LoggerInterface; class Money { @@ -77,6 +78,31 @@ class Money $output['uf'] = $ufService->get($date); return $this->withJson($response, $output); } + public function ufs(ServerRequestInterface $request, ResponseInterface $response, Service\Redis $redisService): ResponseInterface + { + $redisKey = 'uf'; + $output = [ + 'ufs' => [] + ]; + try { + $output['ufs'] = (array) $this->fetchRedis($redisService, $redisKey); + } catch (EmptyRedis) {} + return $this->withJson($response, $output); + } + public function updateUfs(ServerRequestInterface $request, ResponseInterface $response, + LoggerInterface $logger, + Service\UF $ufService): ResponseInterface + { + $body = $request->getParsedBody(); + $dates = array_map(function($dateData) { + return new DateTimeImmutable($dateData); + }, $body['fechas']); + $output = [ + 'input' => $body, + 'ufs' => $ufService->updateMany($dates) + ]; + return $this->withJson($response, $output); + } public function ipc(ServerRequestInterface $request, ResponseInterface $response, Service\IPC $ipcService): ResponseInterface { diff --git a/app/src/Controller/Admin/Users.php b/app/src/Controller/Admin/Users.php new file mode 100644 index 0000000..cc64ae2 --- /dev/null +++ b/app/src/Controller/Admin/Users.php @@ -0,0 +1,21 @@ +fetchAll('name'); + } catch (EmptyResult) {} + return $view->render($response, 'admin.users', compact('users')); + } +} diff --git a/app/src/Service/Login.php b/app/src/Service/Login.php index 4d658f4..1fe92c2 100644 --- a/app/src/Service/Login.php +++ b/app/src/Service/Login.php @@ -15,7 +15,10 @@ use function setcookie; class Login { - public function __construct(protected Repository\Login $repository, protected string $cookie_name, protected int $max_login_time, protected string $domain = '', protected string $path = '', protected string $cookie_separator = ':') + public function __construct(protected Repository\Login $repository, protected Repository\User $userRepository, + protected string $cookie_name, + protected int $max_login_time, protected string $domain = '', + protected string $path = '', protected string $cookie_separator = ':') { $this->loadCookie(); } @@ -23,19 +26,18 @@ class Login protected string $selector = ''; protected string $token = ''; - public function isIn(): bool + public function isIn(?string $selector = null, ?string $sentToken = null): bool { try { - $login = $this->repository->fetchActiveBySelector($this->selector); - if (!$this->validToken($login)) { + $login = $this->repository->fetchActiveBySelector($selector ?? $this->selector); + if (!$this->validToken($login, $sentToken)) { return false; } $now = new DateTimeImmutable(); if ($login->dateTime->add(new DateInterval("PT{$this->max_login_time}H")) > $now) { return true; } - } catch (PDOException|EmptyResult) { - } + } catch (PDOException|EmptyResult) {} return false; } public function getUser(): Model\User @@ -54,6 +56,23 @@ class Login { return $this->cookie_separator; } + + public function addUser(array $data): Model\User + { + try { + return $this->userRepository->fetchByName($data['name']); + } catch (EmptyResult) { + list($passphrase, $encrypted) = $this->splitPassword($data['password']); + $password = $this->cryptoJs_aes_decrypt($encrypted, $passphrase); + $password = password_hash($password, PASSWORD_DEFAULT); + $user = $this->userRepository->create([ + 'name' => $data['name'], + 'password' => $password, + 'enabled' => $data['enabled'] ?? 1 + ]); + return $this->userRepository->save($user); + } + } public function validateUser(Model\User $user, string $encryptedPassword): bool { list($passphrase, $encrypted) = $this->splitPassword($encryptedPassword); @@ -106,6 +125,10 @@ class Login return false; } } + public function parseToken(Model\Login $login): string + { + return implode($this->cookie_separator, [$login->selector, $login->token]); + } protected function loadCookie(): void { @@ -145,8 +168,11 @@ class Login ); } - protected function validToken(Model\Login $login): bool + protected function validToken(Model\Login $login, ?string $sentToken = null): bool { + if ($sentToken !== null) { + return password_verify($sentToken, $login->token); + } return password_verify($this->token, $login->token); } protected function generateToken(Model\Login $login): array diff --git a/app/src/Service/UF.php b/app/src/Service/UF.php index d1da508..ebc2cec 100644 --- a/app/src/Service/UF.php +++ b/app/src/Service/UF.php @@ -4,12 +4,14 @@ namespace Incoviba\Service; use DateTimeInterface; use DateTimeImmutable; use Incoviba\Common\Implement\Exception\EmptyRedis; +use Psr\Log\LoggerInterface; class UF { protected string $redisKey = 'uf'; - public function __construct(protected Redis $redisService, protected Money $moneyService) {} + public function __construct(protected Redis $redisService, protected Money $moneyService, + protected LoggerInterface $logger) {} public function get(?DateTimeInterface $date = null): float { @@ -25,12 +27,36 @@ class UF $uf = $ufs[$date->format('Y-m-d')]; } catch (EmptyRedis) { $uf = $this->moneyService->getUF($date); + if ($uf === 0.0) { + return 0.0; + } $ufs[$date->format('Y-m-d')] = $uf; ksort($ufs); $this->redisService->set($this->redisKey, json_encode($ufs), 60 * 60 * 24 * 30); } return $uf; } + public function updateMany(array $dates): array + { + $ufs = []; + try { + $ufs = json_decode($this->redisService->get($this->redisKey), JSON_OBJECT_AS_ARRAY); + } catch (EmptyRedis) {} + $updated = []; + foreach ($dates as $date) { + if (!isset($ufs[$date->format('Y-m-d')]) or $ufs[$date->format('Y-m-d')] === 0) { + $uf = $this->moneyService->getUF($date); + if ($uf === 0.0) { + continue; + } + $updated[$date->format('Y-m-d')] = $uf; + $ufs[$date->format('Y-m-d')] = $this->moneyService->getUF($date); + } + } + ksort($ufs); + $this->redisService->set($this->redisKey, json_encode($ufs), 60 * 60 * 24 * 30); + return $updated; + } public function transform(DateTimeInterface $date, float $input, string $from = 'uf'): float { diff --git a/cli/common/Alias/Application.php b/cli/common/Alias/Application.php index 6565ed1..2b2308f 100644 --- a/cli/common/Alias/Application.php +++ b/cli/common/Alias/Application.php @@ -1,10 +1,12 @@ add($app->getContainer()->get(Incoviba\Command\Money\UF::class)); $app->add($app->getContainer()->get(Incoviba\Command\Money\IPC::class)); +$app->add($app->getContainer()->get(Incoviba\Command\Money\UF\Update::class)); diff --git a/cli/setup/settings/folders.php b/cli/setup/settings/folders.php index 9e51323..1df85e8 100644 --- a/cli/setup/settings/folders.php +++ b/cli/setup/settings/folders.php @@ -10,6 +10,10 @@ return [ $arr['resources'], 'commands' ]); + $arr['cache'] = implode(DIRECTORY_SEPARATOR, [ + $arr['base'], + 'cache' + ]); return (object) $arr; } ]; diff --git a/cli/setup/setups/client.php b/cli/setup/setups/client.php index 12292f8..dd293a3 100644 --- a/cli/setup/setups/client.php +++ b/cli/setup/setups/client.php @@ -2,8 +2,8 @@ use Psr\Container\ContainerInterface; return [ - Psr\Http\Client\ClientInterface::class => function(ContainerInterface $container) { - return new GuzzleHttp\Client([ + Incoviba\Service\Login::class => function(ContainerInterface $container) { + $client = new GuzzleHttp\Client([ 'base_uri' => $container->get('API_URL'), 'headers' => [ 'Authorization' => [ @@ -11,5 +11,23 @@ return [ ] ] ]); + return new Incoviba\Service\Login( + $client, + $container->get(Psr\Log\LoggerInterface::class), + implode(DIRECTORY_SEPARATOR, [$container->get('folders')->cache, 'token']), + $container->get('API_USERNAME'), + $container->get('API_PASSWORD') + ); + }, + Psr\Http\Client\ClientInterface::class => function(ContainerInterface $container) { + $login = $container->get(Incoviba\Service\Login::class); + return new GuzzleHttp\Client([ + 'base_uri' => $container->get('API_URL'), + 'headers' => [ + 'Authorization' => [ + "Bearer {$login->getKey($container->get('API_KEY'))}" + ] + ] + ]); } ]; diff --git a/cli/src/Command/Full.php b/cli/src/Command/Full.php index 5c7d9d0..9eba9f2 100644 --- a/cli/src/Command/Full.php +++ b/cli/src/Command/Full.php @@ -15,6 +15,7 @@ class Full extends Command 'comunas', 'money:ipc', 'money:uf', + 'money:uf:update', 'proyectos:activos', 'ventas:cierres:vigentes', 'ventas:cuotas:hoy', diff --git a/cli/src/Command/Money/UF/Update.php b/cli/src/Command/Money/UF/Update.php new file mode 100644 index 0000000..13df8a6 --- /dev/null +++ b/cli/src/Command/Money/UF/Update.php @@ -0,0 +1,47 @@ +logger->debug("Running {$this->getName()}"); + $url = '/api/money/ufs'; + $response = $this->client->get($url); + $output->writeln("GET {$url}"); + if ($response->getStatusCode() !== 200) { + $this->logger->error("Error: [{$response->getStatusCode()}] {$response->getReasonPhrase()}"); + return Console\Command\Command::FAILURE; + } + $data = json_decode($response->getBody()->getContents(), JSON_OBJECT_AS_ARRAY)['ufs']; + $zeros = []; + foreach ($data as $date => $value) { + if ($value === 0) { + $zeros[] = $date; + } + } + if (count($zeros) > 0) { + $output->writeln('Updating ' . count($zeros) . ' UFs'); + $uri = '/api/money/ufs'; + $response = $this->client->post($uri, [ + 'body' => http_build_query(['fechas' => $zeros]), + 'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'] + ]); + $output->writeln("POST {$uri}"); + if ($response->getStatusCode() !== 200) { + $this->logger->error("Error: [{$response->getStatusCode()}] {$response->getReasonPhrase()}"); + return Console\Command\Command::FAILURE; + } + $body = json_decode($response->getBody()->getContents(), JSON_OBJECT_AS_ARRAY); + $output->writeln('Updated ' . count($body['ufs']) . ' UFs'); + } + return Console\Command\Command::SUCCESS; + } +} diff --git a/cli/src/Service/Login.php b/cli/src/Service/Login.php new file mode 100644 index 0000000..d1ba8cb --- /dev/null +++ b/cli/src/Service/Login.php @@ -0,0 +1,59 @@ +client->request('POST', $url, [ + 'body' => http_build_query([ + 'username' => $this->username, + 'password' => $this->password + ]), + 'headers' => ['Content-Type' => 'application/x-www-form-urlencoded'] + ]); + } catch (ClientExceptionInterface $exception) { + $this->logger->error($exception); + return ''; + } + + if ($response->getStatusCode() !== 200) { + return ''; + } + $body = $response->getBody()->getContents(); + $data = json_decode($body, true); + if (!key_exists('token', $data)) { + $this->logger->error('Token not found'); + return ''; + } + file_put_contents($this->tokenFilename, $data['token']); + return $data['token']; + } + public function retrieveToken(): string + { + if (!file_exists($this->tokenFilename)) { + throw new Exception('Token file not found'); + } + return file_get_contents($this->tokenFilename); + } + public function getKey(string $apiKey, string $separator = 'g'): string + { + try { + $token = $this->retrieveToken(); + } catch (Exception) { + $token = $this->login(); + } + return implode('', [md5($apiKey), $separator, $token]); + } +} diff --git a/redisinsight.compose.yml b/redisinsight.compose.yml index b813053..cc497d3 100644 --- a/redisinsight.compose.yml +++ b/redisinsight.compose.yml @@ -8,7 +8,7 @@ services: volumes: - redisinsight:/data ports: - - "5540:5540" + - "${INSIGHT_PORT:-5540}:5540" volumes: redisinsight: {}