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')
+
+
+
+
+
+ Nombre |
+
+
+ |
+
+
+
+ @foreach($users as $user)
+
+ {{ $user->name }} |
+
+
+
+ |
+
+ @endforeach
+
+
+
+
+
+
+
+
+
+ Cancelar
+
+
+ Crear
+
+
+
+
+@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: {}