feat: poc working

This commit is contained in:
Chen Asraf
2023-02-02 01:13:03 +02:00
parent 90ddeb1502
commit c32cdaf38d
16 changed files with 321 additions and 93 deletions

9
.editorconfig Normal file
View File

@@ -0,0 +1,9 @@
[*]
tab_width = 2
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false

2
.eslintignore Normal file
View File

@@ -0,0 +1,2 @@
templates/
scaffolds/

2
.prettierignore Normal file
View File

@@ -0,0 +1,2 @@
templates/
scaffolds/

15
.prettierrc Normal file
View File

@@ -0,0 +1,15 @@
{
"printWidth": 100,
"semi": false,
"singleQuote": true,
"trailingComma": "all",
"overrides": [
{
"files": "*.md",
"options": {
"printWidth": 100,
"proseWrap": "always"
}
}
]
}

6
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"cSpell.words": [
"Cospend",
"projectid"
]
}

View File

@@ -25,4 +25,7 @@ It will use the default currency as the base, and will fill existing currencies
<route>autocurrency.page.index</route>
</navigation>
</navigations>
<background-jobs>
<job>OCA\AutoCurrency\Cron\FetchCurrenciesJob</job>
</background-jobs>
</info>

View File

@@ -9,20 +9,24 @@ use OCA\AutoCurrency\AppInfo\Application;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IRequest;
use OCP\BackgroundJob\IJobList;
use OCP\Util;
class PageController extends Controller {
public function __construct(IRequest $request) {
parent::__construct(Application::APP_ID, $request);
}
private IJobList $jobList;
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function index(): TemplateResponse {
Util::addScript(Application::APP_ID, 'autocurrency-main');
public function __construct(IRequest $request, IJobList $jobList) {
parent::__construct(Application::APP_ID, $request);
$this->jobList = $jobList;
}
return new TemplateResponse(Application::APP_ID, 'main');
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function index(): TemplateResponse {
Util::addScript(Application::APP_ID, 'autocurrency-main');
// $this->jobList->add('OCA\AutoCurrency\BackgroundJob\FetchCurrenciesJob');
return new TemplateResponse(Application::APP_ID, 'main');
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace OCA\AutoCurrency\Cron;
use OCA\AutoCurrency\Service\FetchCurrenciesService;
use OCP\BackgroundJob\TimedJob;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\ILogger;
class FetchCurrenciesJob extends TimedJob {
private FetchCurrenciesService $service;
private ILogger $logger;
public function __construct(ITimeFactory $time, FetchCurrenciesService $service, ILogger $logger) {
parent::__construct($time);
$this->service = $service;
$this->logger = $logger;
// Run once a day
$this->setInterval(60);
// $this->setTimeSensitivity(\OCP\BackgroundJob\IJob::TIME_INSENSITIVE);
}
protected function run($arguments) {
$this->logger->info('Running cron job for FetchCurrenciesTask - args: ' . json_encode($arguments));
$this->service->doCron();
}
}

47
lib/Db/CospendProjectMapper.php Executable file
View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
// SPDX-FileCopyrightText: Chen Asraf <contact@casraf.dev>
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace OCA\AutoCurrency\Db;
use OCA\Cospend\Db\Project;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @template-extends QBMapper<Project>
*/
class CospendProjectMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'cospend_projects', Project::class);
}
/**
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws DoesNotExistException
*/
public function find(int $id): Project {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('cospend_projects')
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
return $this->findEntity($qb);
}
/**
* @param string $projectId
* @return array
*/
public function findAll(): array {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('cospend_projects');
return $this->findEntities($qb);
}
}

34
lib/Db/Currency.php Executable file
View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
// SPDX-FileCopyrightText: Chen Asraf <contact@casraf.dev>
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace OCA\AutoCurrency\Db;
use JsonSerializable;
use OCP\AppFramework\Db\Entity;
/**
* @method getId(): int
* @method getName(): string
* @method setName(string $name): void
* @method getExchangeRate(): string
* @method setExchangeRate(string $exchangeRate): void
* @method getProjectId(): string
* @method setProjectId(string $exchangeRate): void
*/
class Currency extends Entity implements JsonSerializable {
protected string $name = '';
protected string $exchangeRate = '';
protected string $projectid = '';
public function jsonSerialize(): array {
return [
'id' => $this->id,
'name' => $this->name,
'exchange_rate' => $this->exchangeRate,
'projectid' => $this->projectid,
];
}
}

48
lib/Db/CurrencyMapper.php Executable file
View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
// SPDX-FileCopyrightText: Chen Asraf <contact@casraf.dev>
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace OCA\AutoCurrency\Db;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @template-extends QBMapper<Currency>
*/
class CurrencyMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'cospend_currencies', Currency::class);
}
/**
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws DoesNotExistException
*/
public function find(int $id, string $projectId): Currency {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('cospend_currencies')
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('projectid', $qb->createNamedParameter($projectId)));
return $this->findEntity($qb);
}
/**
* @param string $projectId
* @return array
*/
public function findAll(string $projectId): array {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('cospend_currencies')
->where($qb->expr()->eq('projectid', $qb->createNamedParameter($projectId)));
return $this->findEntities($qb);
}
}

