This commit is contained in:
2021-06-28 23:15:13 -04:00
parent 0061a3d920
commit f4a8db56ff
93 changed files with 2422 additions and 0 deletions

6
frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
# Composer
/vendor/
composer.lock
# Cache
**/cache/

7
frontend/PHP.Dockerfile Normal file
View File

@ -0,0 +1,7 @@
FROM php:7.4-fpm
RUN docker-php-ext-install pdo pdo_mysql
RUN pecl install xdebug && docker-php-ext-enable xdebug
WORKDIR /app/frontend

View File

@ -0,0 +1,15 @@
<?php
namespace ProVM\Crypto\Common\Controller;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Views\Blade as View;
class Coins {
public function __invoke(Request $request, Response $response, View $view): Response {
return $view->render($response, 'coins.list');
}
public function get(Request $request, Response $response, View $view, $coin_id): Response {
return $view->render($response, 'coins.show', compact('coin_id'));
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace ProVM\Crypto\Common\Controller;
use Psr\Http\Message\ServerRequestInterface as Request;
use PSr\Http\Message\ResponseInterface as Response;
use Slim\Views\Blade as View;
class Home {
public function __invoke(Request $request, Response $response, View $view) {
return $view->render($response, 'home');
}
}

44
frontend/composer.json Normal file
View File

@ -0,0 +1,44 @@
{
"name": "provm/crypto-ui",
"description": "Crypto currency UI",
"type": "project",
"require": {
"slim/slim": "^4.7",
"rubellum/slim-blade-view": "^0.1.1",
"nyholm/psr7": "^1.4",
"nyholm/psr7-server": "^1.0",
"php-di/slim-bridge": "^3.1",
"zeuxisoo/slim-whoops": "^0.7.3",
"provm/models": "dev-master",
"vlucas/phpdotenv": "^5.3",
"spatie/crypto": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
"kint-php/kint": "^3.3"
},
"license": "MIT",
"authors": [
{
"name": "Aldarien",
"email": "aldarien85@gmail.com"
}
],
"autoload": {
"psr-4": {
"ProVM\\Crypto\\Common\\": "common",
"ProVM\\Crypto\\": "../src",
"ProVM\\Common\\": "../provm/common",
"ProVM\\": "../provm/src"
}
},
"repositories": [
{
"type": "git",
"url": "http://git.provm.cl/ProVM/models.git"
}
],
"config": {
"secure-http": false
}
}

22
frontend/nginx.conf Normal file
View File

@ -0,0 +1,22 @@
server {
listen ${FRONTEND_PORT} default_server;
root /app/frontend/public;
access_log /var/log/nginx/frontend.access.log;
error_log /var/log/nginx/frontend.error.log;
index index.php index.html index.htm;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ \.php$ {
fastcgi_pass frontend:9000;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_index index.php;
include fastcgi_params;
}
}

View File

View File

@ -0,0 +1,9 @@
<?php
session_start();
include_once implode(DIRECTORY_SEPARATOR, [
dirname(__DIR__),
'setup',
'app.php'
]);
$app->run();

View File

@ -0,0 +1,12 @@
<?php
use ProVM\Crypto\Common\Controller\Home;
$files = new DirectoryIterator(__DIR__ . DIRECTORY_SEPARATOR . 'web');
foreach ($files as $file) {
if ($file->isDir()) {
continue;
}
include $file->getRealPath();
}
$app->get('/', Home::class);

View File

@ -0,0 +1,11 @@
<?php
use ProVM\Crypto\Common\Controller\Coins;
$app->group('/coins', function($app) {
$app->get('/add', [Coins::class, 'add']);
$app->get('[/]', Coins::class);
});
$app->group('/coin/{coin_id}', function($app) {
$app->get('[/]', [Coins::class, 'get']);
});

View File

@ -0,0 +1,5 @@
@extends('layout.base')
@section('page_title')
Coins
@endsection

View File

@ -0,0 +1,264 @@
@extends('coins.base')
@section('page_content')
<h3 class="ui basic segment header">Monedas</h3>
<table class="ui striped table" id="coins">
<thead>
<tr>
<th>
Moneda
</th>
<th>
Código
</th>
<th class="right aligned">
<i class="plus icon"></i>
</th>
</tr>
</thead>
<tbody></tbody>
</table>
<div class="ui modal" id="coin_modal">
<div class="header"></div>
<div class="content"></div>
<div class="actions">
<div class="ui approve button"></div>
</div>
</div>
@endsection
@push('page_scripts')
<script type="text/javascript">
function reload() {
window.location.reload()
}
const coins = {
urls: {
api: '{{$urls->api}}',
base: '{{$urls->base}}'
},
modal: null,
setup: function(table, modal) {
this.modal = modal
table.find('plus icon').css('cursor', 'pointer').click(() => {
addCoin()
})
table.find('tbody').append($('<div></div>').attr('class', 'ui active dimmer').append(
$('<div></div>').attr('class', 'ui loader')
))
table.find('.plus.icon').css('cursor', 'pointer').click((e) => {
this.add()
})
this.load(table)
},
load: function(table) {
const url = this.urls.api + '/coins'
const body = table.find('tbody')
$.getJSON(url, (data) => {
body.html('')
data.coins.forEach((v) => {
const u = this.urls.base + '/coin/' + v.id
const tr = $('<tr></tr>').append(
$('<td></td>').append($('<a></a>').attr('href', u).html(v.name))
).append(
$('<td></td>').append($('<a></a>').attr('href', u).html(v.code))
).append(
$('<td></td>').attr('class', 'right aligned').append(
$('<i></i>').attr('class', 'edit icon edit_coin').attr('data-id', v.id)
).append(
$('<i></i>').attr('class', 'minus icon minus_coin').attr('data-id', v.id)
)
)
body.append(tr)
})
$('.edit_coin').css('cursor', 'pointer').click((e) => {
const elem = $(e.target)
const id = elem.attr('data-id')
this.edit(id)
})
$('.minus_coin').css('cursor', 'pointer').click((e) => {
const elem = $(e.target)
const id = elem.attr('data-id')
this.delete(id)
})
}).catch(() => {
body.html('')
})
},
add: function() {
this.modal.find('.header').html('Agregar')
this.modal.find('.actions .approve.button').html('Agregar')
this.modal.find('.content').html('')
const form = $('<form></form>').attr('class', 'ui form').append(
$('<div></div>').attr('class', 'field').append(
$('<label></label>').html('Nombre')
).append(
$('<input />').attr('type', 'text').attr('name', 'name')
)
).append(
$('<div></div>').attr('class', 'field').append(
$('<label></label>').html('Código')
).append(
$('<input />').attr('type', 'text').attr('name', 'code').attr('size', '5').attr('maxlength', '5')
)
).append(
$('<div></div>').attr('class', 'field').append(
$('<label></label>').html('Prefijo')
).append(
$('<input />').attr('type', 'text').attr('name', 'prefix')
)
).append(
$('<div></div>').attr('class', 'field').append(
$('<label></label>').html('Sufijo')
).append(
$('<input />').attr('type', 'text').attr('name', 'suffix')
)
).append(
$('<div></div>').attr('class', 'field').append(
$('<label></label>').html('Decimales')
).append(
$('<input />').attr('type', 'text').attr('name', 'decimals')
)
).append(
$('<div></div>').attr('class', 'field').append(
$('<label></label>').html('Url')
).append(
$('<input />').attr('type', 'text').attr('name', 'ref_url')
)
)
this.modal.find('.content').append(form)
this.modal.modal('show')
this.modal.modal({
onApprove: () => {
const url = this.urls.api + '/coins/add'
const fields = [
'name',
'code',
'prefix',
'suffix',
'decimals',
'ref_url'
]
let data = {}
fields.forEach((k) => {
const val = form.find("[name='" + k + "']").val()
if (val != '') {
data[k] = val
}
})
data = JSON.stringify(data)
$.post(url, data, (response) => {
if (response.created === true) {
return reload()
}
}, 'json')
}
})
},
edit: function(id) {
const url = this.urls.api + '/coin/' + id
$.getJSON(url, (data) => {
const elem = data.coin
this.modal.find('.header').html('Editar')
this.modal.find('.actions .approve.button').html('Editar')
this.modal.find('.content').html('')
const form = $('<form></form>').attr('class', 'ui form').append(
$('<div></div>').attr('class', 'field').append(
$('<label></label>').html('Nombre')
).append(
$('<input />').attr('type', 'text').attr('name', 'name').attr('value', elem.name)
)
).append(
$('<div></div>').attr('class', 'field').append(
$('<label></label>').html('Código')
).append(
$('<input />').attr('type', 'text').attr('name', 'code').attr('size', '5').attr('maxlength', '5').attr('value', elem.code)
)
).append(
$('<div></div>').attr('class', 'field').append(
$('<label></label>').html('Prefijo')
).append(
$('<input />').attr('type', 'text').attr('name', 'prefix').attr('value', elem.prefix)
)
).append(
$('<div></div>').attr('class', 'field').append(
$('<label></label>').html('Sufijo')
).append(
$('<input />').attr('type', 'text').attr('name', 'suffix').attr('value', elem.suffix)
)
).append(
$('<div></div>').attr('class', 'field').append(
$('<label></label>').html('Decimales')
).append(
$('<input />').attr('type', 'text').attr('name', 'decimals').attr('value', elem.decimals)
)
).append(
$('<div></div>').attr('class', 'field').append(
$('<label></label>').html('Url')
).append(
$('<input />').attr('type', 'text').attr('name', 'ref_url').attr('value', elem.ref_url)
)
)
this.modal.find('.content').append(form)
this.modal.modal('show')
this.modal.find('.actions .approve.button').click(() => {
const url = this.urls.api + '/coin/' + id + '/edit'
const fields = [
'name',
'code',
'prefix',
'suffix',
'decimals',
'ref_url'
]
let data = {}
fields.forEach((k) => {
const val = form.find("[name='" + k + "']").val()
if (val != '' && val != elem[k]) {
data[k] = val
}
})
data = JSON.stringify(data)
$.ajax(
{
url: url,
method: 'PUT',
data: data,
success: (response) => {
if (response.edited === true) {
return reload()
}
},
dataType: 'json'
}
)
})
})
},
delete: function(id) {
const url = this.urls.api + '/coin/' + id + '/delete'
$.ajax(
{
url: url,
method: 'DELETE',
success: (response) => {
console.debug(response)
if (response.deleted === true) {
return reload()
}
},
dataType: 'json'
}
)
}
}
$(document).ready(() => {
const modal = $('#coin_modal')
modal.modal()
modal.modal('setting', 'closable', true)
const table = $('#coins')
coins.setup(table, modal)
})
</script>
@endpush

View File

@ -0,0 +1,31 @@
@extends('coins.base')
@section('page_content')
<h3 class="ui header">Moneda - <span class="coin_name"></span></h3>
<div class="ui list" id="coin_data">
</div>
@endsection
@push('page_scripts')
<script type="text/javascript">
$(document).ready(() => {
const id = '{{$coin_id}}'
const api_url = '{{$urls->api}}'
const url = api_url + '/coin/' + id
$.getJSON(url, (data) => {
$('.coin_name').html(data.coin.name)
$('#coin_data').append(
$('<div></div>').attr('class', 'item').html('Código: ' + data.coin.code)
).append(
$('<div></div>').attr('class', 'item').html('Prefijo: ' + data.coin.prefix)
).append(
$('<div></div>').attr('class', 'item').html('Sufijo: ' + data.coin.suffix)
).append(
$('<div></div>').attr('class', 'item').html('Decimales: ' + data.coin.decimals)
).append(
$('<div></div>').attr('class', 'item').html('Url: ' + data.coin.ref_url)
)
})
})
</script>
@endpush

View File

@ -0,0 +1,5 @@
@extends('layout.base')
@section('page_content')
Home
@endsection

View File

@ -0,0 +1,5 @@
<!DOCTYPE html>
<html lang="es">
@include('layout.head')
@include('layout.body')
</html>

View File

@ -0,0 +1,16 @@
<body>
@include('layout.body.header')
<div class="ui attached segment" id="page_content">
@hasSection('page_sidebar')
<div class="ui visible sidebar inverted vertical labeled icon menu" id="page_sidebar">
@yield('page_sidebar')
</div>
<div class="pusher">
@endif
@yield('page_content')
@hasSection('page_sidebar')
</div>
@endif
</div>
@include('layout.body.footer')
</body>

View File

@ -0,0 +1,4 @@
<footer>
</footer>
@include('layout.body.scripts')

View File

@ -0,0 +1,6 @@
<header class="ui top attached menu">
<h1 class="brand item">
<a href="{{$urls->base}}">Cryptos</a>
</h1>
<a class="item" href="{{$urls->base}}/coins">Monedas</a>
</header>

View File

@ -0,0 +1,15 @@
@if (isset($page_scripts))
@foreach ($page_scripts as $script)
@if (isset($script->url))
<script src="{{$script->url}}"></script>
@elseif (isset($script->full))
{!!$script->full!!}
@endif
@endforeach
@endif
@stack('page_scripts')
<script type="text/javascript">
@stack('global_script')
</script>

View File

@ -0,0 +1,11 @@
<head>
<meta charset="utf8" />
<title>
Crypto
@hasSection('page_title')
-
@yield('page_title')
@endif
</title>
@include('layout.head.styles')
</head>

View File

@ -0,0 +1,11 @@
@if (isset($page_styles))
@foreach ($page_styles as $style)
@if (isset($style->url))
<link href="{{$style->url}}" />
@elseif (isset($style->link))
{!!$style->link!!}
@endif
@endforeach
@endif
@stack('page_styles')

52
frontend/setup/app.php Normal file
View File

@ -0,0 +1,52 @@
<?php
use DI\ContainerBuilder as Builder;
use DI\Bridge\Slim\Bridge;
include_once 'composer.php';
$builder = new Builder();
$folders = [
'env',
'common',
'web'
];
$files = [
'settings',
'setups'
];
foreach ($files as $file) {
foreach ($folders as $folder) {
$filename = implode(DIRECTORY_SEPARATOR, [
__DIR__,
$folder,
$file . '.php'
]);
if (!file_exists($filename)) {
continue;
}
$builder->addDefinitions($filename);
}
}
$container = $builder->build();
$app = Bridge::create($container);
//$app->setBasePath($container->get('base_url'));
$app->addRoutingMiddleware();
foreach ($folders as $folder) {
$filename = implode(DIRECTORY_SEPARATOR, [
__DIR__,
$folder,
'middlewares.php'
]);
if (!file_exists($filename)) {
continue;
}
include_once $filename;
}
include_once 'router.php';
$app->add(new Zeuxisoo\Whoops\Slim\WhoopsMiddleware(['enable' => $container->get('debug') ?: true]));

View File

@ -0,0 +1,9 @@
<?php
return [
'base_url' => function() {
if (isset($_ENV['BASE_URL'])) {
return $_ENV['BASE_URL'];
}
return $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'];
}
];

View File

@ -0,0 +1,6 @@
<?php
include_once implode(DIRECTORY_SEPARATOR, [
dirname(__DIR__),
'vendor',
'autoload.php'
]);

4
frontend/setup/env/settings.php vendored Normal file
View File

@ -0,0 +1,4 @@
<?php
return [
'debug' => $_ENV['DEBUG'] ?? false
];

View File

@ -0,0 +1,5 @@
<?php
include_once implode(DIRECTORY_SEPARATOR, [
$app->getContainer()->get('locations')->routes,
'web.php'
]);

View File

@ -0,0 +1,68 @@
<?php
use Psr\Container\ContainerInterface as Container;
return [
'login_time' => 5*60*60,
'locations' => function() {
$arr = ['base' => dirname(__DIR__, 2)];
$arr['resources'] = implode(DIRECTORY_SEPARATOR, [
$arr['base'],
'resources'
]);
$arr['data'] = implode(DIRECTORY_SEPARATOR, [
$arr['resources'],
'data'
]);
$arr['routes'] = implode(DIRECTORY_SEPARATOR, [
$arr['resources'],
'routes'
]);
$arr['templates'] = implode(DIRECTORY_SEPARATOR, [
$arr['resources'],
'views'
]);
$arr['cache'] = implode(DIRECTORY_SEPARATOR, [
$arr['base'],
'cache'
]);
return (object) $arr;
},
'urls' => function(Container $c) {
$arr = ['base' => $c->get('base_url'), 'api' => $_ENV['API_URL']];
return (object) $arr;
},
'scripts' => function() {
$arr = [
[
'full' => '<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>'
],
[
'full' => '<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.8.7/semantic.min.js" integrity="sha512-1Nyd5H4Aad+OyvVfUOkO/jWPCrEvYIsQENdnVXt1+Jjc4NoJw28nyRdrpOCyFH4uvR3JmH/5WmfX1MJk2ZlhgQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>'
]
];
foreach ($arr as $i => $a) {
$arr[$i] = (object) $a;
}
return $arr;
},
'styles' => function() {
$arr = [
[
'link' => '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.8.7/semantic.min.css" integrity="sha512-g/MzOGVPy3OQ4ej1U+qe4D/xhLwUn5l5xL0Fa7gdC258ZWVJQGwsbIR47SWMpRxSPjD0tfu/xkilTy+Lhrl3xg==" crossorigin="anonymous" referrerpolicy="no-referrer" />'
],
[
'url' => 'https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.8.7/themes/default/assets/fonts/brand-icons.woff2'
],
[
'url' => 'https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.8.7/themes/default/assets/fonts/icons.woff2'
],
[
'url' => 'https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.8.7/themes/default/assets/fonts/outline-icons.woff2'
]
];
foreach ($arr as $i => $a) {
$arr[$i] = (object) $a;
}
return $arr;
}
];

View File

@ -0,0 +1,17 @@
<?php
use Psr\Container\ContainerInterface as Container;
return [
Slim\Views\Blade::class => function(Container $container) {
return new Slim\Views\Blade(
$container->get('locations')->templates,
$container->get('locations')->cache,
null,
[
'urls' => $container->get('urls'),
'page_styles' => $container->get('styles'),
'page_scripts' => $container->get('scripts')
]
);
}
];