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>
|
||||
</navigation>
|
||||
</navigations>
|
||||
<background-jobs>
|
||||
<job>OCA\AutoCurrency\Cron\FetchCurrenciesJob</job>
|
||||
</background-jobs>
|
||||
</info>
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
class NoteNotFound extends \Exception {
|
||||
class ProjectNotFound extends \Exception {
|
||||
}
|
||||
Reference in New Issue
Block a user