View File

@@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
// SPDX-FileCopyrightText: Chen Asraf <contact@casraf.dev>
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace OCA\AutoCurrency\Db;
use JsonSerializable;
use OCP\AppFramework\Db\Entity;
/**
* @method getId(): int
* @method getTitle(): string
* @method setTitle(string $title): void
* @method getContent(): string
* @method setContent(string $content): void
* @method getUserId(): string
* @method setUserId(string $userId): void
*/
class Note extends Entity implements JsonSerializable {
protected string $title = '';
protected string $content = '';
protected string $userId = '';
public function jsonSerialize(): array {
return [
'id' => $this->id,
'title' => $this->title,
'content' => $this->content
];
}
}

View File

@@ -1,48 +0,0 @@
<?php
declare(strict_types=1);
// SPDX-FileCopyrightText: Chen Asraf <contact@casraf.dev>
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace OCA\AutoCurrency\Db;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @template-extends QBMapper<Note>
*/
class NoteMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'autocurrency', Note::class);
}
/**
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws DoesNotExistException
*/
public function find(int $id, string $userId): Note {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('autocurrency')
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
return $this->findEntity($qb);
}
/**
* @param string $userId
* @return array
*/
public function findAll(string $userId): array {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('autocurrency')
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
return $this->findEntities($qb);
}
}

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
// SPDX-FileCopyrightText: Chen Asraf <contact@casraf.dev>
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace OCA\AutoCurrency\Service;
class CurrencyNotFound extends \Exception {
}

View File

@@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
// SPDX-FileCopyrightText: Chen Asraf <contact@casraf.dev>
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace OCA\AutoCurrency\Service;
use Exception;
use OCA\AutoCurrency\Service\CurrencyNotFound;
use OCA\AutoCurrency\Db\CospendProjectMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCA\AutoCurrency\Db\Currency;
use OCA\AutoCurrency\Db\CurrencyMapper;
use OCP\ILogger;
class FetchCurrenciesService {
private static $EXCHANGE_URL = 'https://api.exchangerate.host/latest?base={base}';
private CurrencyMapper $currencyMapper;
private CospendProjectMapper $projectMapper;
private ILogger $logger;
public function __construct(CurrencyMapper $currencyMapper, CospendProjectMapper $projectMapper, ILogger $logger) {
$this->currencyMapper = $currencyMapper;
$this->projectMapper = $projectMapper;
$this->logger = $logger;
}
public function doCron(): void {
$projects = $this->projectMapper->findAll();
$currencyMap = [];
foreach ($projects as $project) {
$baseCurrency = $this->getCurrencyName($project->getCurrencyname());
if (isset($currencyMap[$baseCurrency])) {
$json = $currencyMap[$baseCurrency];
} else {
// request currency exchange rates from the API
// $this->logger->info('Fetching exchange rates for base currency ' . $baseCurrency);
print('Fetching exchange rates for base currency ' . $baseCurrency);
$fp = fopen(str_replace('{base}', $baseCurrency, FetchCurrenciesService::$EXCHANGE_URL), 'r');
$data = stream_get_contents($fp);
fclose($fp);
$json = json_decode($data, true);
// $this->logger->info('Fetched exchange rates for base currency: ' . json_encode($json));
print('Fetched exchange rates for base currency: ' . json_encode($json));
if ($json['success'] == false) {
// $this->logger->error(new \Error('Failed to fetch exchange rates for base currency ' . $baseCurrency));
print(new \Error('Failed to fetch exchange rates for base currency ' . $baseCurrency));
continue;
}
$currencyMap[$baseCurrency] = $json;
}
$currencies = $this->findAll($project->id);
foreach ($currencies as $currency) {
$cur = $this->getCurrencyName($currency->getName());
$currency->setExchangeRate(1 / $json['rates'][$cur]);
// $this->logger->info('Setting exchange rate for currency ' . $cur . ' to ' . $json['rates'][$cur]);
print('Setting exchange rate for currency ' . $cur . ' to ' . (1 / $json['rates'][$cur]));
$this->currencyMapper->update($currency);
}
}
}
private function getCurrencyName(string $name): string {
// find 3-letter currency code for the base currency
preg_match('/([A-Z]{3})/', $name, $matches);
// $this->logger->info('Matches: ' . json_encode($matches));
print('Matches: ' . json_encode($matches));
if (count($matches) === 2) {
$name = $matches[1];
}
return $name;
}
/**
* @return list<Currency>
*/
public function findAll(string $projectId): array {
return $this->currencyMapper->findAll($projectId);
}
/**
* @return never
*/
private function handleException(Exception $e) {
if ($e instanceof DoesNotExistException ||
$e instanceof MultipleObjectsReturnedException) {
// TODO determine type
throw new CurrencyNotFound($e->getMessage());
} else {
throw $e;
}
}
}

View File

@@ -5,5 +5,5 @@ declare(strict_types=1);
namespace OCA\AutoCurrency\Service;
class NoteNotFound extends \Exception {
class ProjectNotFound extends \Exception {
}