mirror of
https://github.com/chenasraf/nextcloud-autocurrency.git
synced 2026-05-17 17:28:06 +00:00
feat: poc working
This commit is contained in:
9
.editorconfig
Normal file
9
.editorconfig
Normal 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
2
.eslintignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
templates/
|
||||||
|
scaffolds/
|
||||||
2
.prettierignore
Normal file
2
.prettierignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
templates/
|
||||||
|
scaffolds/
|
||||||
15
.prettierrc
Normal file
15
.prettierrc
Normal 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
6
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"cSpell.words": [
|
||||||
|
"Cospend",
|
||||||
|
"projectid"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -25,4 +25,7 @@ It will use the default currency as the base, and will fill existing currencies
|
|||||||
<route>autocurrency.page.index</route>
|
<route>autocurrency.page.index</route>
|
||||||
</navigation>
|
</navigation>
|
||||||
</navigations>
|
</navigations>
|
||||||
|
<background-jobs>
|
||||||
|
<job>OCA\AutoCurrency\Cron\FetchCurrenciesJob</job>
|
||||||
|
</background-jobs>
|
||||||
</info>
|
</info>
|
||||||
|
|||||||
@@ -9,20 +9,24 @@ use OCA\AutoCurrency\AppInfo\Application;
|
|||||||
use OCP\AppFramework\Controller;
|
use OCP\AppFramework\Controller;
|
||||||
use OCP\AppFramework\Http\TemplateResponse;
|
use OCP\AppFramework\Http\TemplateResponse;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
|
use OCP\BackgroundJob\IJobList;
|
||||||
use OCP\Util;
|
use OCP\Util;
|
||||||
|
|
||||||
class PageController extends Controller {
|
class PageController extends Controller {
|
||||||
public function __construct(IRequest $request) {
|
private IJobList $jobList;
|
||||||
parent::__construct(Application::APP_ID, $request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public function __construct(IRequest $request, IJobList $jobList) {
|
||||||
* @NoAdminRequired
|
parent::__construct(Application::APP_ID, $request);
|
||||||
* @NoCSRFRequired
|
$this->jobList = $jobList;
|
||||||
*/
|
}
|
||||||
public function index(): TemplateResponse {
|
|
||||||
Util::addScript(Application::APP_ID, 'autocurrency-main');
|
|
||||||
|
|
||||||
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');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
lib/Cron/FetchCurrenciesJob.php
Normal file
27
lib/Cron/FetchCurrenciesJob.php
Normal 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
47
lib/Db/CospendProjectMapper.php
Executable 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
34
lib/Db/Currency.php
Executable 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
48
lib/Db/CurrencyMapper.php
Executable 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
9
lib/Service/CurrencyNotFound.php
Executable file
9
lib/Service/CurrencyNotFound.php
Executable 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 {
|
||||||
|
}
|
||||||
103
lib/Service/FetchCurrenciesService.php
Normal file
103
lib/Service/FetchCurrenciesService.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,5 +5,5 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace OCA\AutoCurrency\Service;
|
namespace OCA\AutoCurrency\Service;
|
||||||
|
|
||||||
class NoteNotFound extends \Exception {
|
class ProjectNotFound extends \Exception {
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user