Compare commits

...

8 Commits

Author SHA1 Message Date
4141f42ad8 Python build image 2021-07-14 10:14:16 -04:00
3562021da7 Docker Compose with Cron 2021-07-14 10:12:53 -04:00
2859574848 Frontend coins and charts 2021-07-14 10:12:22 -04:00
4a321b4105 Python with Flask 2021-07-14 10:11:54 -04:00
7a6e9dec95 Crontab 2021-07-14 10:09:26 -04:00
8e1e644904 Backend coins and update 2021-07-14 10:08:48 -04:00
c930ffc55e Logging for python files 2021-07-07 08:56:05 -04:00
8c870cb43f Env samples 2021-07-07 08:55:49 -04:00
34 changed files with 798 additions and 173 deletions

4
.common.env.sample Normal file
View File

@ -0,0 +1,4 @@
DEBUG=true
DB_HOST="db"
ENV='dev'
API_URL='http://localhost:8081'

4
.db.env.sample Normal file
View File

@ -0,0 +1,4 @@
MYSQL_ROOT_PASSWORD=
MYSQL_USER=
MYSQL_PASSWORD=
MYSQL_DATABASE=

2
.env.sample Normal file
View File

@ -0,0 +1,2 @@
FRONTEND_PORT=#Port for frontend UI
BACKEND_PORT=#Port for backend app

9
backend/Cron.Dockerfile Normal file
View 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"]

View File

@ -1,37 +1,13 @@
FROM continuumio/miniconda3 as build
FROM python:3.9 as runtime
WORKDIR /app
WORKDIR /app/
COPY ./python /app/src
COPY ./python/ /app/
RUN conda env create -f src/environment.yml
RUN python -m pip install gunicorn httpx flask
#RUN echo "conda activate cryptos" >> ~/.bashrc
#SHELL ["/bin/bash", "--login", "-c"]
EXPOSE 5001
RUN conda install -c conda-forge conda-pack
CMD ["gunicorn", "-b", "0.0.0.0:5001", "crypto.index:app"]
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" ]
#ENTRYPOINT [ "/bin/bash" ]

View File

@ -63,4 +63,108 @@ class Coins {
$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);
}
}

View File

@ -1,16 +1,18 @@
<?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 $execs;
public function __construct(Factory $factory, array $executables) {
protected $client;
public function __construct(Factory $factory, Client $client) {
$this->factory = $factory;
$this->execs = $executables;
$this->client = $client;
$this->load();
}
public function load() {
@ -22,39 +24,154 @@ class Update {
}
protected $coins;
public function register(int $coin_id, int $type = 0) {
/*if (array_search($coin_id, $this->coins[$type]) !== false) {
return;
}*/
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) {
\ORM::raw_execute("INSERT INTO coin_registers (coin_id, type) VALUES (?, ?)", [$coin_id, $type]);
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();
$exe = [$this->execs[$type]];
$url = [];
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;
return $this->getHistoricalCrypto($coin);
case 1:
$exe []= '-i ' . $coin->identifier;
$exe []= 'hist -hi';
$exe []= '-s ' . $f->copy()->subYears(10)->year;
break;
return $this->getHistoricalIndicador($coin);
default:
return false;
}
!d(implode(' ', $exe));
$output = shell_exec(implode(' ', $exe));
!d($output);
}
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;
}
}

View File

@ -11,7 +11,8 @@
"provm/models": "^1.0-rc",
"spatie/crypto": "^2.0",
"robmorgan/phinx": "^0.12.5",
"nesbot/carbon": "^2.49"
"nesbot/carbon": "^2.49",
"guzzlehttp/guzzle": "^7.3"
},
"require-dev": {
"phpunit/phpunit": "^9.5",

View File

@ -9,5 +9,11 @@ $app->group('/coins', function($app) {
$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']);
});

View File

@ -23,16 +23,5 @@ return [
]);
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'
]);
}
'python_api' => $_ENV['PYTHON_API']
];

View File

@ -12,13 +12,13 @@ return [
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('coingecko'),
$container->get('mindicador')
]
$container->get(GuzzleHttp\Client::class)
);
}
];

View File

