This commit is contained in:
Juan Pablo Vial
2024-07-26 23:15:48 -04:00
parent 43bb7a83c8
commit 84861b5e57
24 changed files with 457 additions and 18 deletions

View File

@ -0,0 +1,10 @@
<?php
$app->group('/admin', function($app) {
$files = new FilesystemIterator(implode(DIRECTORY_SEPARATOR, [__DIR__, 'admin']));
foreach ($files as $file) {
if ($file->isDir()) {
continue;
}
include_once $file->getRealPath();
}
});

View File

@ -0,0 +1,6 @@
<?php
use Incoviba\Controller\Admin\Users;
$app->group('/users', function($app) {
$app->get('[/]', Users::class);
});

View File

@ -0,0 +1,13 @@
<?php
$app->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();
}
}
});

View File

@ -0,0 +1,6 @@
<?php
use Incoviba\Controller\API\Admin\Users;
$app->group('/users', function($app) {
$app->post('/add[/]', Users::class . ':add');
});

View File

@ -0,0 +1,4 @@
<?php
use Incoviba\Controller\API\Login;
$app->post('/login[/]', Login::class);

View File

@ -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']);
});

View File

@ -0,0 +1,100 @@
@extends('layout.base')
@section('page_content')
<div class="ui container">
<h2 class="ui header">Usuarios</h2>
<table class="ui table">
<thead>
<tr>
<th>Nombre</th>
<th class="right aligned">
<button class="ui mini green icon button" id="create-user-button">
<i class="plus icon"></i>
</button>
</th>
</tr>
</thead>
<tbody>
@foreach($users as $user)
<tr>
<td>{{ $user->name }}</td>
<td class="right aligned">
<button class="ui mini blue icon button">
<i class="edit icon"></i>
</button>
<button class="ui mini red icon button">
<i class="trash icon"></i>
</button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="ui modal" id="create-user-modal">
<i class="close icon"></i>
<div class="header">
Crear usuario
</div>
<div class="content">
<form class="ui form">
<div class="field">
<label>Nombre</label>
<input type="text" name="name" placeholder="Nombre">
</div>
<div class="field">
<label>Correo</label>
<input type="email" name="email" placeholder="Correo">
</div>
<div class="field">
<label>Contraseña</label>
<input type="password" name="password" placeholder="Contraseña">
</div>
</form>
</div>
<div class="actions">
<div class="ui black deny button">
Cancelar
</div>
<div class="ui positive right labeled icon button">
Crear
<i class="checkmark icon"></i>
</div>
</div>
</div>
@endsection
@include('layout.body.scripts.cryptojs')
@push('page_scripts')
<script>
function encryptPassword(password) {
const passphrase = Math.floor(Math.random() * Date.now()).toString()
const encrypted = CryptoJS.AES.encrypt(password, passphrase)
return [passphrase, encrypted.toString()].join('')
}
$(document).ready(function () {
$('#create-user-modal').modal({
onApprove: function() {
const url = '{{$urls->api}}/admin/users/add'
const method = 'post'
const body = new FormData(document.querySelector('#create-user-modal form'))
body.set('password', encryptPassword(body.get('password')))
fetchAPI(url, {method, body}).then(response => {
if (!response) {
return;
}
response.json().then(result => {
if (result.success) {
location.reload()
}
})
})
}
})
$('#create-user-button').on('click', function () {
$('#create-user-modal').modal('show')
})
});
</script>
@endpush

View File

@ -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')
);
}

View File

@ -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),

View File

@ -0,0 +1,30 @@
<?php
namespace Incoviba\Controller\API\Admin;
use Exception;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Controller\API\withJson;
use Incoviba\Service;
class Users
{
use withJson;
public function add(ServerRequestInterface $request, ResponseInterface $response, Service\Login $loginService): ResponseInterface
{
$body = $request->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);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Incoviba\Controller\API;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Incoviba\Repository;
use Incoviba\Service;
use Psr\Log\LoggerInterface;
class Login
{
use withJson;
public function __invoke(ServerRequestInterface $request, ResponseInterface $response,
Repository\User $userRepository,
Repository\Login $loginRepository,
Service\Login $loginService): ResponseInterface
{
$body = $request->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);
}
}

View File

@ -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
{

View File

@ -0,0 +1,21 @@
<?php
namespace Incoviba\Controller\Admin;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Incoviba\Common\Alias\View;
use Incoviba\Common\Implement\Exception\EmptyResult;
use Incoviba\Repository;
class Users
{
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, View $view,
Repository\User $userRepository): ResponseInterface
{
$users = [];
try {
$users = $userRepository->fetchAll('name');
} catch (EmptyResult) {}
return $view->render($response, 'admin.users', compact('users'));
}
}

View File

@ -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

View File

@ -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
{