v0.1.0
This commit is contained in:
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/
|
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
|
37
backend/Py.Dockerfile
Normal file
37
backend/Py.Dockerfile
Normal file
@ -0,0 +1,37 @@
|
||||
FROM continuumio/miniconda3 as build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./python /app/src
|
||||
|
||||
RUN conda env create -f src/environment.yml
|
||||
|
||||
#RUN echo "conda activate cryptos" >> ~/.bashrc
|
||||
#SHELL ["/bin/bash", "--login", "-c"]
|
||||
|
||||
RUN conda install -c conda-forge conda-pack
|
||||
|
||||
RUN conda pack -n cryptos -o /tmp/env.tar && \
|
||||
mkdir /venv && cd /venv && tar xf /tmp/env.tar && \
|
||||
rm /tmp/env.tar
|
||||
|
||||
RUN /venv/bin/conda-unpack
|
||||
|
||||
|
||||
FROM python:buster as runtime
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./python /app/src
|
||||
COPY ./api/bin /app/bin
|
||||
|
||||
COPY --from=build /venv /venv
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
RUN pip install pyinstaller
|
||||
|
||||
RUN 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
|
||||
|
||||
ENTRYPOINT [ "/bin/bash" ]
|
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);
|
||||
}
|
||||
}
|
66
backend/api/common/Controller/Coins.php
Normal file
66
backend/api/common/Controller/Coins.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
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();
|
||||
}
|
||||
}
|
60
backend/api/common/Service/Update.php
Normal file
60
backend/api/common/Service/Update.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
namespace ProVM\Crypto\Common\Service;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use ProVM\Common\Factory\Model as Factory;
|
||||
use ProVM\Crypto\Coin;
|
||||
|
||||
class Update {
|
||||
protected $factory;
|
||||
protected $execs;
|
||||
public function __construct(Factory $factory, array $executables) {
|
||||
$this->factory = $factory;
|
||||
$this->execs = $executables;
|
||||
$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;
|
||||
}*/
|
||||
$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) {
|
||||
\ORM::raw_execute("INSERT INTO coin_registers (coin_id, type) VALUES (?, ?)", [$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();
|
||||
$exe = [$this->execs[$type]];
|
||||
switch ($type) {
|
||||
case 0:
|
||||
$exe []= '-i ' . $coin->identifier;
|
||||
$exe []= '-c usd,clp';
|
||||
$exe []= 'hist -hi';
|
||||
$exe []= '-f ' . $f->copy()->subYears(10)->timestamp;
|
||||
$exe []= '-t ' . $f->timestamp();
|
||||
break;
|
||||
case 1:
|
||||
$exe []= '-i ' . $coin->identifier;
|
||||
$exe []= 'hist -hi';
|
||||
$exe []= '-s ' . $f->copy()->subYears(10)->year;
|
||||
break;
|
||||
}
|
||||
!d(implode(' ', $exe));
|
||||
$output = shell_exec(implode(' ', $exe));
|
||||
!d($output);
|
||||
}
|
||||
public function run() {
|
||||
}
|
||||
}
|
44
backend/api/composer.json
Normal file
44
backend/api/composer.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"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"
|
||||
},
|
||||
"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);
|
13
backend/api/resources/routes/api/coins.php
Normal file
13
backend/api/resources/routes/api/coins.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?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->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
|
38
backend/api/setup/api/settings.php
Normal file
38
backend/api/setup/api/settings.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?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;
|
||||
},
|
||||
'coingecko' => function(Container $c) {
|
||||
return implode(DIRECTORY_SEPARATOR, [
|
||||
$c->get('locations')->bin,
|
||||
'coingecko'
|
||||
]);
|
||||
},
|
||||
'mindicador' => function(Container $c) {
|
||||
return implode(DIRECTORY_SEPARATOR, [
|
||||
$c->get('locations')->bin,
|
||||
'mindicador'
|
||||
]);
|
||||
}
|
||||
];
|
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();
|
||||
},
|
||||
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('coingecko'),
|
||||
$container->get('mindicador')
|
||||
]
|
||||
);
|
||||
}
|
||||
];
|
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'
|
||||
]);
|
36
backend/api/src/Coin.php
Normal file
36
backend/api/src/Coin.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
namespace ProVM\Crypto;
|
||||
|
||||
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', '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);
|
||||
}
|
||||
|
||||
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 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.
90
backend/python/coingecko.py
Normal file
90
backend/python/coingecko.py
Normal file
@ -0,0 +1,90 @@
|
||||
import argparse
|
||||
import httpx
|
||||
import json
|
||||
import datetime
|
||||
|
||||
|
||||
class CoinGecko:
|
||||
def __init__(self, base_url: str = None):
|
||||
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):
|
||||
url = self.__build_url('coins/list')
|
||||
return self.__get(url)
|
||||
|
||||
def get(self, ids: tuple, currencies: tuple, last_updated: bool = True):
|
||||
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):
|
||||
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()
|
||||
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))
|
10
backend/python/environment.yml
Normal file
10
backend/python/environment.yml
Normal file
@ -0,0 +1,10 @@
|
||||
name: cryptos
|
||||
channels:
|
||||
- defaults
|
||||
dependencies:
|
||||
- httpx=0.17.1
|
||||
- pip=21.1.2
|
||||
- python=3.9.5
|
||||
- setuptools=52.0.0
|
||||
# - pip:
|
||||
# - pyinstaller==4.3
|
72
backend/python/miindicador.py
Normal file
72
backend/python/miindicador.py
Normal file
@ -0,0 +1,72 @@
|
||||
import argparse
|
||||
import httpx
|
||||
import json
|
||||
import datetime
|
||||
|
||||
|
||||
class MiIndicador:
|
||||
def __init__(self, base_url: str = None):
|
||||
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):
|
||||
url = self.__build_url('')
|
||||
return self.__get(url)
|
||||
|
||||
def get(self, indicador: str, fecha: str = None):
|
||||
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):
|
||||
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()
|
||||
mi = MiIndicador(args.url)
|
||||
if 'historical' in args and args.historical:
|
||||
print(mi.historical(args.indicador, args.since))
|
||||
exit()
|
||||
print(mi.get(args.indicador))
|
30
backend/python/requirements.txt
Normal file
30
backend/python/requirements.txt
Normal file
@ -0,0 +1,30 @@
|
||||
# This file may be used to create an environment using:
|
||||
# $ conda create --name <env> --file <this file>
|
||||
# platform: win-64
|
||||
altgraph=0.17=pypi_0
|
||||
ca-certificates=2021.5.25=haa95532_1
|
||||
certifi=2021.5.30=py39haa95532_0
|
||||
future=0.18.2=pypi_0
|
||||
h11=0.12.0=pyhd3eb1b0_0
|
||||
h2=4.0.0=py39haa95532_3
|
||||
hpack=4.0.0=py_0
|
||||
httpcore=0.12.3=pyhd3eb1b0_0
|
||||
httpx=0.17.1=pyhd3eb1b0_0
|
||||
hyperframe=6.0.1=pyhd3eb1b0_0
|
||||
idna=2.10=pyhd3eb1b0_0
|
||||
openssl=1.1.1k=h2bbff1b_0
|
||||
pefile=2021.5.24=pypi_0
|
||||
pip=21.1.2=py39haa95532_0
|
||||
pyinstaller=4.3=pypi_0
|
||||
pyinstaller-hooks-contrib=2021.1=pypi_0
|
||||
python=3.9.5=h6244533_3
|
||||
pywin32-ctypes=0.2.0=pypi_0
|
||||
rfc3986=1.4.0=py_0
|
||||
setuptools=52.0.0=py39haa95532_0
|
||||
sniffio=1.2.0=py39haa95532_1
|
||||
sqlite=3.35.4=h2bbff1b_0
|
||||
tzdata=2020f=h52ac0ba_0
|
||||
vc=14.2=h21ff451_1
|
||||
vs2015_runtime=14.27.29016=h5e58377_2
|
||||
wheel=0.36.2=pyhd3eb1b0_0
|
||||
wincertstore=0.2=py39h2bbff1b_0
|
77
docker-compose.yml
Normal file
77
docker-compose.yml
Normal file
@ -0,0 +1,77 @@
|
||||
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
|
||||
|
||||
# python:
|
||||
# container_name: python
|
||||
# build:
|
||||
# context: ./backend
|
||||
# dockerfile: Py.Dockerfile
|
||||
# volumes:
|
||||
# - ./backend/python:/app/src
|
||||
# - ./backend/api/bin:/app/bin
|
||||
# tty: true
|
||||
# stdin_open: true
|
||||
|
||||
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
|
15
frontend/common/Controller/Coins.php
Normal file
15
frontend/common/Controller/Coins.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace ProVM\Crypto\Common\Controller;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Slim\Views\Blade as View;
|
||||
|
||||
class Coins {
|
||||
public function __invoke(Request $request, Response $response, View $view): Response {
|
||||
return $view->render($response, 'coins.list');
|
||||
}
|
||||
public function get(Request $request, Response $response, View $view, $coin_id): Response {
|
||||
return $view->render($response, 'coins.show', compact('coin_id'));
|
||||
}
|
||||
}
|
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
|
264
frontend/resources/views/coins/list.blade.php
Normal file
264
frontend/resources/views/coins/list.blade.php
Normal file
@ -0,0 +1,264 @@
|
||||
@extends('coins.base')
|
||||
|
||||
@section('page_content')
|
||||
<h3 class="ui basic segment header">Monedas</h3>
|
||||
<table class="ui striped table" id="coins">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Moneda
|
||||
</th>
|
||||
<th>
|
||||
Código
|
||||
</th>
|
||||
<th class="right aligned">
|
||||
<i class="plus icon"></i>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
<div class="ui modal" id="coin_modal">
|
||||
<div class="header"></div>
|
||||
<div class="content"></div>
|
||||
<div class="actions">
|
||||
<div class="ui approve button"></div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('page_scripts')
|
||||
<script type="text/javascript">
|
||||
function reload() {
|
||||
window.location.reload()
|
||||
}
|
||||
const coins = {
|
||||
urls: {
|
||||
api: '{{$urls->api}}',
|
||||
base: '{{$urls->base}}'
|
||||
},
|
||||
modal: null,
|
||||
setup: function(table, modal) {
|
||||
this.modal = modal
|
||||
table.find('plus icon').css('cursor', 'pointer').click(() => {
|
||||
addCoin()
|
||||
})
|
||||
table.find('tbody').append($('<div></div>').attr('class', 'ui active dimmer').append(
|
||||
$('<div></div>').attr('class', 'ui loader')
|
||||
))
|
||||
table.find('.plus.icon').css('cursor', 'pointer').click((e) => {
|
||||
this.add()
|
||||
})
|
||||
this.load(table)
|
||||
},
|
||||
load: function(table) {
|
||||
const url = this.urls.api + '/coins'
|
||||
const body = table.find('tbody')
|
||||
$.getJSON(url, (data) => {
|
||||
body.html('')
|
||||
data.coins.forEach((v) => {
|
||||
const u = this.urls.base + '/coin/' + v.id
|
||||
const tr = $('<tr></tr>').append(
|
||||
$('<td></td>').append($('<a></a>').attr('href', u).html(v.name))
|
||||
).append(
|
||||
$('<td></td>').append($('<a></a>').attr('href', u).html(v.code))
|
||||
).append(
|
||||
$('<td></td>').attr('class', 'right aligned').append(
|
||||
$('<i></i>').attr('class', 'edit icon edit_coin').attr('data-id', v.id)
|
||||
).append(
|
||||
$('<i></i>').attr('class', 'minus icon minus_coin').attr('data-id', v.id)
|
||||
)
|
||||
)
|
||||
body.append(tr)
|
||||
})
|
||||
$('.edit_coin').css('cursor', 'pointer').click((e) => {
|
||||
const elem = $(e.target)
|
||||
const id = elem.attr('data-id')
|
||||
this.edit(id)
|
||||
})
|
||||
$('.minus_coin').css('cursor', 'pointer').click((e) => {
|
||||
const elem = $(e.target)
|
||||
const id = elem.attr('data-id')
|
||||
this.delete(id)
|
||||
})
|
||||
}).catch(() => {
|
||||
body.html('')
|
||||
})
|
||||
},
|
||||
add: function() {
|
||||
this.modal.find('.header').html('Agregar')
|
||||
this.modal.find('.actions .approve.button').html('Agregar')
|
||||
this.modal.find('.content').html('')
|
||||
const form = $('<form></form>').attr('class', 'ui form').append(
|
||||
$('<div></div>').attr('class', 'field').append(
|
||||
$('<label></label>').html('Nombre')
|
||||
).append(
|
||||
$('<input />').attr('type', 'text').attr('name', 'name')
|
||||
)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'field').append(
|
||||
$('<label></label>').html('Código')
|
||||
).append(
|
||||
$('<input />').attr('type', 'text').attr('name', 'code').attr('size', '5').attr('maxlength', '5')
|
||||
)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'field').append(
|
||||
$('<label></label>').html('Prefijo')
|
||||
).append(
|
||||
$('<input />').attr('type', 'text').attr('name', 'prefix')
|
||||
)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'field').append(
|
||||
$('<label></label>').html('Sufijo')
|
||||
).append(
|
||||
$('<input />').attr('type', 'text').attr('name', 'suffix')
|
||||
)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'field').append(
|
||||
$('<label></label>').html('Decimales')
|
||||
).append(
|
||||
$('<input />').attr('type', 'text').attr('name', 'decimals')
|
||||
)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'field').append(
|
||||
$('<label></label>').html('Url')
|
||||
).append(
|
||||
$('<input />').attr('type', 'text').attr('name', 'ref_url')
|
||||
)
|
||||
)
|
||||
this.modal.find('.content').append(form)
|
||||
this.modal.modal('show')
|
||||
this.modal.modal({
|
||||
onApprove: () => {
|
||||
const url = this.urls.api + '/coins/add'
|
||||
const fields = [
|
||||
'name',
|
||||
'code',
|
||||
'prefix',
|
||||
'suffix',
|
||||
'decimals',
|
||||
'ref_url'
|
||||
]
|
||||
let data = {}
|
||||
fields.forEach((k) => {
|
||||
const val = form.find("[name='" + k + "']").val()
|
||||
if (val != '') {
|
||||
data[k] = val
|
||||
}
|
||||
})
|
||||
data = JSON.stringify(data)
|
||||
$.post(url, data, (response) => {
|
||||
if (response.created === true) {
|
||||
return reload()
|
||||
}
|
||||
}, 'json')
|
||||
}
|
||||
})
|
||||
},
|
||||
edit: function(id) {
|
||||
const url = this.urls.api + '/coin/' + id
|
||||
$.getJSON(url, (data) => {
|
||||
const elem = data.coin
|
||||
this.modal.find('.header').html('Editar')
|
||||
this.modal.find('.actions .approve.button').html('Editar')
|
||||
this.modal.find('.content').html('')
|
||||
const form = $('<form></form>').attr('class', 'ui form').append(
|
||||
$('<div></div>').attr('class', 'field').append(
|
||||
$('<label></label>').html('Nombre')
|
||||
).append(
|
||||
$('<input />').attr('type', 'text').attr('name', 'name').attr('value', elem.name)
|
||||
)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'field').append(
|
||||
$('<label></label>').html('Código')
|
||||
).append(
|
||||
$('<input />').attr('type', 'text').attr('name', 'code').attr('size', '5').attr('maxlength', '5').attr('value', elem.code)
|
||||
)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'field').append(
|
||||
$('<label></label>').html('Prefijo')
|
||||
).append(
|
||||
$('<input />').attr('type', 'text').attr('name', 'prefix').attr('value', elem.prefix)
|
||||
)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'field').append(
|
||||
$('<label></label>').html('Sufijo')
|
||||
).append(
|
||||
$('<input />').attr('type', 'text').attr('name', 'suffix').attr('value', elem.suffix)
|
||||
)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'field').append(
|
||||
$('<label></label>').html('Decimales')
|
||||
).append(
|
||||
$('<input />').attr('type', 'text').attr('name', 'decimals').attr('value', elem.decimals)
|
||||
)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'field').append(
|
||||
$('<label></label>').html('Url')
|
||||
).append(
|
||||
$('<input />').attr('type', 'text').attr('name', 'ref_url').attr('value', elem.ref_url)
|
||||
)
|
||||
)
|
||||
this.modal.find('.content').append(form)
|
||||
this.modal.modal('show')
|
||||
this.modal.find('.actions .approve.button').click(() => {
|
||||
const url = this.urls.api + '/coin/' + id + '/edit'
|
||||
const fields = [
|
||||
'name',
|
||||
'code',
|
||||
'prefix',
|
||||
'suffix',
|
||||
'decimals',
|
||||
'ref_url'
|
||||
]
|
||||
let data = {}
|
||||
fields.forEach((k) => {
|
||||
const val = form.find("[name='" + k + "']").val()
|
||||
if (val != '' && val != elem[k]) {
|
||||
data[k] = val
|
||||
}
|
||||
})
|
||||
data = JSON.stringify(data)
|
||||
$.ajax(
|
||||
{
|
||||
url: url,
|
||||
method: 'PUT',
|
||||
data: data,
|
||||
success: (response) => {
|
||||
if (response.edited === true) {
|
||||
return reload()
|
||||
}
|
||||
},
|
||||
dataType: 'json'
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
},
|
||||
delete: function(id) {
|
||||
const url = this.urls.api + '/coin/' + id + '/delete'
|
||||
$.ajax(
|
||||
{
|
||||
url: url,
|
||||
method: 'DELETE',
|
||||
success: (response) => {
|
||||
console.debug(response)
|
||||
if (response.deleted === true) {
|
||||
return reload()
|
||||
}
|
||||
},
|
||||
dataType: 'json'
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
$(document).ready(() => {
|
||||
const modal = $('#coin_modal')
|
||||
modal.modal()
|
||||
modal.modal('setting', 'closable', true)
|
||||
const table = $('#coins')
|
||||
|
||||
coins.setup(table, modal)
|
||||
})
|
||||
</script>
|
||||
@endpush
|
31
frontend/resources/views/coins/show.blade.php
Normal file
31
frontend/resources/views/coins/show.blade.php
Normal file
@ -0,0 +1,31 @@
|
||||
@extends('coins.base')
|
||||
|
||||
@section('page_content')
|
||||
<h3 class="ui header">Moneda - <span class="coin_name"></span></h3>
|
||||
<div class="ui list" id="coin_data">
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('page_scripts')
|
||||
<script type="text/javascript">
|
||||
$(document).ready(() => {
|
||||
const id = '{{$coin_id}}'
|
||||
const api_url = '{{$urls->api}}'
|
||||
const url = api_url + '/coin/' + id
|
||||
$.getJSON(url, (data) => {
|
||||
$('.coin_name').html(data.coin.name)
|
||||
$('#coin_data').append(
|
||||
$('<div></div>').attr('class', 'item').html('Código: ' + data.coin.code)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'item').html('Prefijo: ' + data.coin.prefix)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'item').html('Sufijo: ' + data.coin.suffix)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'item').html('Decimales: ' + data.coin.decimals)
|
||||
).append(
|
||||
$('<div></div>').attr('class', 'item').html('Url: ' + data.coin.ref_url)
|
||||
)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
@endpush
|
5
frontend/resources/views/home.blade.php
Normal file
5
frontend/resources/views/home.blade.php
Normal file
@ -0,0 +1,5 @@
|
||||
@extends('layout.base')
|
||||
|
||||
@section('page_content')
|
||||
Home
|
||||
@endsection
|
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'
|
||||
]);
|
68
frontend/setup/web/settings.php
Normal file
68
frontend/setup/web/settings.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
use Psr\Container\ContainerInterface as Container;
|
||||
|
||||
return [
|
||||
'login_time' => 5*60*60,
|
||||
'locations' => function() {
|
||||
$arr = ['base' => dirname(__DIR__, 2)];
|
||||
$arr['resources'] = implode(DIRECTORY_SEPARATOR, [
|
||||
$arr['base'],
|
||||
'resources'
|
||||
]);
|
||||
$arr['data'] = implode(DIRECTORY_SEPARATOR, [
|
||||
$arr['resources'],
|
||||
'data'
|
||||
]);
|
||||
$arr['routes'] = implode(DIRECTORY_SEPARATOR, [
|
||||
$arr['resources'],
|
||||
'routes'
|
||||
]);
|
||||
$arr['templates'] = implode(DIRECTORY_SEPARATOR, [
|
||||
$arr['resources'],
|
||||
'views'
|
||||
]);
|
||||
$arr['cache'] = implode(DIRECTORY_SEPARATOR, [
|
||||
$arr['base'],
|
||||
'cache'
|
||||
]);
|
||||
return (object) $arr;
|
||||
},
|
||||
'urls' => function(Container $c) {
|
||||
$arr = ['base' => $c->get('base_url'), 'api' => $_ENV['API_URL']];
|
||||
return (object) $arr;
|
||||
},
|
||||
'scripts' => function() {
|
||||
$arr = [
|
||||
[
|
||||
'full' => '<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>'
|
||||
],
|
||||
[
|
||||
'full' => '<script src="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.8.7/semantic.min.js" integrity="sha512-1Nyd5H4Aad+OyvVfUOkO/jWPCrEvYIsQENdnVXt1+Jjc4NoJw28nyRdrpOCyFH4uvR3JmH/5WmfX1MJk2ZlhgQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>'
|
||||
]
|
||||
];
|
||||
foreach ($arr as $i => $a) {
|
||||
$arr[$i] = (object) $a;
|
||||
}
|
||||
return $arr;
|
||||
},
|
||||
'styles' => function() {
|
||||
$arr = [
|
||||
[
|
||||
'link' => '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.8.7/semantic.min.css" integrity="sha512-g/MzOGVPy3OQ4ej1U+qe4D/xhLwUn5l5xL0Fa7gdC258ZWVJQGwsbIR47SWMpRxSPjD0tfu/xkilTy+Lhrl3xg==" crossorigin="anonymous" referrerpolicy="no-referrer" />'
|
||||
],
|
||||
[
|
||||
'url' => 'https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.8.7/themes/default/assets/fonts/brand-icons.woff2'
|
||||
],
|
||||
[
|
||||
'url' => 'https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.8.7/themes/default/assets/fonts/icons.woff2'
|
||||
],
|
||||
[
|
||||
'url' => 'https://cdnjs.cloudflare.com/ajax/libs/fomantic-ui/2.8.7/themes/default/assets/fonts/outline-icons.woff2'
|
||||
]
|
||||
];
|
||||
foreach ($arr as $i => $a) {
|
||||
$arr[$i] = (object) $a;
|
||||
}
|
||||
return $arr;
|
||||
}
|
||||
];
|
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);
|
||||
}
|
||||
}
|
||||
}
|
22
provm/src/Login.php
Normal file
22
provm/src/Login.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace ProVM;
|
||||
|
||||
use ProVM\Common\Alias\Model;
|
||||
use ProVM\Common\Define\Model\DateTime as DT;
|
||||
|
||||
/**
|
||||
* @property User $user_id
|
||||
* @property \DateTime $date_time
|
||||
* @property string $token
|
||||
*/
|
||||
class Login extends Model {
|
||||
use DT;
|
||||
|
||||
protected $user;
|
||||
public function user() {
|
||||
if ($this->user === null) {
|
||||
$this->user = $this->childOf(User::class, [Model::SELF_KEY => 'user_id']);
|
||||
}
|
||||
return $this->user;
|
||||
}
|
||||
}
|
15
provm/src/User.php
Normal file
15
provm/src/User.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace ProVM;
|
||||
|
||||
use ProVM\Common\Alias\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $password
|
||||
*/
|
||||
class User extends Model {
|
||||
public function checkPassword(string $password): bool {
|
||||
return password_verify($this->password, $password);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user