Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
4141f42ad8 | |||
3562021da7 | |||
2859574848 | |||
4a321b4105 | |||
7a6e9dec95 | |||
8e1e644904 | |||
c930ffc55e | |||
8c870cb43f | |||
f4a8db56ff | |||
0061a3d920 |
4
.common.env.sample
Normal file
4
.common.env.sample
Normal file
@ -0,0 +1,4 @@
|
||||
DEBUG=true
|
||||
DB_HOST="db"
|
||||
ENV='dev'
|
||||
API_URL='http://localhost:8081'
|
4
.db.env.sample
Normal file
4
.db.env.sample
Normal file
@ -0,0 +1,4 @@
|
||||
MYSQL_ROOT_PASSWORD=
|
||||
MYSQL_USER=
|
||||
MYSQL_PASSWORD=
|
||||
MYSQL_DATABASE=
|
2
.env.sample
Normal file
2
.env.sample
Normal file
@ -0,0 +1,2 @@
|
||||
FRONTEND_PORT=#Port for frontend UI
|
||||
BACKEND_PORT=#Port for backend app
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
# Env files
|
||||
**/*.env
|
||||
|
||||
# Logs
|
||||
**/logs/
|
2
backend/.gitignore
vendored
Normal file
2
backend/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Certs
|
||||
**/certs/
|
9
backend/Cron.Dockerfile
Normal file
9
backend/Cron.Dockerfile
Normal file
@ -0,0 +1,9 @@
|
||||
FROM ubuntu
|
||||
|
||||
RUN apt-get update && apt-get install -y cron
|
||||
|
||||
COPY ./automation/crontab /var/spool/cron/crontabs/backend
|
||||
|
||||
#ENTRYPOINT [ "/bin/bash" ]
|
||||
|
||||
CMD ["cron", "-f"]
|
7
backend/PHP.Dockerfile
Normal file
7
backend/PHP.Dockerfile
Normal 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/backend/api
|
13
backend/Py.Dockerfile
Normal file
13
backend/Py.Dockerfile
Normal file
@ -0,0 +1,13 @@
|
||||
FROM python:3.9 as runtime
|
||||
|
||||
WORKDIR /app/
|
||||
|
||||
COPY ./python/ /app/
|
||||
|
||||
RUN python -m pip install gunicorn httpx flask
|
||||
|
||||
EXPOSE 5001
|
||||
|
||||
CMD ["gunicorn", "-b", "0.0.0.0:5001", "crypto.index:app"]
|
||||
|
||||
#ENTRYPOINT [ "/bin/bash" ]
|
3
backend/api/.gitignore
vendored
Normal file
3
backend/api/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Composer
|
||||
/vendor/
|
||||
composer.lock
|
29
backend/api/Readme.md
Normal file
29
backend/api/Readme.md
Normal file
@ -0,0 +1,29 @@
|
||||
# API
|
||||
|
||||
## Concepts
|
||||
+ [x] Coins
|
||||
+ [x] Wallets
|
||||
+ Name
|
||||
+ Public Address
|
||||
+ [x] Location
|
||||
+ Home PC
|
||||
+ Notebook
|
||||
+ Exchange
|
||||
+ [x] Transactions -
|
||||
Read from blockchain
|
||||
+ [x] Values
|
||||
+ [ ] In USD
|
||||
+ [ ] In CLF
|
||||
+ [ ] In BTC
|
||||
|
||||
## Actions
|
||||
|
||||
+ List
|
||||
+ Show/Read
|
||||
+ Add
|
||||
+ Edit
|
||||
+ Delete
|
||||
|
||||
+ [x] Coins
|
||||
+ [x] Wallets
|
||||
+ [x] Locations
|
46
backend/api/common/Controller/API.php
Normal file
46
backend/api/common/Controller/API.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
namespace ProVM\Crypto\Common\Controller;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use ProVM\Common\Define\Controller\JSON;
|
||||
|
||||
class API {
|
||||
use JSON;
|
||||
|
||||
public function __invoke(Request $request, Response $response): Response {
|
||||
$output = [
|
||||
'version' => '1.0.0',
|
||||
'routes' => [
|
||||
'/coins' => [
|
||||
'/' => 'List all coins',
|
||||
'/add' => 'Add coin'
|
||||
],
|
||||
'/coin/{coin_id}' => [
|
||||
'/' => 'Show coin information',
|
||||
'/edit' => 'Edit coin',
|
||||
'/delete' => 'Delete coin'
|
||||
],
|
||||
'/locations' => [
|
||||
'/' => 'List all locations',
|
||||
'/add' => 'Add location'
|
||||
],
|
||||
'/location/{location_id}' => [
|
||||
'/' => 'Show location information',
|
||||
'/edit' => 'Edit location',
|
||||
'/delete' => 'Delete location'
|
||||
],
|
||||
'/wallets' => [
|
||||
'/' => 'List all wallets',
|
||||
'/add' => 'Add wallet'
|
||||
],
|
||||
'/wallet/{wallet_id}' => [
|
||||
'/' => 'Show wallet information',
|
||||
'/edit' => 'Edit wallet',
|
||||
'/delete' => 'Delete wallet'
|
||||
]
|
||||
]
|
||||
];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
}
|
170
backend/api/common/Controller/Coins.php
Normal file
170
backend/api/common/Controller/Coins.php
Normal file
@ -0,0 +1,170 @@
|
||||
<?php
|
||||
namespace ProVM\Crypto\Common\Controller;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use ProVM\Common\Define\Controller\JSON;
|
||||
use ProVM\Common\Factory\Model as ModelFactory;
|
||||
use ProVM\Crypto\Coin;
|
||||
|
||||
class Coins {
|
||||
use JSON;
|
||||
|
||||
public function __invoke(Request $request, Response $response, ModelFactory $factory): Response {
|
||||
$coins = $factory->find(Coin::class)->array();
|
||||
usort($coins, function($a, $b) {
|
||||
return strcmp($a['code'], $b['code']);
|
||||
});
|
||||
$output = compact('coins');
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function show(Request $request, Response $response, ModelFactory $factory, $coin_id): Response {
|
||||
$coin = $factory->find(Coin::class)->one($coin_id);
|
||||
if (!$coin) {
|
||||
return $this->withJson($response, ['coin' => null]);
|
||||
}
|
||||
$output = ['coin' => $coin->toArray()];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function add(Request $request, Response $response, ModelFactory $factory): Response {
|
||||
$post = $request->getBody()->getContents();
|
||||
$post = json_decode($post);
|
||||
$coin = Coin::add($factory, $post);
|
||||
$status = false;
|
||||
if ($coin->isNew()) {
|
||||
$status = $coin->save();
|
||||
}
|
||||
$output = [
|
||||
'input' => $post,
|
||||
'coin' => $coin->toArray(),
|
||||
'created' => $status
|
||||
];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function edit(Request $request, Response $response, ModelFactory $factory, $coin_id): Response {
|
||||
$coin = $factory->find(Coin::class)->one($coin_id);
|
||||
if (!$coin) {
|
||||
return $this->withJson($response, ['coin' => null]);
|
||||
}
|
||||
$post = json_decode($request->getBody()->getContents());
|
||||
$edited = $coin->edit($post);
|
||||
$output = ['input' => $post, 'coin' => $coin->toArray(), 'edited' => $edited];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function delete(Request $request, Response $response, ModelFactory $factory, $coin_id): Response {
|
||||
$coin = $factory->find(Coin::class)->one($coin_id);
|
||||
if (!$coin) {
|
||||
return $this->withJson($response, ['coin' => null, 'deleted' => false]);
|
||||
}
|
||||
$output = [
|
||||
'coin' => $coin->toArray()
|
||||
];
|
||||
$status = $coin->delete();
|
||||
$output['deleted'] = $status;
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function values(Request $request, Response $response, ModelFactory $factory, $coin_id): Response {
|
||||
$coin = $factory->find(Coin::class)->one($coin_id);
|
||||
if (!$coin) {
|
||||
return $this->withJson($response, ['coin' => null, 'values' => []]);
|
||||
}
|
||||
$values = $coin->values();
|
||||
if ($values === null) {
|
||||
return $this->withJson($response, [
|
||||
'coin' => $coin->toArray(),
|
||||
'values' => []
|
||||
]);
|
||||
}
|
||||
usort($values, function($a, $b) {
|
||||
return $a->dateTime()->timestamp - $b->dateTime()->timestamp;
|
||||
});
|
||||
$values = array_map(function($item) {
|
||||
$arr = $item->toArray();
|
||||
$arr['formatted'] = $item->unit()->format($item->value);
|
||||
return $arr;
|
||||
}, $values);
|
||||
$output = [
|
||||
'coin' => $coin->toArray(),
|
||||
'values' => $values
|
||||
];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function valuesMonth(Request $request, Response $response, ModelFactory $factory, $coin_id): Response {
|
||||
$coin = $factory->find(Coin::class)->one($coin_id);
|
||||
if (!$coin) {
|
||||
return $this->withJson($response, ['coin' => null, 'values' => []]);
|
||||
}
|
||||
$values = $coin->values('month');
|
||||
if ($values === null) {
|
||||
return $this->withJson($response, [
|
||||
'coin' => $coin->toArray(),
|
||||
'values' => []
|
||||
]);
|
||||
}
|
||||
usort($values, function($a, $b) {
|
||||
return $a->dateTime()->timestamp - $b->dateTime()->timestamp;
|
||||
});
|
||||
$values = array_map(function($item) {
|
||||
$arr = $item->toArray();
|
||||
$arr['formatted'] = $item->unit()->format($item->value);
|
||||
return $arr;
|
||||
}, $values);
|
||||
$output = [
|
||||
'coin' => $coin->toArray(),
|
||||
'values' => $values
|
||||
];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function valuesSixMonths(Request $request, Response $response, ModelFactory $factory, $coin_id): Response {
|
||||
$coin = $factory->find(Coin::class)->one($coin_id);
|
||||
if (!$coin) {
|
||||
return $this->withJson($response, ['coin' => null, 'values' => []]);
|
||||
}
|
||||
$values = $coin->values('months');
|
||||
if ($values === null) {
|
||||
return $this->withJson($response, [
|
||||
'coin' => $coin->toArray(),
|
||||
'values' => []
|
||||
]);
|
||||
}
|
||||
usort($values, function($a, $b) {
|
||||
return $a->dateTime()->timestamp - $b->dateTime()->timestamp;
|
||||
});
|
||||
$values = array_map(function($item) {
|
||||
$arr = $item->toArray();
|
||||
$arr['formatted'] = $item->unit()->format($item->value);
|
||||
return $arr;
|
||||
}, $values);
|
||||
$output = [
|
||||
'coin' => $coin->toArray(),
|
||||
'values' => $values
|
||||
];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function valuesYear(Request $request, Response $response, ModelFactory $factory, $coin_id): Response {
|
||||
$coin = $factory->find(Coin::class)->one($coin_id);
|
||||
if (!$coin) {
|
||||
return $this->withJson($response, ['coin' => null, 'values' => []]);
|
||||
}
|
||||
$values = $coin->values('year');
|
||||
if ($values === null) {
|
||||
return $this->withJson($response, [
|
||||
'coin' => $coin->toArray(),
|
||||
'values' => []
|
||||
]);
|
||||
}
|
||||
usort($values, function($a, $b) {
|
||||
return $a->dateTime()->timestamp - $b->dateTime()->timestamp;
|
||||
});
|
||||
$values = array_map(function($item) {
|
||||
$arr = $item->toArray();
|
||||
$arr['formatted'] = $item->unit()->format($item->value);
|
||||
return $arr;
|
||||
}, $values);
|
||||
$output = [
|
||||
'coin' => $coin->toArray(),
|
||||
'values' => $values
|
||||
];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
}
|
70
backend/api/common/Controller/Locations.php
Normal file
70
backend/api/common/Controller/Locations.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
namespace ProVM\Crypto\Common\Controller;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use ProVM\Common\Define\Controller\JSON;
|
||||
use ProVM\Common\Factory\Model as ModelFactory;
|
||||
use ProVM\Crypto\Location;
|
||||
|
||||
class Locations {
|
||||
use JSON;
|
||||
|
||||
public function __invoke(Request $request, Response $response, ModelFactory $factory): Response {
|
||||
$locations = $factory->find(Location::class)->array();
|
||||
$output = compact('locations');
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function show(Request $request, Response $response, ModelFactory $factory, $location_id): Response {
|
||||
$location = $factory->find(Location::class)->one($location_id);
|
||||
if (!$location) {
|
||||
return $this->withJson($response, ['location' => null]);
|
||||
}
|
||||
$output = ['location' => $location->asArray()];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function add(Request $request, Response $response, ModelFactory $factory): Response {
|
||||
$post = json_decode($request->getBody()->getContents());
|
||||
$fields = [
|
||||
'name' => 'name',
|
||||
'description' => 'description'
|
||||
];
|
||||
$data = array_combine($fields, array_merge(array_intersect_key((array) $post, $fields), array_fill_keys(array_keys(array_diff_key($fields, (array) $post)), null)));
|
||||
$location = $factory->find(Location::class)->where([
|
||||
['name', $data['name']]
|
||||
])->one();
|
||||
$status = true;
|
||||
if (!$location) {
|
||||
$location = $factory->create(Location::class, $data);
|
||||
$status = $location->save();
|
||||
}
|
||||
$output = [
|
||||
'information_provided' => $post,
|
||||
'used_data' => $data,
|
||||
'location' => $location->asArray(),
|
||||
'saved' => $status
|
||||
];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function edit(Request $request, Response $response, ModelFactory $factory, $location_id): Response {
|
||||
$location = $factory->find(Location::class)->one($location_id);
|
||||
if (!$location) {
|
||||
return $this->withJson($response, ['location' => null]);
|
||||
}
|
||||
$post = json_decode($request->getBody()->getContents());
|
||||
$output = compact('location');
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function delete(Request $request, Response $response, ModelFactory $factory, $location_id): Response {
|
||||
$location = $factory->find(Location::class)->one($location_id);
|
||||
if (!$location) {
|
||||
return $this->withJson($response, ['location' => null, 'deleted' => false]);
|
||||
}
|
||||
$output = [
|
||||
'location' => $location->asArray()
|
||||
];
|
||||
$status = $location->delete();
|
||||
$output['deleted'] = $status;
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
}
|
27
backend/api/common/Controller/Update.php
Normal file
27
backend/api/common/Controller/Update.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
namespace ProVM\Crypto\Common\Controller;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use ProVM\Common\Define\Controller\JSON;
|
||||
use ProVM\Crypto\Common\Service\Update as Updater;
|
||||
|
||||
class Update {
|
||||
use JSON;
|
||||
|
||||
public function __invoke(Request $request, Response $response, Updater $updater) {
|
||||
$result = $updater->run();
|
||||
$output = [
|
||||
'result' => $result
|
||||
];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function register(Request $request, Response $response, Updater $updater, $coin_id, $type) {
|
||||
$result = $updater->register($coin_id, $type);
|
||||
$output = [
|
||||
'input' => ['coin_id' => $coin_id, 'type' => $type],
|
||||
'result' => $result
|
||||
];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
}
|
71
backend/api/common/Controller/Wallets.php
Normal file
71
backend/api/common/Controller/Wallets.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
namespace ProVM\Crypto\Common\Controller;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use ProVM\Common\Define\Controller\JSON;
|
||||
use ProVM\Common\Factory\Model as ModelFactory;
|
||||
use ProVM\Crypto\Wallet;
|
||||
|
||||
class Wallets {
|
||||
use JSON;
|
||||
|
||||
public function __invoke(Request $request, Response $response, ModelFactory $factory): Response {
|
||||
$wallets = $factory->find(Wallet::class)->array();
|
||||
$output = compact('wallets');
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function show(Request $request, Response $response, ModelFactory $factory, $wallet_id): Response {
|
||||
$wallet = $factory->find(Wallet::class)->one($wallet_id);
|
||||
if (!$wallet) {
|
||||
return $this->withJson($response, ['wallet' => null]);
|
||||
}
|
||||
$output = ['wallet' => $wallet->asArray()];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function add(Request $request, Response $response, ModelFactory $factory): Response {
|
||||
$post = json_decode($request->getBody()->getContents());
|
||||
$fields = [
|
||||
'name' => 'name',
|
||||
'location' => 'location_id',
|
||||
'address' => 'public_address'
|
||||
];
|
||||
$data = array_combine($fields, array_merge(array_intersect_key((array) $post, $fields), array_fill_keys(array_keys(array_diff_key($fields, (array) $post)), null)));
|
||||
$wallet = $factory->find(Wallet::class)->where([
|
||||
['name', $data['name']]
|
||||
])->one();
|
||||
$status = true;
|
||||
if (!$wallet) {
|
||||
$wallet = $factory->create(Wallet::class, $data);
|
||||
$status = $wallet->save();
|
||||
}
|
||||
$output = [
|
||||
'information_provided' => $post,
|
||||
'used_data' => $data,
|
||||
'wallet' => $wallet->asArray(),
|
||||
'saved' => $status
|
||||
];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function edit(Request $request, Response $response, ModelFactory $factory, $wallet_id): Response {
|
||||
$wallet = $factory->find(Wallet::class)->one($wallet_id);
|
||||
if (!$wallet) {
|
||||
return $this->withJson($response, ['wallet' => null]);
|
||||
}
|
||||
$post = json_decode($request->getBody()->getContents());
|
||||
$output = compact('wallet');
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function delete(Request $request, Response $response, ModelFactory $factory, $wallet_id): Response {
|
||||
$wallet = $factory->find(Wallet::class)->one($wallet_id);
|
||||
if (!$wallet) {
|
||||
return $this->withJson($response, ['wallet' => null, 'deleted' => false]);
|
||||
}
|
||||
$output = [
|
||||
'wallet' => $wallet->asArray()
|
||||
];
|
||||
$status = $wallet->delete();
|
||||
$output['deleted'] = $status;
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
}
|
7
backend/api/common/Factory/Model.php
Normal file
7
backend/api/common/Factory/Model.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
namespace ProVM\Crypto\Common\Factory;
|
||||
|
||||
use ProVM\Common\Factory\Model as BaseFactory;
|
||||
|
||||
class Model extends BaseFactory {
|
||||
}
|
38
backend/api/common/Middleware/CORS.php
Normal file
38
backend/api/common/Middleware/CORS.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
namespace ProVM\Crypto\Common\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface as Handler;
|
||||
use Slim\Routing\RouteContext;
|
||||
|
||||
final class CORS implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* Invoke middleware.
|
||||
*
|
||||
* @param ServerRequestInterface $request The request
|
||||
* @param RequestHandlerInterface $handler The handler
|
||||
*
|
||||
* @return ResponseInterface The response
|
||||
*/
|
||||
public function process(Request $request, Handler $handler): Response {
|
||||
$routeContext = RouteContext::fromRequest($request);
|
||||
$routingResults = $routeContext->getRoutingResults();
|
||||
$methods = $routingResults->getAllowedMethods();
|
||||
$requestHeaders = $request->getHeaderLine('Access-Control-Request-Headers');
|
||||
|
||||
$response = $handler->handle($request);
|
||||
|
||||
/*$response = $response
|
||||
->withHeader('Access-Control-Allow-Origin', '*')
|
||||
->withHeader('Access-Control-Allow-Methods', implode(', ', $methods))
|
||||
->withHeader('Access-Control-Allow-Headers', $requestHeaders ?: '*');*/
|
||||
|
||||
// Optional: Allow Ajax CORS requests with Authorization header
|
||||
$response = $response->withHeader('Access-Control-Allow-Credentials', 'true');
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
49
backend/api/common/Service/Auth.php
Normal file
49
backend/api/common/Service/Auth.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
namespace ProVM\Crypto\Common\Service;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use ProVM\Common\Factory\Model as ModelFactory;
|
||||
use ProVM\Crypt\User;
|
||||
use ProVM\Crypt\Login;
|
||||
|
||||
class Auth {
|
||||
protected $login_time;
|
||||
public function __construct(int $login_time) {
|
||||
$this->login_time = $login_time;
|
||||
}
|
||||
protected $factory;
|
||||
public function setFactory(ModelFactory $factory) {
|
||||
$this->factory = $factory;
|
||||
}
|
||||
protected function createToken() {
|
||||
return password_hash(random_bytes(100), \PASSWORD_BCRYPT);
|
||||
}
|
||||
public function login(string $username, string $password) {
|
||||
$user = $this->factory->find(User::class)->where([['name', $username]])->one();
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
if (!password_verify($password, $user->password)) {
|
||||
return false;
|
||||
}
|
||||
$now = Carbon::now();
|
||||
$login = $this->factory->find(Login::class)->where([['user_id', $user->id], ['date_time', $now->copy()->subSeconds($this->login_time), '>=']])->one();
|
||||
if (!$login) {
|
||||
$token = $this->createToken();
|
||||
$data = [
|
||||
'user_id' => $user->id,
|
||||
'token' => $token,
|
||||
'date_time' => $now->format('Y-m-d H:i:s')
|
||||
];
|
||||
$login = $this->factory->create(Login::class, $data);
|
||||
} else {
|
||||
$login->date($now);
|
||||
}
|
||||
$login->save();
|
||||
return $token;
|
||||
}
|
||||
public function isLoggedIn($token) {
|
||||
$login = $this->factory->find(Login::class)->where([['token', $token]])->one();
|
||||
return $login->user();
|
||||
}
|
||||
}
|
177
backend/api/common/Service/Update.php
Normal file
177
backend/api/common/Service/Update.php
Normal file
@ -0,0 +1,177 @@
|
||||
<?php
|
||||
namespace ProVM\Crypto\Common\Service;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use Carbon\Carbon;
|
||||
use ProVM\Common\Factory\Model as Factory;
|
||||
use ProVM\Crypto\Coin;
|
||||
use ProVM\Crypto\Value;
|
||||
|
||||
class Update {
|
||||
protected $factory;
|
||||
protected $client;
|
||||
public function __construct(Factory $factory, Client $client) {
|
||||
$this->factory = $factory;
|
||||
$this->client = $client;
|
||||
$this->load();
|
||||
}
|
||||
public function load() {
|
||||
$this->coins = [[], []];
|
||||
$results = \ORM::for_table('coin_registers')->find_many();
|
||||
foreach ($results as $result) {
|
||||
$this->coins[$result->type] []= $result->coin_id;
|
||||
}
|
||||
}
|
||||
protected $coins;
|
||||
public function register(int $coin_id, int $type = 0) {
|
||||
if (array_search($coin_id, $this->coins[$type]) !== false) {
|
||||
return false;
|
||||
}
|
||||
$this->coins[$type] []= $coin_id;
|
||||
$this->getHistorical($coin_id, $type);
|
||||
$check = \ORM::for_table('coin_registers')->where('coin_id', $coin_id)->find_one();
|
||||
if (!$check) {
|
||||
return \ORM::raw_execute("INSERT INTO coin_registers (coin_id, type, date_Time) VALUES (?, ?, NOW())", [$coin_id, $type]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
protected function getHistorical(int $coin_id, int $type) {
|
||||
$coin = $this->factory->find(Coin::class)->one($coin_id);
|
||||
$f = Carbon::now();
|
||||
$url = [];
|
||||
switch ($type) {
|
||||
case 0:
|
||||
return $this->getHistoricalCrypto($coin);
|
||||
case 1:
|
||||
return $this->getHistoricalIndicador($coin);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
protected function getHistoricalCrypto(Coin $coin) {
|
||||
$f = Carbon::now();
|
||||
$url = [];
|
||||
$url []= 'crypto';
|
||||
$url []= 'historical';
|
||||
$url []= $coin->identifier;
|
||||
$url []= $f->copy()->subYears(10)->timestamp;
|
||||
$url []= $f->timestamp;
|
||||
$url = implode('/', $url);
|
||||
$to = $this->factory->find(Coin::class)->where([['code', 'USD']])->one();
|
||||
$response = $this->client->get($url);
|
||||
$results = json_decode($response->getBody()->getContents());
|
||||
$created = [];
|
||||
foreach ($results->prices as $result) {
|
||||
$d = Carbon::createFromTimestamp(substr($result[0], 0, -3));
|
||||
$value = $result[1];
|
||||
$data = [
|
||||
'date_time' => $d->format('Y-m-d H:i:s'),
|
||||
'coin_id' => $coin->id,
|
||||
'value' => $value,
|
||||
'unit_id' => $to->id
|
||||
];
|
||||
$value = Value::add($this->factory, $data);
|
||||
$status = $value->save();
|
||||
$created []= [
|
||||
'value' => $value->toArray(),
|
||||
'created' => $status
|
||||
];
|
||||
}
|
||||
return $created;
|
||||
}
|
||||
protected function getHistoricalIndicador(Coin $coin) {
|
||||
$f = Carbon::now();
|
||||
$urls = [];
|
||||
for ($i = 10; $i >= 0; $i --) {
|
||||
$url = [];
|
||||
$url []= 'indicador';
|
||||
$url []= 'historical';
|
||||
$url []= $coin->identifier;
|
||||
$url []= $f->copy()->subYears($i)->year;
|
||||
$urls []= implode('/', $url);
|
||||
}
|
||||
$to = $this->factory->find(Coin::class)->where([['code', 'CLP']])->one();
|
||||
$created = [];
|
||||
foreach ($urls as $url) {
|
||||
$response = $this->client->get($url);
|
||||
$results = json_decode($response->getBody()->getContents());
|
||||
foreach ($results->serie as $result) {
|
||||
$d = Carbon::parse($result->fecha);
|
||||
$value = $result->valor;
|
||||
$data = [
|
||||
'date_time' => $d->format('Y-m-d H:i:s'),
|
||||
'coin_id' => $coin->id,
|
||||
'value' => $value,
|
||||
'unit_id' => $to->id
|
||||
];
|
||||
$value = Value::add($this->factory, $data);
|
||||
$status = $value->save();
|
||||
$created []= [
|
||||
'value' => $value->toArray(),
|
||||
'created' => $status
|
||||
];
|
||||
}
|
||||
}
|
||||
return $created;
|
||||
}
|
||||
public function run() {
|
||||
$created = [];
|
||||
$created = array_merge($created, $this->getCryptos($this->coins[0]));
|
||||
$created = array_merge($created, $this->getIndicadores($this->coins[1]));
|
||||
return $created;
|
||||
}
|
||||
protected function getCryptos(array $coins) {
|
||||
$created = [];
|
||||
foreach ($coins as $coin_id) {
|
||||
$coin = $this->factory->find(Coin::class)->one($coin_id);
|
||||
$url = [];
|
||||
$url []= 'crypto';
|
||||
$url []= $coin->identifier;
|
||||
$url = implode('/', $url);
|
||||
$to = $this->factory->find(Coin::class)->where([['code', 'USD']])->one();
|
||||
$response = $this->client->get($url);
|
||||
$results = json_decode($response->getBody()->getContents());
|
||||
$data = [
|
||||
'date_time' => $results->n->last_updated_at,
|
||||
'coin_id' => $coin->id,
|
||||
'value' => 1 / $results->n->usd,
|
||||
'unit_id' => $to->id
|
||||
];
|
||||
$value = Value::add($this->factory, $data);
|
||||
$status = $value->save();
|
||||
$created []= [
|
||||
'value' => $value->toArray(),
|
||||
'created' => $status
|
||||
];
|
||||
}
|
||||
return $created;
|
||||
}
|
||||
protected function getIndicadores(array $coins) {
|
||||
$created = [];
|
||||
foreach ($coins as $coin_id) {
|
||||
$coin = $this->factory->find(Coin::class)->one($coin_id);
|
||||
$f = Carbon::now();
|
||||
$url = [];
|
||||
$url []= 'indicador';
|
||||
$url []= $coin->identifier;
|
||||
$url []= $f->format('d-m-Y');
|
||||
$url = implode('/', $url);
|
||||
$to = $this->factory->find(Coin::class)->where([['code', 'CLP']])->one();
|
||||
$response = $this->client->get($url);
|
||||
$results = json_decode($response->getBody()->getContents());
|
||||
$data = [
|
||||
'date_time' => $results->serie[0]->fecha,
|
||||
'coin_id' => $coin->id,
|
||||
'value' => $results->serie[0]->valor,
|
||||
'unit_id' => $to->id
|
||||
];
|
||||
$value = Value::add($this->factory, $data);
|
||||
$status = $value->save();
|
||||
$created []= [
|
||||
'value' => $value->toArray(),
|
||||
'created' => $status
|
||||
];
|
||||
}
|
||||
return $created;
|
||||
}
|
||||
}
|
45
backend/api/composer.json
Normal file
45
backend/api/composer.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "provm/crypto",
|
||||
"description": "Crypto currency API",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"slim/slim": "^4.7",
|
||||
"php-di/slim-bridge": "^3.1",
|
||||
"nyholm/psr7": "^1.4",
|
||||
"nyholm/psr7-server": "^1.0",
|
||||
"zeuxisoo/slim-whoops": "^0.7.3",
|
||||
"provm/models": "^1.0-rc",
|
||||
"spatie/crypto": "^2.0",
|
||||
"robmorgan/phinx": "^0.12.5",
|
||||
"nesbot/carbon": "^2.49",
|
||||
"guzzlehttp/guzzle": "^7.3"
|
||||
},
|
||||
"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
|
||||
}
|
||||
}
|
73
backend/api/db/migrations/20210621171837_create_tables.php
Normal file
73
backend/api/db/migrations/20210621171837_create_tables.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Phinx\Migration\AbstractMigration;
|
||||
|
||||
final class CreateTables extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* Change Method.
|
||||
*
|
||||
* Write your reversible migrations using this method.
|
||||
*
|
||||
* More information on writing migrations is available here:
|
||||
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
|
||||
*
|
||||
* Remember to call "create()" or "update()" and NOT "save()" when working
|
||||
* with the Table class.
|
||||
*/
|
||||
public function change(): void
|
||||
{
|
||||
$this->table('coins')
|
||||
->addColumn('identifier', 'string')
|
||||
->addColumn('code', 'string', ['limit' => 5])
|
||||
->addColumn('name', 'string')
|
||||
->addColumn('prefix', 'string', ['default' => ''])
|
||||
->addColumn('suffix', 'string', ['default' => ''])
|
||||
->addColumn('decimals', 'integer', ['default' => 0])
|
||||
->addColumn('ref_url', 'text', ['default' => ''])
|
||||
->create();
|
||||
|
||||
$this->table('locations')
|
||||
->addColumn('name', 'string')
|
||||
->addColumn('description', 'string')
|
||||
->create();
|
||||
|
||||
$cascade = ['update' => 'CASCADE', 'delete' => 'CASCADE'];
|
||||
|
||||
$this->table('wallets')
|
||||
->addColumn('name', 'string')
|
||||
->addColumn('location_id', 'integer')
|
||||
->addForeignKey('location_id', 'locations', 'id', $cascade)
|
||||
->addColumn('public_address', 'string')
|
||||
->create();
|
||||
|
||||
$this->table('values')
|
||||
->addColumn('date_time', 'datetime')
|
||||
->addColumn('coin_id', 'integer')
|
||||
->addForeignKey('coin_id', 'coins', 'id', $cascade)
|
||||
->addColumn('value', 'double')
|
||||
->addColumn('unit_id', 'integer')
|
||||
->addForeignKey('unit_id', 'coins', 'id', $cascade)
|
||||
->create();
|
||||
|
||||
$this->table('transactions')
|
||||
->addColumn('date_time', 'datetime')
|
||||
->addColumn('coin_id', 'integer')
|
||||
->addForeignKey('coin_id', 'coins', 'id', $cascade)
|
||||
->addColumn('wallet_id', 'integer')
|
||||
->addForeignKey('wallet_id', 'wallets', 'id', $cascade)
|
||||
->addColumn('amount', 'double')
|
||||
->addColumn('value', 'double')
|
||||
->addColumn('unit_id', 'integer')
|
||||
->addForeignKey('unit_id', 'coins', 'id', $cascade)
|
||||
->create();
|
||||
|
||||
$this->table('coin_registers')
|
||||
->addColumn('type', 'integer')
|
||||
->addColumn('coin_id', 'integer')
|
||||
->addForeignKey('coin_id', 'coins', 'id')
|
||||
->addColumn('date_time', 'datetime')
|
||||
->create();
|
||||
}
|
||||
}
|
89
backend/api/db/migrations/schema.yml
Normal file
89
backend/api/db/migrations/schema.yml
Normal file
@ -0,0 +1,89 @@
|
||||
name: crypto
|
||||
tables:
|
||||
- name: coins
|
||||
columns:
|
||||
- name: id
|
||||
type: int
|
||||
unsigned: true
|
||||
auto_increment: true
|
||||
- name: code
|
||||
type: varchar(5)
|
||||
- name: name
|
||||
type: varchar(200)
|
||||
- name: ref_url
|
||||
type: text
|
||||
primary_key: id
|
||||
- name: locations
|
||||
columns:
|
||||
- name: id
|
||||
type: int
|
||||
unsigned: true
|
||||
auto_increment: true
|
||||
- name: name
|
||||
type: varchar(100)
|
||||
- name: description
|
||||
type: varchar(200)
|
||||
primary_key: id
|
||||
- name: transactions
|
||||
columns:
|
||||
- name: id
|
||||
type: int
|
||||
unsigned: true
|
||||
auto_increment: true
|
||||
- name: date_time
|
||||
type: datetime
|
||||
- name: coin_id
|
||||
type: int
|
||||
unsigned: true
|
||||
- name: wallet_id
|
||||
type: int
|
||||
unsigned: true
|
||||
- name: amount
|
||||
type: double
|
||||
- name: value
|
||||
type: double
|
||||
- name: unit_id
|
||||
type: int
|
||||
unsigned: true
|
||||
primary_key: id
|
||||
foreign_keys:
|
||||
- table: coins
|
||||
local_column: coin_id
|
||||
foreign_column: id
|
||||
- table: wallets
|
||||
local_column: wallet_id
|
||||
foreign_column: id
|
||||
- table: coins
|
||||
local_column: unit_id
|
||||
foreign_column: id
|
||||
- name: values
|
||||
columns:
|
||||
- name: id
|
||||
type: int
|
||||
unsigned: true
|
||||
auto_increment: true
|
||||
- name: date_time
|
||||
type: datetime
|
||||
- name: coin_id
|
||||
type: int
|
||||
unsigned: true
|
||||
- name: value
|
||||
type: double
|
||||
- name: unit_id
|
||||
type: int
|
||||
unsigned: true
|
||||
primary_key: id
|
||||
foreign_keys:
|
||||
- table: coins
|
||||
local_column: coin_id
|
||||
foreign_column: id
|
||||
- table: coins
|
||||
local_column: unit_id
|
||||
foreign_column: id
|
||||
- name: wallets
|
||||
columns:
|
||||
- name: id
|
||||
- name: name
|
||||
- name: location_id
|
||||
- name: public_address
|
||||
primary_key: id
|
41
backend/api/phinx.php
Normal file
41
backend/api/phinx.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
return
|
||||
[
|
||||
'paths' => [
|
||||
'migrations' => '%%PHINX_CONFIG_DIR%%/db/migrations',
|
||||
'seeds' => '%%PHINX_CONFIG_DIR%%/db/seeds'
|
||||
],
|
||||
'environments' => [
|
||||
'default_migration_table' => 'phinxlog',
|
||||
'default_environment' => 'development',
|
||||
'production' => [
|
||||
'adapter' => 'mysql',
|
||||
'host' => $_ENV['DB_HOST'],
|
||||
'name' => $_ENV['MYSQL_DATABASE'],
|
||||
'user' => $_ENV['MYSQL_USER'],
|
||||
'pass' => $_ENV['MYSQL_PASSWORD'],
|
||||
//'port' => '3306',
|
||||
'charset' => 'utf8',
|
||||
],
|
||||
'development' => [
|
||||
'adapter' => 'mysql',
|
||||
'host' => $_ENV['DB_HOST'],
|
||||
'name' => $_ENV['MYSQL_DATABASE'] . '_dev',
|
||||
'user' => $_ENV['MYSQL_USER'],
|
||||
'pass' => $_ENV['MYSQL_PASSWORD'],
|
||||
//'port' => '3306',
|
||||
'charset' => 'utf8',
|
||||
],
|
||||
'testing' => [
|
||||
'adapter' => 'mysql',
|
||||
'host' => $_ENV['DB_HOST'],
|
||||
'name' => $_ENV['MYSQL_DATABASE'] . '_test',
|
||||
'user' => $_ENV['MYSQL_USER'],
|
||||
'pass' => $_ENV['MYSQL_PASSWORD'],
|
||||
//'port' => '3306',
|
||||
'charset' => 'utf8',
|
||||
]
|
||||
],
|
||||
'version_order' => 'creation'
|
||||
];
|
0
backend/api/public/.htaccess
Normal file
0
backend/api/public/.htaccess
Normal file
9
backend/api/public/index.php
Normal file
9
backend/api/public/index.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
include_once implode(DIRECTORY_SEPARATOR, [
|
||||
dirname(__DIR__),
|
||||
'setup',
|
||||
'app.php'
|
||||
]);
|
||||
$app->run();
|
12
backend/api/resources/data/api.json
Normal file
12
backend/api/resources/data/api.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"routes": [
|
||||
{
|
||||
"alias": [
|
||||
"/",
|
||||
"/help"
|
||||
],
|
||||
"description": "API help"
|
||||
}
|
||||
]
|
||||
}
|
15
backend/api/resources/routes/api.php
Normal file
15
backend/api/resources/routes/api.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
use ProVM\Crypto\Common\Controller\API;
|
||||
|
||||
$folder = __DIR__ . DIRECTORY_SEPARATOR . 'api';
|
||||
if (file_exists($folder)) {
|
||||
$files = new DirectoryIterator($folder);
|
||||
foreach ($files as $file) {
|
||||
if ($file->getExtension() != 'php') {
|
||||
continue;
|
||||
}
|
||||
include_once $file->getRealPath();
|
||||
}
|
||||
}
|
||||
|
||||
$app->get('[/]', API::class);
|
19
backend/api/resources/routes/api/coins.php
Normal file
19
backend/api/resources/routes/api/coins.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
use ProVM\Crypto\Common\Controller\Coins;
|
||||
|
||||
$app->group('/coins', function($app) {
|
||||
$app->post('/add', [Coins::class, 'add']);
|
||||
$app->get('[/]', Coins::class);
|
||||
});
|
||||
|
||||
$app->group('/coin/{coin_id}', function($app) {
|
||||
$app->put('/edit', [Coins::class, 'edit']);
|
||||
$app->delete('/delete', [Coins::class, 'delete']);
|
||||
$app->group('/values', function($app) {
|
||||
$app->get('/month', [Coins::class, 'valuesMonth']);
|
||||
$app->get('/months', [Coins::class, 'valuesSixMonths']);
|
||||
$app->get('/year', [Coins::class, 'valuesYear']);
|
||||
$app->get('[/]', [Coins::class, 'values']);
|
||||
});
|
||||
$app->get('[/]', [Coins::class, 'show']);
|
||||
});
|
13
backend/api/resources/routes/api/locations.php
Normal file
13
backend/api/resources/routes/api/locations.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
use ProVM\Crypto\Common\Controller\Locations;
|
||||
|
||||
$app->group('/locations', function($app) {
|
||||
$app->post('/add', [Locations::class, 'add']);
|
||||
$app->get('[/]', Locations::class);
|
||||
});
|
||||
|
||||
$app->group('/location/{location_id}', function($app) {
|
||||
$app->put('/edit', [Locations::class, 'edit']);
|
||||
$app->delete('/delete', [Locations::class, 'delete']);
|
||||
$app->get('[/]', [Locations::class, 'show']);
|
||||
});
|
7
backend/api/resources/routes/api/update.php
Normal file
7
backend/api/resources/routes/api/update.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
use ProVM\Crypto\Common\Controller\Update;
|
||||
|
||||
$app->group('/update', function($app) {
|
||||
$app->get('/register/{type:[0,1]}/{coin_id:[\d+]}', [Update::class, 'register']);
|
||||
$app->get('[/]', Update::class);
|
||||
});
|
13
backend/api/resources/routes/api/wallets.php
Normal file
13
backend/api/resources/routes/api/wallets.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
use ProVM\Crypto\Common\Controller\Wallets;
|
||||
|
||||
$app->group('/wallets', function($app) {
|
||||
$app->post('/add', [Wallets::class, 'add']);
|
||||
$app->get('[/]', Wallets::class);
|
||||
});
|
||||
|
||||
$app->group('/wallet/{wallet_id}', function($app) {
|
||||
$app->put('/edit', [Wallets::class, 'edit']);
|
||||
$app->delete('/delete', [Wallets::class, 'delete']);
|
||||
$app->get('[/]', [Wallets::class, 'show']);
|
||||
});
|
1
backend/api/setup/api/middleware.php
Normal file
1
backend/api/setup/api/middleware.php
Normal file
@ -0,0 +1 @@
|
||||
<?php
|
27
backend/api/setup/api/settings.php
Normal file
27
backend/api/setup/api/settings.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
use Psr\Container\ContainerInterface as Container;
|
||||
|
||||
return [
|
||||
'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['bin'] = implode(DIRECTORY_SEPARATOR, [
|
||||
dirname($arr['base']),
|
||||
'automation',
|
||||
'bin'
|
||||
]);
|
||||
return (object) $arr;
|
||||
},
|
||||
'python_api' => $_ENV['PYTHON_API']
|
||||
];
|
24
backend/api/setup/api/setups.php
Normal file
24
backend/api/setup/api/setups.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
use Psr\Container\ContainerInterface as Container;
|
||||
|
||||
return [
|
||||
ProVM\Crypto\Common\Service\API::class => function(Container $container) {
|
||||
$filename = implode(DIRECTORY_SEPARATOR, [
|
||||
$container->get('locations')->data,
|
||||
'api.json'
|
||||
]);
|
||||
return new ProVM\Crypto\Common\Service\API($filename);
|
||||
},
|
||||
ProVM\Common\Factory\Model::class => function(Container $container) {
|
||||
return new ProVM\Crypto\Common\Factory\Model();
|
||||
},
|
||||
GuzzleHttp\Client::class => function(Container $container) {
|
||||
return new GuzzleHttp\Client(['base_uri' => $container->get('python_api')]);
|
||||
},
|
||||
ProVM\Crypto\Common\Service\Update::class => function(Container $container) {
|
||||
return new ProVM\Crypto\Common\Service\Update(
|
||||
$container->get(ProVM\Crypto\Common\Factory\Model::class),
|
||||
$container->get(GuzzleHttp\Client::class)
|
||||
);
|
||||
}
|
||||
];
|
55
backend/api/setup/app.php
Normal file
55
backend/api/setup/app.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
use DI\ContainerBuilder as Builder;
|
||||
use DI\Bridge\Slim\Bridge;
|
||||
|
||||
include_once 'composer.php';
|
||||
|
||||
$builder = new Builder();
|
||||
|
||||
$folders = [
|
||||
'env',
|
||||
'common',
|
||||
'api'
|
||||
];
|
||||
$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->add(new ProVM\Crypto\Common\Middleware\CORS());
|
||||
$app->addRoutingMiddleware();
|
||||
|
||||
include_once 'databases.php';
|
||||
|
||||
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]));
|
6
backend/api/setup/composer.php
Normal file
6
backend/api/setup/composer.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
include_once implode(DIRECTORY_SEPARATOR, [
|
||||
dirname(__DIR__),
|
||||
'vendor',
|
||||
'autoload.php'
|
||||
]);
|
3
backend/api/setup/databases.php
Normal file
3
backend/api/setup/databases.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
$service = $app->getContainer()->get(ProVM\Common\Service\Database::class);
|
||||
$service->load();
|
25
backend/api/setup/env/settings.php
vendored
Normal file
25
backend/api/setup/env/settings.php
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
return [
|
||||
'base_url' => '',
|
||||
'debug' => $_ENV['DEBUG'],
|
||||
'databases' => function() {
|
||||
$settings = [
|
||||
'short_names' => true,
|
||||
'dbs' => []
|
||||
];
|
||||
$default = [
|
||||
'engine' => 'mysql',
|
||||
'host' => (object) [
|
||||
'name' => $_ENV['DB_HOST'],
|
||||
'port' => $_ENV['DB_PORT'] ?? null
|
||||
],
|
||||
'user' => (object) [
|
||||
'name' => $_ENV['MYSQL_USER'],
|
||||
'password' => $_ENV['MYSQL_PASSWORD']
|
||||
],
|
||||
'name' => $_ENV['MYSQL_DATABASE'] . ($_ENV['ENV'] ? '_' . $_ENV['ENV'] : '')
|
||||
];
|
||||
$settings['dbs']['default'] = (object) $default;
|
||||
return (object) $settings;
|
||||
}
|
||||
];
|
8
backend/api/setup/env/setups.php
vendored
Normal file
8
backend/api/setup/env/setups.php
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
use Psr\Container\ContainerInterface as Container;
|
||||
|
||||
return [
|
||||
ProVM\Common\Service\Database::class => function(Container $container) {
|
||||
return new ProVM\Common\Service\Database($container->get('databases'));
|
||||
}
|
||||
];
|
5
backend/api/setup/router.php
Normal file
5
backend/api/setup/router.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
include_once implode(DIRECTORY_SEPARATOR, [
|
||||
$app->getContainer()->get('locations')->routes,
|
||||
'api.php'
|
||||
]);
|
70
backend/api/src/Coin.php
Normal file
70
backend/api/src/Coin.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
namespace ProVM\Crypto;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use ProVM\Common\Alias\Model;
|
||||
use ProVM\Common\Factory\Model as Factory;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $identifier
|
||||
* @property string $code
|
||||
* @property string $name
|
||||
* @property string $prefix
|
||||
* @property string $suffix
|
||||
* @property int $decimals
|
||||
* @property string $ref_url
|
||||
*/
|
||||
class Coin extends Model {
|
||||
protected static $_table = 'coins';
|
||||
protected static $fields = ['code', 'name', 'identifier', 'prefix', 'suffix', 'decimals', 'ref_url'];
|
||||
|
||||
public function format(float $value): string {
|
||||
$output = [];
|
||||
if ($this->prefix != '') {
|
||||
$output []= $this->prefix;
|
||||
}
|
||||
$output []= number_format($value, $this->decimals ?? 0, ',', '.');
|
||||
if ($this->suffix != '') {
|
||||
$output []= $this->suffix;
|
||||
}
|
||||
return implode(' ', $output);
|
||||
}
|
||||
protected $values;
|
||||
public function values($period = null) {
|
||||
if ($this->values === null) {
|
||||
$this->values = $this->parentOf(Value::class, [Model::CHILD_KEY => 'coin_id']);
|
||||
}
|
||||
if ($this->values === null) {
|
||||
return $this->values;
|
||||
}
|
||||
if ($period === null) {
|
||||
return $this->values;
|
||||
}
|
||||
$f = Carbon::now();
|
||||
switch ($period) {
|
||||
case 'month':
|
||||
case 'mes':
|
||||
$m = $f->copy()->subMonths(1);
|
||||
return array_filter($this->values, function($item) use ($m) {
|
||||
return ($item->dateTime()->greaterThanOrEqualTo($m));
|
||||
});
|
||||
case 'months':
|
||||
case 'meses':
|
||||
$m = $f->copy()->subMonths(6);
|
||||
return array_filter($this->values, function($item) use ($m) {
|
||||
return ($item->dateTime()->greaterThanOrEqualTo($m));
|
||||
});
|
||||
case 'year':
|
||||
case 'año':
|
||||
$m = $f->copy()->subYears(1);
|
||||
return array_filter($this->values, function($item) use ($m) {
|
||||
return ($item->dateTime()->greaterThanOrEqualTo($m));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static function find(Factory $factory, $input) {
|
||||
return $factory->find(Coin::class)->where([['code', $input->code]])->one();
|
||||
}
|
||||
}
|
14
backend/api/src/Location.php
Normal file
14
backend/api/src/Location.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace ProVM\Crypto;
|
||||
|
||||
use ProVM\Common\Alias\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $description
|
||||
*/
|
||||
class Location extends Model {
|
||||
protected static $_table = 'locations';
|
||||
protected static $fields = ['name', 'description'];
|
||||
}
|
43
backend/api/src/Transaction.php
Normal file
43
backend/api/src/Transaction.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
namespace ProVM\Crypto;
|
||||
|
||||
use ProVM\Common\Alias\Model;
|
||||
use ProVM\Common\Define\Model\DateTime as DT;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property \DateTime $date_time
|
||||
* @property Coin $coin_id
|
||||
* @property Wallet $wallet_id
|
||||
* @property float $amount
|
||||
* @property float $value
|
||||
* @property Coin $unit_id
|
||||
*/
|
||||
class Transaction extends Model {
|
||||
use DT;
|
||||
|
||||
protected static $_table = 'transactions';
|
||||
protected static $fields = ['date_time', 'coin_id', 'wallet_id', 'amount', 'value', 'unit_id'];
|
||||
|
||||
protected $coin;
|
||||
public function coin() {
|
||||
if ($this->coin === null) {
|
||||
$this->coin = $this->childOf(Coin::class, [Model::SELF_KEY => 'coin_id']);
|
||||
}
|
||||
return $this->coin;
|
||||
}
|
||||
protected $wallet;
|
||||
public function wallet() {
|
||||
if ($this->wallet === null) {
|
||||
$this->wallet = $this->childOf(Wallet::class, [Model::SELF_KEY => 'wallet_id']);
|
||||
}
|
||||
return $this->wallet;
|
||||
}
|
||||
protected $unit;
|
||||
public function unit() {
|
||||
if ($this->unit === null) {
|
||||
$this->unit = $this->childOf(Coin::class, [Model::SELF_KEY => 'unit_id']);
|
||||
}
|
||||
return $this->unit;
|
||||
}
|
||||
}
|
34
backend/api/src/Value.php
Normal file
34
backend/api/src/Value.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
namespace ProVM\Crypto;
|
||||
|
||||
use ProVM\Common\Alias\Model;
|
||||
use ProVM\Common\Define\Model\DateTime as DT;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property \DateTime $date_time
|
||||
* @property Coin $coin_id
|
||||
* @property float $value
|
||||
* @property Coin $unit_id
|
||||
*/
|
||||
class Value extends Model {
|
||||
use DT;
|
||||
|
||||
public static $_table = 'values';
|
||||
protected static $fields = ['date_time', 'coin_id', 'value', 'unit_id'];
|
||||
|
||||
protected $coin;
|
||||
public function coin() {
|
||||
if ($this->coin === null) {
|
||||
$this->coin = $this->childOf(Coin::class, [Model::SELF_KEY => 'coin_id']);
|
||||
}
|
||||
return $this->coin;
|
||||
}
|
||||
protected $unit;
|
||||
public function unit() {
|
||||
if ($this->unit === null) {
|
||||
$this->unit = $this->childOf(Coin::class, [Model::SELF_KEY => 'unit_id']);
|
||||
}
|
||||
return $this->unit;
|
||||
}
|
||||
}
|
29
backend/api/src/Wallet.php
Normal file
29
backend/api/src/Wallet.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
namespace ProVM\Crypto;
|
||||
|
||||
use ProVM\Common\Alias\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property Location $location_id
|
||||
* @property string $public_address
|
||||
*/
|
||||
class Wallet extends Model {
|
||||
protected static $_table = 'wallets';
|
||||
protected static $fields = ['name', 'location_id', 'public_address'];
|
||||
|
||||
protected $location;
|
||||
public function location() {
|
||||
if ($this->location === null) {
|
||||
$this->location = $this->childOf(Location::class, [Model::SELF_KEY => 'location_id']);
|
||||
}
|
||||
return $this->location;
|
||||
}
|
||||
public function setLocation(Location $location) {
|
||||
if ($location->name == $this->location->name) {
|
||||
return;
|
||||
}
|
||||
$this->location = $location;
|
||||
}
|
||||
}
|
BIN
backend/automation/bin/coingecko
Normal file
BIN
backend/automation/bin/coingecko
Normal file
Binary file not shown.
BIN
backend/automation/bin/mindicador
Normal file
BIN
backend/automation/bin/mindicador
Normal file
Binary file not shown.
1
backend/automation/crontab
Normal file
1
backend/automation/crontab
Normal file
@ -0,0 +1 @@
|
||||
0 2 * * * curl http://backend/update
|
31
backend/docker-compose.yml
Normal file
31
backend/docker-compose.yml
Normal file
@ -0,0 +1,31 @@
|
||||
version: '3'
|
||||
services:
|
||||
web:
|
||||
image: jwilder/nginx-proxy
|
||||
ports:
|
||||
- "8086:80"
|
||||
- "4430:443"
|
||||
volumes:
|
||||
- //var/run/docker.sock:/tmp/docker.sock:ro
|
||||
- ./nginx.conf:/etc/nginx/conf.d/nginx.conf
|
||||
- ./proxy.conf:/etc/nginx/proxy.conf
|
||||
- ./api:/app
|
||||
- ./certs:/etc/nginx/certs
|
||||
php:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: PHP.Dockerfile
|
||||
volumes:
|
||||
- ./api:/app
|
||||
db:
|
||||
image: mariadb:latest
|
||||
env_file: .db.env
|
||||
volumes:
|
||||
- mysqldata:/var/lib/mysql
|
||||
adminer:
|
||||
image: adminer:alpine
|
||||
env_file: .db.env
|
||||
ports:
|
||||
- 8087:8080
|
||||
volumes:
|
||||
mysqldata: {}
|
29
backend/nginx.conf
Normal file
29
backend/nginx.conf
Normal file
@ -0,0 +1,29 @@
|
||||
server {
|
||||
listen ${BACKEND_PORT} default_server;
|
||||
root /app/backend/api/public;
|
||||
|
||||
access_log /var/log/nginx/backend.access.log;
|
||||
error_log /var/log/nginx/backend.error.log;
|
||||
|
||||
index index.php index.html index.htm;
|
||||
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
|
||||
|
||||
location / {
|
||||
try_files $uri /index.php$is_args$args;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass backend:9000;
|
||||
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
fastcgi_index index.php;
|
||||
include fastcgi_params;
|
||||
|
||||
include /app/backend/proxy.conf;
|
||||
}
|
||||
}
|
19
backend/proxy.conf
Normal file
19
backend/proxy.conf
Normal file
@ -0,0 +1,19 @@
|
||||
# HTTP 1.1 support
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
#proxy_set_header Connection $proxy_connection;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
#proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
||||
#proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
|
||||
#proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
|
||||
|
||||
# Mitigate httpoxy attack
|
||||
proxy_set_header Proxy "";
|
||||
|
||||
# Custom Values Higher Buffer Size
|
||||
proxy_buffer_size 128k;
|
||||
proxy_buffers 4 256k;
|
||||
proxy_busy_buffers_size 256k;
|
BIN
backend/python/__pycache__/coingecko.cpython-39.pyc
Normal file
BIN
backend/python/__pycache__/coingecko.cpython-39.pyc
Normal file
Binary file not shown.
BIN
backend/python/__pycache__/miindicador.cpython-39.pyc
Normal file
BIN
backend/python/__pycache__/miindicador.cpython-39.pyc
Normal file
Binary file not shown.
BIN
backend/python/crypto/__pycache__/coingecko.cpython-39.pyc
Normal file
BIN
backend/python/crypto/__pycache__/coingecko.cpython-39.pyc
Normal file
Binary file not shown.
BIN
backend/python/crypto/__pycache__/index.cpython-39.pyc
Normal file
BIN
backend/python/crypto/__pycache__/index.cpython-39.pyc
Normal file
Binary file not shown.
BIN
backend/python/crypto/__pycache__/miindicador.cpython-39.pyc
Normal file
BIN
backend/python/crypto/__pycache__/miindicador.cpython-39.pyc
Normal file
Binary file not shown.
101
backend/python/crypto/coingecko.py
Normal file
101
backend/python/crypto/coingecko.py
Normal file
@ -0,0 +1,101 @@
|
||||
import argparse
|
||||
import httpx
|
||||
import json
|
||||
import datetime
|
||||
|
||||
|
||||
class CoinGecko:
|
||||
def __init__(self, base_url: str = None):
|
||||
print('Creating object CoinGecko')
|
||||
|
||||
if base_url is None:
|
||||
base_url = 'https://api.coingecko.com/api/v3'
|
||||
self.base_url = base_url
|
||||
|
||||
def __build_url(self, sub_url: str, query: str = ''):
|
||||
sub = sub_url
|
||||
if query != '':
|
||||
sub = '?'.join([
|
||||
sub,
|
||||
query
|
||||
])
|
||||
url = '/'.join([
|
||||
self.base_url,
|
||||
sub
|
||||
])
|
||||
return url
|
||||
|
||||
def __get(self, url: str):
|
||||
resp = httpx.get(url)
|
||||
if resp.status_code != httpx.codes.OK:
|
||||
raise Exception(resp.reason_phrase)
|
||||
return json.loads(resp.text)
|
||||
|
||||
def list(self):
|
||||
print('Getting list of coins from {}'.format(self.base_url))
|
||||
|
||||
url = self.__build_url('coins/list')
|
||||
return self.__get(url)
|
||||
|
||||
def get(self, ids: tuple, currencies: tuple, last_updated: bool = True):
|
||||
print('Getting {} in {} from {}'.format(ids, currencies, self.base_url))
|
||||
|
||||
sub = 'simple/price'
|
||||
query = '&'.join([
|
||||
'='.join(['ids', ','.join(ids)]),
|
||||
'='.join(['vs_currencies', ','.join(currencies)]),
|
||||
'='.join(['include_last_updated_at', 'true' if last_updated else 'false'])
|
||||
])
|
||||
url = self.__build_url(sub, query)
|
||||
res = self.__get(url)
|
||||
for k, d in res.items():
|
||||
res[k]['last_updated_at'] = datetime.datetime.fromtimestamp(d['last_updated_at'])\
|
||||
.strftime('%Y-%m-%d %H:%M:%S.%f%z')
|
||||
return res
|
||||
|
||||
def historical(self, id_: str, currency: str, from_: str, to: str):
|
||||
print('Getting historical data for {} in {} from {} to {} from {}'.format(id_, currency, from_, to, self.base_url))
|
||||
|
||||
sub = '/'.join([
|
||||
'coins',
|
||||
id_,
|
||||
'market_chart',
|
||||
'range'
|
||||
])
|
||||
query = '&'.join([
|
||||
'='.join(['vs_currency', currency]),
|
||||
'='.join(['from', from_]),
|
||||
'='.join(['to', to])
|
||||
])
|
||||
url = self.__build_url(sub, query)
|
||||
res = self.__get(url)
|
||||
return res
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-u', '--url')
|
||||
parser.add_argument('-i', '--ids', type=str)
|
||||
parser.add_argument('-c', '--currencies', type=str)
|
||||
hist = parser.add_subparsers()
|
||||
hparser = hist.add_parser('hist')
|
||||
hparser.add_argument('-hi', '--historical', action='store_true')
|
||||
hparser.add_argument('-f', '--from_')
|
||||
hparser.add_argument('-t', '--to')
|
||||
args = parser.parse_args()
|
||||
|
||||
print('Called with args: {}'.format(vars(args)))
|
||||
|
||||
cg = CoinGecko(args.url)
|
||||
_ids = tuple(args.ids.split(','))
|
||||
_currencies = tuple(args.currencies.split(','))
|
||||
if 'historical' in args and args.historical:
|
||||
from__ = args.from_
|
||||
if '-' in from__:
|
||||
from__ = str(datetime.datetime.fromisoformat(from__).timestamp())
|
||||
to_ = args.to
|
||||
if '-' in to_:
|
||||
to_ = str(datetime.datetime.fromisoformat(to_).timestamp())
|
||||
print(cg.historical(id_=_ids[0], currency=_currencies[0], from_=from__, to=to_))
|
||||
exit()
|
||||
print(cg.get(ids=_ids, currencies=_currencies))
|
50
backend/python/crypto/index.py
Normal file
50
backend/python/crypto/index.py
Normal file
@ -0,0 +1,50 @@
|
||||
from flask import Flask
|
||||
|
||||
from crypto.coingecko import CoinGecko
|
||||
from crypto.miindicador import MiIndicador
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def main():
|
||||
output = [
|
||||
"<html>",
|
||||
"<body>",
|
||||
"Welcome",
|
||||
"</body>",
|
||||
"</html>"
|
||||
]
|
||||
return "\n".join(output)
|
||||
|
||||
|
||||
@app.route('/crypto/historical/<ids>/<from_>/<to>')
|
||||
@app.route('/crypto/historical/<ids>/<from_>/<to>/<currencies>')
|
||||
def historical_crypto(ids, from_, to, currencies='usd'):
|
||||
cg = CoinGecko()
|
||||
return cg.historical(id_=ids, from_=from_, to=to, currency=currencies)
|
||||
|
||||
|
||||
@app.route('/crypto/<ids>')
|
||||
@app.route('/crypto/<ids>/<currencies>')
|
||||
def crypto(ids, currencies=('usd', 'clp')):
|
||||
cg = CoinGecko()
|
||||
return cg.get(ids, currencies)
|
||||
|
||||
|
||||
@app.route('/indicador/historical/<ind>')
|
||||
@app.route('/indicador/historical/<ind>/<since>')
|
||||
def historical_indicador(ind, since=None):
|
||||
mi = MiIndicador()
|
||||
return mi.historical(ind, since)
|
||||
|
||||
|
||||
@app.route('/indicador/<ind>')
|
||||
@app.route('/indicador/<ind>/<fecha>')
|
||||
def indicador(ind, fecha=None):
|
||||
mi = MiIndicador()
|
||||
return mi.get(ind, fecha)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=5001, debug=True)
|
83
backend/python/crypto/miindicador.py
Normal file
83
backend/python/crypto/miindicador.py
Normal file
@ -0,0 +1,83 @@
|
||||
import argparse
|
||||
import httpx
|
||||
import json
|
||||
import datetime
|
||||
|
||||
|
||||
class MiIndicador:
|
||||
def __init__(self, base_url: str = None):
|
||||
print('Creating object Mindicador')
|
||||
|
||||
if base_url is None:
|
||||
base_url = 'https://mindicador.cl/api'
|
||||
self.base_url = base_url
|
||||
|
||||
def __build_url(self, sub_url: str, query: str = ''):
|
||||
sub = sub_url
|
||||
if query != '':
|
||||
sub = '?'.join([
|
||||
sub,
|
||||
query
|
||||
])
|
||||
url = '/'.join([
|
||||
self.base_url,
|
||||
sub
|
||||
])
|
||||
return url
|
||||
|
||||
def __get(self, url: str):
|
||||
resp = httpx.get(url)
|
||||
if resp.status_code != httpx.codes.OK:
|
||||
raise Exception(resp.reason_phrase)
|
||||
return json.loads(resp.text)
|
||||
|
||||
def list(self):
|
||||
print('List possible indicators')
|
||||
|
||||
url = self.__build_url('')
|
||||
return self.__get(url)
|
||||
|
||||
def get(self, indicador: str, fecha: str = None):
|
||||
print('Getting {} in date {} from {}'.format(indicador, fecha, self.base_url))
|
||||
|
||||
url = indicador
|
||||
if fecha is not None:
|
||||
url = '/'.join([url, fecha])
|
||||
url = self.__build_url(url)
|
||||
res = self.__get(url)
|
||||
for i, item in enumerate(res['serie']):
|
||||
res['serie'][i]['fecha'] = datetime.datetime.fromisoformat(item['fecha'].replace('T', ' ').replace('Z', ''))\
|
||||
.strftime('%Y-%m-%d')
|
||||
return res
|
||||
|
||||
def historical(self, indicador: str, since: str = None):
|
||||
print('Getting historical data for {} since {} from {}'.format(indicador, since, self.base_url))
|
||||
|
||||
sub = indicador
|
||||
if since is not None:
|
||||
sub = '/'.join([sub, since])
|
||||
url = self.__build_url(sub)
|
||||
res = self.__get(url)
|
||||
for i, item in enumerate(res['serie']):
|
||||
res['serie'][i]['fecha'] = datetime.datetime.fromisoformat(item['fecha'].replace('T', ' ').replace('Z', ''))\
|
||||
.strftime('%Y-%m-%d')
|
||||
return res
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-u', '--url')
|
||||
parser.add_argument('-i', '--indicador')
|
||||
hist = parser.add_subparsers()
|
||||
hparser = hist.add_parser('hist')
|
||||
hparser.add_argument('-hi', '--historical', action='store_true')
|
||||
hparser.add_argument('-s', '--since')
|
||||
args = parser.parse_args()
|
||||
|
||||
print('Called with args: {}'.format(vars(args)))
|
||||
|
||||
mi = MiIndicador(args.url)
|
||||
if 'historical' in args and args.historical:
|
||||
print(mi.historical(args.indicador, args.since))
|
||||
exit()
|
||||
print(mi.get(args.indicador))
|
3
build.python.bat
Normal file
3
build.python.bat
Normal file
@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
|
||||
docker build -f "./backend/Py.Dockerfile" .
|
6
build.sh
Normal file
6
build.sh
Normal file
@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
pyinstaller -F -n coingecko --clean --log-level DEBUG --distpath /app/bin/ /app/src/coingecko.py
|
||||
pyinstaller -F -n mindicador --clean --log-level DEBUG --distpath /app/bin/ /app/src/miindicador.py
|
||||
#python -m nuitka --follow-imports --standalone --output-dir="/app/bin" /app/src/coingecko.py
|
||||
#python -m nuitka --follow-imports --standalone --output-dir="/app/bin" /app/src/miindicador.py
|
83
docker-compose.yml
Normal file
83
docker-compose.yml
Normal file
@ -0,0 +1,83 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
proxy:
|
||||
container_name: crypto-proxy
|
||||
restart: unless-stopped
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- 8080:${FRONTEND_PORT}
|
||||
- 8081:${BACKEND_PORT}
|
||||
volumes:
|
||||
- .:/app
|
||||
- ./frontend/nginx.conf:/etc/nginx/templates/default.conf.template
|
||||
- ./backend/nginx.conf:/etc/nginx/templates/backend.conf.template
|
||||
- ./logs:/var/log/nginx
|
||||
env_file: .env
|
||||
depends_on:
|
||||
- frontend
|
||||
- backend
|
||||
|
||||
frontend:
|
||||
container_name: crypto-frontend
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: ./frontend
|
||||
dockerfile: PHP.Dockerfile
|
||||
env_file: .common.env
|
||||
volumes:
|
||||
- .:/app
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
backend:
|
||||
container_name: crypto-backend
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: PHP.Dockerfile
|
||||
env_file:
|
||||
- .common.env
|
||||
- .db.env
|
||||
volumes:
|
||||
- .:/app
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
cron:
|
||||
container_name: crypto-cron
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Cron.Dockerfile
|
||||
volumes:
|
||||
- ./backend/automation/crontab:/var/spool/cron/crontabs/backend
|
||||
|
||||
python:
|
||||
container_name: crypto-python
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Py.Dockerfile
|
||||
volumes:
|
||||
- ./backend/python:/app
|
||||
- ./backend/automation/bin:/app/bin
|
||||
|
||||
db:
|
||||
container_name: crypto-db
|
||||
restart: unless-stopped
|
||||
image: mariadb:latest
|
||||
env_file: .db.env
|
||||
volumes:
|
||||
- mysqldata:/var/lib/mysql
|
||||
|
||||
adminer:
|
||||
container_name: crypto-adminer
|
||||
restart: unless-stopped
|
||||
image: adminer
|
||||
depends_on:
|
||||
- db
|
||||
ports:
|
||||
- 8082:8080
|
||||
environment:
|
||||
ADMINER_DESIGN: "dracula"
|
||||
|
||||
volumes:
|
||||
mysqldata:
|
6
frontend/.gitignore
vendored
Normal file
6
frontend/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# Composer
|
||||
/vendor/
|
||||
composer.lock
|
||||
|
||||
# Cache
|
||||
**/cache/
|
7
frontend/PHP.Dockerfile
Normal file
7
frontend/PHP.Dockerfile
Normal 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
|
17
frontend/common/Controller/Coins.php
Normal file
17
frontend/common/Controller/Coins.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?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'));
|
||||
}
|
||||
public function values(Request $request, Response $response, View $view, $coin_id): Response {
|
||||
}
|
||||
}
|
12
frontend/common/Controller/Home.php
Normal file
12
frontend/common/Controller/Home.php
Normal 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
44
frontend/composer.json
Normal 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
22
frontend/nginx.conf
Normal 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;
|
||||
}
|
||||
}
|
0
frontend/public/.htaccess
Normal file
0
frontend/public/.htaccess
Normal file
9
frontend/public/index.php
Normal file
9
frontend/public/index.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
include_once implode(DIRECTORY_SEPARATOR, [
|
||||
dirname(__DIR__),
|
||||
'setup',
|
||||
'app.php'
|
||||
]);
|
||||
$app->run();
|
12
frontend/resources/routes/web.php
Normal file
12
frontend/resources/routes/web.php
Normal 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);
|
11
frontend/resources/routes/web/coins.php
Normal file
11
frontend/resources/routes/web/coins.php
Normal 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']);
|
||||
});
|
5
frontend/resources/views/coins/base.blade.php
Normal file
5
frontend/resources/views/coins/base.blade.php
Normal file
@ -0,0 +1,5 @@
|
||||
@extends('layout.base')
|
||||
|
||||
@section('page_title')
|
||||
Coins
|
||||
@endsection
|
274
frontend/resources/views/coins/list.blade.php
Normal file
274
frontend/resources/views/coins/list.blade.php
Normal file
@ -0,0 +1,274 @@
|
||||
@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}}'
|
||||
},
|
||||
table: null,
|
||||
modal: null,
|
||||
setup: function(table, modal) {
|
||||
this.table = table
|
||||
this.modal = modal
|
||||
table.find('plus icon').css('cursor', 'pointer').click(() => {
|
||||
addCoin()
|
||||
})
|
||||
table.find('.plus.icon').css('cursor', 'pointer').click((e) => {
|
||||
this.add()
|
||||
})
|
||||
this.load()
|
||||
},
|
||||
load: function() {
|
||||
const url = this.urls.api + '/coins'
|
||||
const table = this.table
|
||||
const body = table.find('tbody')
|
||||
table.find('tbody').append($('<div></div>').attr('class', 'ui active dimmer').append(
|
||||
$('<div></div>').attr('class', 'ui loader')
|
||||
))
|
||||
$.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('Identificador')
|
||||
).append(
|
||||
$('<input />').attr('type', 'text').attr('name', 'identifier')
|
||||
)
|
||||
).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',
|
||||
'identifier',
|
||||
'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 this.load()
|
||||
}
|
||||
}, '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 this.load()
|
||||
}
|
||||
},
|
||||
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 this.load()
|
||||
}
|
||||
},
|
||||
dataType: 'json'
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
$(document).ready(() => {
|
||||
const modal = $('#coin_modal')
|
||||
modal.modal()
|
||||
modal.modal('setting', 'closable', true)
|
||||
const table = $('#coins')
|
||||
|
||||
coins.setup(table, modal)
|
||||
})
|
||||
</script>
|
||||
@endpush
|
132
frontend/resources/views/coins/show.blade.php
Normal file
132
frontend/resources/views/coins/show.blade.php
Normal file
@ -0,0 +1,132 @@
|
||||
@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>
|
||||
<canvas id="coin_graph" width="400" height="200"></canvas>
|
||||
<table class="ui table" id="coin_values">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Fecha
|
||||
</th>
|
||||
<th>
|
||||
Valor
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
@endsection
|
||||
|
||||
@push('page_scripts')
|
||||
<script type="text/javascript">
|
||||
const coin = {
|
||||
id: {{$coin_id}},
|
||||
urls: {
|
||||
api: '{{$urls->api}}'
|
||||
},
|
||||
data: {},
|
||||
values: {},
|
||||
draw: function() {
|
||||
$('.coin_name').html(this.data.name)
|
||||
$('#coin_data').append(
|
||||
$('<div></div>').attr('class', 'item').html('Código: ' + this.data.code)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'item').html('Prefijo: ' + this.data.prefix)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'item').html('Sufijo: ' + this.data.suffix)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'item').html('Decimales: ' + this.data.decimals)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'item').html('Url: ' + this.data.ref_url)
|
||||
)
|
||||
},
|
||||
loading: function(elem) {
|
||||
elem.html('')
|
||||
elem.append(
|
||||
$('<div></div>').attr('class', 'ui active dimmer').append(
|
||||
$('<div></div>').attr('class', 'ui indeterminate elastic text loader').html('Cargando los datos.')
|
||||
)
|
||||
)
|
||||
},
|
||||
getData: function() {
|
||||
const elem_id = '#coin_data'
|
||||
this.loading($(elem_id))
|
||||
const url = this.urls.api + '/coin/' + this.id
|
||||
$.getJSON(url, (data) => {
|
||||
$(elem_id).html('')
|
||||
this.data = data.coin
|
||||
this.draw()
|
||||
this.getValues()
|
||||
})
|
||||
},
|
||||
getValues: function() {
|
||||
/*const elem_id = '#coin_values'
|
||||
$(elem_id).show()
|
||||
this.loading($(elem_id).find('tbody'))*/
|
||||
this.loading($('#coin_graph'))
|
||||
const url = this.urls.api + '/coin/' + this.id + '/values'
|
||||
$.getJSON(url, (data) => {
|
||||
//$(elem_id).find('tbody').html('')
|
||||
this.values = data.values
|
||||
this.graphValues()
|
||||
})
|
||||
},
|
||||
showValues: function() {
|
||||
const list_id = '#coin_values'
|
||||
list = $(list_id).find('tbody')
|
||||
const f = Intl.DateTimeFormat('es-CL', {dateStyle: 'medium', timeStyle: 'long'})
|
||||
$.each(this.values, (i, elem) => {
|
||||
const d = new Date(elem.date_time)
|
||||
list.append(
|
||||
$('<tr></tr>').append(
|
||||
$('<td></td>').html(f.format(d))
|
||||
).append(
|
||||
$('<td></td>').html(elem.formatted)
|
||||
)
|
||||
)
|
||||
})
|
||||
},
|
||||
graphValues: function() {
|
||||
const ctx = document.getElementById('coin_graph').getContext('2d')
|
||||
let labels = []
|
||||
let values = []
|
||||
const f = Intl.DateTimeFormat('es-CL', {dateStyle: 'medium'})
|
||||
$.each(this.values, (i, el) => {
|
||||
const d = new Date(el.date_time)
|
||||
labels.push(f.format(d))
|
||||
values.push(el.value)
|
||||
})
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: this.data.name,
|
||||
data: values,
|
||||
fill: false,
|
||||
borderWidth: 1,
|
||||
tension: 0.1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
setup: function() {
|
||||
this.getData()
|
||||
$('#coin_values').hide()
|
||||
}
|
||||
}
|
||||
$(document).ready(() => {
|
||||
coin.setup()
|
||||
})
|
||||
</script>
|
||||
@endpush
|
220
frontend/resources/views/home.blade.php
Normal file
220
frontend/resources/views/home.blade.php
Normal file
@ -0,0 +1,220 @@
|
||||
@extends('layout.base')
|
||||
|
||||
@section('page_content')
|
||||
<h3>
|
||||
Home
|
||||
</h3>
|
||||
|
||||
<div class="ui top attached tabular menu" id="coins_menu">
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('page_scripts')
|
||||
<script type="text/javascript">
|
||||
const types = ['month', 'months', 'year']
|
||||
const coins_menu = $('#coins_menu')
|
||||
const f = Intl.DateTimeFormat()
|
||||
let active = false
|
||||
class Coin {
|
||||
constructor(data) {
|
||||
this.data = data
|
||||
this.values = {
|
||||
month: [],
|
||||
months: [],
|
||||
year: []
|
||||
}
|
||||
}
|
||||
getValues(api_url) {
|
||||
const base_url = api_url + '/coin/' + this.data.id + '/values/'
|
||||
let promises = []
|
||||
$.each(types, (i, t) => {
|
||||
let url = base_url + t
|
||||
promises.push($.getJSON(url, (data) => {
|
||||
this.values[t] = data.values
|
||||
}))
|
||||
})
|
||||
Promise.all(promises).then(() => {
|
||||
this.draw()
|
||||
})
|
||||
}
|
||||
size() {
|
||||
let sum = 0
|
||||
$.each(types, (i, t) => {
|
||||
sum += this.values[t].length
|
||||
})
|
||||
return sum
|
||||
}
|
||||
draw() {
|
||||
if (this.size() == 0) {
|
||||
return
|
||||
}
|
||||
const m = $('<div></div>').attr('class', 'item').attr('data-tab', this.data.code).html(this.data.name)
|
||||
coins_menu.append(m)
|
||||
const tabs = $('<div></div>').attr('class', 'ui top attached tabular menu')
|
||||
const column = $('<div></div>').attr('class', 'eight wide column').append(tabs)
|
||||
let active2 = false
|
||||
$.each(types, (i, t) => {
|
||||
if (this.values[t].length == 0) {
|
||||
return
|
||||
}
|
||||
const canvas = $('<canvas></canvas>').attr('id', 'canvas_' + t + '_' + this.data.code).attr('width', '200').attr('height', '200')
|
||||
const tab = $('<div></div>').attr('class', 'item').attr('data-tab', this.data.code + t).html(t.toLowerCase().split(' ').map(function(word) {
|
||||
return (word.charAt(0)).toUpperCase() + word.slice(1)
|
||||
}).join(' '))
|
||||
tabs.append(tab)
|
||||
const sg = $('<div></div>').attr('class', 'ui bottom attached tab segment').attr('data-tab', this.data.code + t).append(
|
||||
$('<div></div>').attr('class', 'content').append(canvas)
|
||||
)
|
||||
column.append(sg)
|
||||
if (!active2) {
|
||||
tab.addClass('active')
|
||||
sg.addClass('active')
|
||||
active2 = true
|
||||
}
|
||||
|
||||
let labels = []
|
||||
let values = []
|
||||
$.each(this.values[t], (k, val) => {
|
||||
const d = new Date(val.date_time)
|
||||
labels.push(f.format(d))
|
||||
values.push(val.value)
|
||||
})
|
||||
const min = Math.min(...values)
|
||||
const max = Math.max(...values)
|
||||
const range = max - min
|
||||
const chart = new Chart(canvas[0], {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: this.data.name,
|
||||
data: values,
|
||||
fill: false,
|
||||
borderWidth: 1,
|
||||
tension: 0.1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
min: min - range / 2,
|
||||
max: max + range / 2
|
||||
}/*,
|
||||
x: {
|
||||
display: false
|
||||
}*/
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
const seg = $('<div></div>').attr('class', 'ui bottom attached tab segment').attr('data-tab', this.data.code).append(
|
||||
$('<div></div>').attr('class', 'ui centered grid').append(column)
|
||||
)
|
||||
coins_menu.after(seg)
|
||||
if (!active) {
|
||||
m.addClass('active')
|
||||
seg.addClass('active')
|
||||
active = true
|
||||
}
|
||||
m.tab()
|
||||
tabs.find('.item').tab()
|
||||
}
|
||||
}
|
||||
const coins = {
|
||||
data: [],
|
||||
urls: {
|
||||
api: '{{$urls->api}}'
|
||||
},
|
||||
getCoins: function() {
|
||||
const url = this.urls.api + '/coins'
|
||||
$.getJSON(url, (data) => {
|
||||
$.each(data.coins, (i, el) => {
|
||||
const c = new Coin(el)
|
||||
c.getValues(this.urls.api)
|
||||
})
|
||||
})
|
||||
},
|
||||
getValues: function() {
|
||||
let p = []
|
||||
$.each(this.data, (i, el) => {
|
||||
const url = this.urls.api + '/coin/' + el.id + '/values/months'
|
||||
p.push($.getJSON(url, (data) => {
|
||||
this.data[i].values = data.values
|
||||
}))
|
||||
})
|
||||
Promise.all(p).then(() => {
|
||||
this.draw()
|
||||
})
|
||||
},
|
||||
draw: function() {
|
||||
const menu = $('#coin_menu')
|
||||
const f = Intl.DateTimeFormat()
|
||||
let active = false
|
||||
$.each(this.data, (i, el) => {
|
||||
if (el.values.length == 0) {
|
||||
return
|
||||
}
|
||||
const m = $('<div></div>').attr('class', 'item').attr('data-tab', el.code).html(el.name)
|
||||
menu.append(m)
|
||||
const canvas = $('<canvas></canvas>').attr('id', 'canvas' + el.code).attr('width', '200').attr('height', '200')
|
||||
const seg = $('<div></div>').attr('class', 'ui bottom attached tab segment').attr('data-tab', el.code).append(
|
||||
$('<div></div>').attr('class', 'ui centered grid').append(
|
||||
$('<div></div>').attr('class', 'eight wide column').append(canvas)
|
||||
)
|
||||
)
|
||||
menu.after(seg)
|
||||
if (!active) {
|
||||
m.addClass('active')
|
||||
seg.addClass('active')
|
||||
active = true
|
||||
}
|
||||
m.tab()
|
||||
let labels = []
|
||||
let values = []
|
||||
$.each(el.values, (k, val) => {
|
||||
const d = new Date(val.date_time)
|
||||
labels.push(f.format(d))
|
||||
values.push(val.value)
|
||||
})
|
||||
const min = Math.min(...values)
|
||||
const max = Math.max(...values)
|
||||
const range = max - min
|
||||
const chart = new Chart(canvas[0], {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: el.name,
|
||||
data: values,
|
||||
fill: false,
|
||||
borderWidth: 1,
|
||||
tension: 0.1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
min: min - range / 2,
|
||||
max: max + range / 2
|
||||
}/*,
|
||||
x: {
|
||||
display: false
|
||||
}*/
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
setup: function() {
|
||||
this.getCoins()
|
||||
}
|
||||
}
|
||||
$(document).ready(() => {
|
||||
coins.setup()
|
||||
})
|
||||
</script>
|
||||
@endpush
|
5
frontend/resources/views/layout/base.blade.php
Normal file
5
frontend/resources/views/layout/base.blade.php
Normal file
@ -0,0 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
@include('layout.head')
|
||||
@include('layout.body')
|
||||
</html>
|
16
frontend/resources/views/layout/body.blade.php
Normal file
16
frontend/resources/views/layout/body.blade.php
Normal 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>
|
4
frontend/resources/views/layout/body/footer.blade.php
Normal file
4
frontend/resources/views/layout/body/footer.blade.php
Normal file
@ -0,0 +1,4 @@
|
||||
<footer>
|
||||
|
||||
</footer>
|
||||
@include('layout.body.scripts')
|
6
frontend/resources/views/layout/body/header.blade.php
Normal file
6
frontend/resources/views/layout/body/header.blade.php
Normal 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>
|
15
frontend/resources/views/layout/body/scripts.blade.php
Normal file
15
frontend/resources/views/layout/body/scripts.blade.php
Normal 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>
|
11
frontend/resources/views/layout/head.blade.php
Normal file
11
frontend/resources/views/layout/head.blade.php
Normal file
@ -0,0 +1,11 @@
|
||||
<head>
|
||||
<meta charset="utf8" />
|
||||
<title>
|
||||
Crypto
|
||||
@hasSection('page_title')
|
||||
-
|
||||
@yield('page_title')
|
||||
@endif
|
||||
</title>
|
||||
@include('layout.head.styles')
|
||||
</head>
|
11
frontend/resources/views/layout/head/styles.blade.php
Normal file
11
frontend/resources/views/layout/head/styles.blade.php
Normal 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
52
frontend/setup/app.php
Normal 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]));
|
9
frontend/setup/common/settings.php
Normal file
9
frontend/setup/common/settings.php
Normal 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'];
|
||||
}
|
||||
];
|
6
frontend/setup/composer.php
Normal file
6
frontend/setup/composer.php
Normal 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
4
frontend/setup/env/settings.php
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
<?php
|
||||
return [
|
||||
'debug' => $_ENV['DEBUG'] ?? false
|
||||
];
|
5
frontend/setup/router.php
Normal file
5
frontend/setup/router.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
include_once implode(DIRECTORY_SEPARATOR, [
|
||||
$app->getContainer()->get('locations')->routes,
|
||||
'web.php'
|
||||
]);
|
67
frontend/setup/web/settings.php
Normal file
67
frontend/setup/web/settings.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?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 = [
|
||||
'<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>',
|
||||
'<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>',
|
||||
'<script src="https://cdn.jsdelivr.net/npm/chart.js@3.4.1/dist/chart.min.js"></script>'
|
||||
];
|
||||
foreach ($arr as $i => $a) {
|
||||
if (strpos($a, '<script') !== false) {
|
||||
$a = ['full' => $a];
|
||||
} else {
|
||||
$a = ['url' => $a];
|
||||
}
|
||||
$arr[$i] = (object) $a;
|
||||
}
|
||||
return $arr;
|
||||
},
|
||||
'styles' => function() {
|
||||
$arr = [
|
||||
'<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" />',
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.8.7/themes/default/assets/fonts/brand-icons.woff2',
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.8.7/themes/default/assets/fonts/icons.woff2',
|
||||
'https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.8.7/themes/default/assets/fonts/outline-icons.woff2'
|
||||
];
|
||||
foreach ($arr as $i => $a) {
|
||||
if (strpos($a, '<link') !== false) {
|
||||
$a = ['link' => $a];
|
||||
} else {
|
||||
$a = ['url' => $a];
|
||||
}
|
||||
$arr[$i] = (object) $a;
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
];
|
17
frontend/setup/web/setups.php
Normal file
17
frontend/setup/web/setups.php
Normal 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')
|
||||
]
|
||||
);
|
||||
}
|
||||
];
|
20
provm/Migrator.md
Normal file
20
provm/Migrator.md
Normal file
@ -0,0 +1,20 @@
|
||||
# Migrator
|
||||
|
||||
## Needs
|
||||
|
||||
+ To detect changes in migration schema and create migrations with Phinx
|
||||
+ Keep track of incremental changes in schema
|
||||
+ Create migration for Phinx
|
||||
|
||||
## Usage
|
||||
|
||||
Check for changes:
|
||||
<code>vendor/bin/migrator check</code>
|
||||
|
||||
Create migrations:
|
||||
<code>vendor/bin/migrator create</code>
|
||||
|
||||
Check the create migrations and then:
|
||||
<code>vendor/bin/migrator migrate</code>
|
||||
or
|
||||
<code>vendor/bin/phinx migrate</code>
|
13
provm/common/Define/Controller/JSON.php
Normal file
13
provm/common/Define/Controller/JSON.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace ProVM\Common\Define\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
|
||||
trait JSON {
|
||||
public function withJson(Response $response, $data, $status_code = 200) {
|
||||
$response->getBody()->write(json_encode($data));
|
||||
return $response
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withStatus($status_code);
|
||||
}
|
||||
}
|
13
provm/common/Define/Model/Date.php
Normal file
13
provm/common/Define/Model/Date.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace ProVM\Common\Define\Model;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
trait Date {
|
||||
public function date(\DateTime $date = null) {
|
||||
if ($date === null) {
|
||||
return Carbon::parse($this->date);
|
||||
}
|
||||
$this->date = $date->format('Y-m-d');
|
||||
}
|
||||
}
|
13
provm/common/Define/Model/DateTime.php
Normal file
13
provm/common/Define/Model/DateTime.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace ProVM\Common\Define\Model;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
trait DateTime {
|
||||
public function dateTime(\DateTime $date_time = null) {
|
||||
if ($date_time === null) {
|
||||
return Carbon::parse($this->date_time);
|
||||
}
|
||||
$this->date_time = $date_time->format('Y-m-d H:i:s');
|
||||
}
|
||||
}
|
13
provm/common/Define/Model/Time.php
Normal file
13
provm/common/Define/Model/Time.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace ProVM\Common\Define\Model;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
trait Time {
|
||||
public function date(\DateTime $time = null) {
|
||||
if ($time === null) {
|
||||
return Carbon::parse($this->time);
|
||||
}
|
||||
$this->time = $time->format('H:i:s');
|
||||
}
|
||||
}
|
23
provm/common/Middleware/Auth.php
Normal file
23
provm/common/Middleware/Auth.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
namespace ProVM\Common\Middleware;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface as Handler;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Nyholm\Psr7\Factory\Psr17Factory;
|
||||
|
||||
class Auth {
|
||||
protected $factory;
|
||||
public function __construct(Psr17Factory $factory) {
|
||||
$this->factory = $factory;
|
||||
}
|
||||
public function __invoke(Request $request, Handler $handler): Response {
|
||||
$response = $handler->handle($request);
|
||||
if ($this->service->isLoggedIn()) {
|
||||
return $response;
|
||||
}
|
||||
$content = $response->getBody();
|
||||
$response = $factory->createResponse(200)->withBody($content);
|
||||
return $response;
|
||||
}
|
||||
}
|
30
provm/common/Service/Database.php
Normal file
30
provm/common/Service/Database.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace ProVM\Common\Service;
|
||||
|
||||
use \ORM;
|
||||
use \Model;
|
||||
use ProVM\Common\Service\Database\MySQL;
|
||||
|
||||
class Database {
|
||||
protected $settings;
|
||||
public function __construct($settings) {
|
||||
$this->settings = $settings;
|
||||
}
|
||||
public function load() {
|
||||
foreach ($this->settings->dbs as $name => $data) {
|
||||
switch (strtolower($data->engine)) {
|
||||
case 'mysql':
|
||||
$obj = new MySQL($data);
|
||||
break;
|
||||
}
|
||||
ORM::configure($obj->dsn(), null, $name);
|
||||
if ($obj->hasUser()) {
|
||||
ORM::configure('username', $data->user->name, $name);
|
||||
ORM::configure('password', $data->user->password, $name);
|
||||
}
|
||||
}
|
||||
if (isset($this->settings->short_names)) {
|
||||
Model::$short_table_names = $this->settings->short_names;
|
||||
}
|
||||
}
|
||||
}
|
25
provm/common/Service/Database/DSN.php
Normal file
25
provm/common/Service/Database/DSN.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
namespace ProVM\Common\Service\Database;
|
||||
|
||||
class DSN {
|
||||
public $engine;
|
||||
public function __construct(string $engine) {
|
||||
$this->engine = $engine;
|
||||
}
|
||||
public $pairs;
|
||||
public function addPair($name, $value) {
|
||||
if ($this->pairs === null) {
|
||||
$this->pairs = [];
|
||||
}
|
||||
$this->pairs []= [$name, $value];
|
||||
return $this;
|
||||
}
|
||||
public function __toString() {
|
||||
return implode(':', [
|
||||
$this->engine,
|
||||
implode(';', array_map(function($item) {
|
||||
return implode('=', $item);
|
||||
}, $this->pairs))
|
||||
]);
|
||||
}
|
||||
}
|
21
provm/common/Service/Database/MySQL.php
Normal file
21
provm/common/Service/Database/MySQL.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
namespace ProVM\Common\Service\Database;
|
||||
|
||||
class MySQL {
|
||||
protected $settings;
|
||||
public function __construct($settings) {
|
||||
$this->settings = $settings;
|
||||
}
|
||||
public function dsn(): string {
|
||||
$dsn = (new DSN($this->settings->engine))
|
||||
->addPair('host', $this->settings->host->name);
|
||||
if (isset($this->settings->host->port)) {
|
||||
$dsn->addPair('port', $this->settings->host->port);
|
||||
}
|
||||
$dsn->addPair('dbname', $this->settings->name);
|
||||
return '' . $dsn;
|
||||
}
|
||||
public function hasUser(): bool {
|
||||
return true;
|
||||
}
|
||||
}
|
19
provm/common/Service/Migrator.php
Normal file
19
provm/common/Service/Migrator.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
namespace ProVM\Common\Service;
|
||||
|
||||
class Migrator {
|
||||
protected $schema_filename;
|
||||
protected $migrations_folder;
|
||||
protected $seeds_folder;
|
||||
public function __construct(string $schema_filename, string $migrations_folder, string $seeds_folder) {
|
||||
$this->schema_filename = $schema_filename;
|
||||
$this->migrations_folder = $migrations_folder;
|
||||
$this->seeds_folder = $seeds_folder;
|
||||
}
|
||||
protected $schema;
|
||||
public function schema() {
|
||||
if ($this->schema === null) {
|
||||
$file = new \File($this->schema_filename);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user