@ -1,6 +1,7 @@
<?php
namespace ProVM\Crypto;
use Carbon\Carbon;
use ProVM\Common\Alias\Model;
use ProVM\Common\Factory\Model as Factory;
@ -16,19 +17,52 @@ use ProVM\Common\Factory\Model as Factory;
*/
class Coin extends Model {
protected static $_table = 'coins';
protected static $fields = ['code', 'name', 'prefix', 'suffix', 'decimals', 'ref_url'];
protected static $fields = ['code', 'name', 'identifier', 'prefix', 'suffix', 'decimals', 'ref_url'];
public function format(float $value): string {
$output = [];
if ($this->prefix == '') {
if ($this->prefix != '') {
$output []= $this->prefix;
}
$output []= number_format($value, $this->decimals ?? 0, ',', '.');
if ($this->suffix == '') {
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();

Binary file not shown.

Binary file not shown.

View File

@ -1 +1 @@
0 2 * * * curl backend/update
0 2 * * * curl http://backend/update

Binary file not shown.

View File

@ -6,6 +6,8 @@ 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
@ -30,10 +32,14 @@ class CoinGecko:
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)]),
@ -48,6 +54,8 @@ class CoinGecko:
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_,
@ -75,16 +83,19 @@ if __name__ == '__main__':
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))
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))

View 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)

View File

@ -6,6 +6,8 @@ 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
@ -30,10 +32,14 @@ class MiIndicador:
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])
@ -45,6 +51,8 @@ class MiIndicador:
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])
@ -65,6 +73,9 @@ if __name__ == '__main__':
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))

View File

@ -1,10 +0,0 @@
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

View File

@ -1,30 +0,0 @@
# 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

3
build.python.bat Normal file
View File

@ -0,0 +1,3 @@
@echo off
docker build -f "./backend/Py.Dockerfile" .

6
build.sh Normal file
View 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

View File

@ -24,7 +24,7 @@ services:
build:
context: ./frontend
dockerfile: PHP.Dockerfile
env_file: common.env
env_file: .common.env
volumes:
- .:/app
depends_on:
@ -36,23 +36,29 @@ services:
context: ./backend
dockerfile: PHP.Dockerfile
env_file:
- common.env
- .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
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

View File

@ -12,4 +12,6 @@ class Coins {
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 {
}
}

View File

@ -37,23 +37,26 @@
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('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)
this.load()
},
load: function(table) {
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) => {
@ -95,6 +98,12 @@
).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')
@ -134,6 +143,7 @@
const fields = [
'name',
'code',
'identifier',
'prefix',
'suffix',
'decimals',
@ -149,7 +159,7 @@
data = JSON.stringify(data)
$.post(url, data, (response) => {
if (response.created === true) {
return reload()
return this.load()
}
}, 'json')
}
@ -226,7 +236,7 @@
data: data,
success: (response) => {
if (response.edited === true) {
return reload()
return this.load()
}
},
dataType: 'json'
@ -244,7 +254,7 @@
success: (response) => {
console.debug(response)
if (response.deleted === true) {
return reload()
return this.load()
}
},
dataType: 'json'

View File

@ -2,30 +2,131 @@
@section('page_content')
<h3 class="ui header">Moneda - <span class="coin_name"></span></h3>
<div class="ui list" id="coin_data">
</div>
<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">
$(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)
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: ' + data.coin.code)
$('<div></div>').attr('class', 'item').html('Código: ' + this.data.code)
).append(
$('<div></div>').attr('class', 'item').html('Prefijo: ' + data.coin.prefix)
$('<div></div>').attr('class', 'item').html('Prefijo: ' + this.data.prefix)
).append(
$('<div></div>').attr('class', 'item').html('Sufijo: ' + data.coin.suffix)
$('<div></div>').attr('class', 'item').html('Sufijo: ' + this.data.suffix)
).append(
$('<div></div>').attr('class', 'item').html('Decimales: ' + data.coin.decimals)
$('<div></div>').attr('class', 'item').html('Decimales: ' + this.data.decimals)
).append(
$('<div></div>').attr('class', 'item').html('Url: ' + data.coin.ref_url)
$('<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

View File

@ -1,5 +1,220 @@
@extends('layout.base')
@section('page_content')
Home
<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

View File

@ -33,34 +33,33 @@ return [
},
'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>'
]
'<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' => '<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'
]
'<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;

5
run.python.bat Normal file
View File

@ -0,0 +1,5 @@
@echo off
set pwd=%cd%
docker run -it -v "%pwd%/backend/python:/app/src" -v "%pwd%/backend/automation/bin:/app/bin" -v "%pwd%/build.sh:/app/build.sh" crypto_python