diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..c1d757f
--- /dev/null
+++ b/.editorconfig
@@ -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
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..a52a73c
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,2 @@
+templates/
+scaffolds/
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..a52a73c
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,2 @@
+templates/
+scaffolds/
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..548c817
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,15 @@
+{
+ "printWidth": 100,
+ "semi": false,
+ "singleQuote": true,
+ "trailingComma": "all",
+ "overrides": [
+ {
+ "files": "*.md",
+ "options": {
+ "printWidth": 100,
+ "proseWrap": "always"
+ }
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..a1791be
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "cSpell.words": [
+ "Cospend",
+ "projectid"
+ ]
+}
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 0ca9d79..154bd63 100755
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -25,4 +25,7 @@ It will use the default currency as the base, and will fill existing currencies
autocurrency.page.index
+
+ OCA\AutoCurrency\Cron\FetchCurrenciesJob
+
diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php
index b22865c..24fef0d 100755
--- a/lib/Controller/PageController.php
+++ b/lib/Controller/PageController.php
@@ -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');
+ }
}
diff --git a/lib/Cron/FetchCurrenciesJob.php b/lib/Cron/FetchCurrenciesJob.php
new file mode 100644
index 0000000..e7c1188
--- /dev/null
+++ b/lib/Cron/FetchCurrenciesJob.php
@@ -0,0 +1,27 @@
+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();
+ }
+}
diff --git a/lib/Db/CospendProjectMapper.php b/lib/Db/CospendProjectMapper.php
new file mode 100755
index 0000000..78f3400
--- /dev/null
+++ b/lib/Db/CospendProjectMapper.php
@@ -0,0 +1,47 @@
+
+// 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
+ */
+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);
+ }
+}
diff --git a/lib/Db/Currency.php b/lib/Db/Currency.php
new file mode 100755
index 0000000..4fee3c0
--- /dev/null
+++ b/lib/Db/Currency.php
@@ -0,0 +1,34 @@
+
+// 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,
+ ];
+ }
+}
diff --git a/lib/Db/CurrencyMapper.php b/lib/Db/CurrencyMapper.php
new file mode 100755
index 0000000..3eef885
--- /dev/null
+++ b/lib/Db/CurrencyMapper.php
@@ -0,0 +1,48 @@
+
+// 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
+ */
+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);
+ }
+}
diff --git a/lib/Db/Note.php b/lib/Db/Note.php
deleted file mode 100755
index 958afd9..0000000
--- a/lib/Db/Note.php
+++ /dev/null
@@ -1,33 +0,0 @@
-
-// 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
- ];
- }
-}
diff --git a/lib/Db/NoteMapper.php b/lib/Db/NoteMapper.php
deleted file mode 100755
index 0767649..0000000
--- a/lib/Db/NoteMapper.php
+++ /dev/null
@@ -1,48 +0,0 @@
-
-// 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
- */
-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);
- }
-}
diff --git a/lib/Service/CurrencyNotFound.php b/lib/Service/CurrencyNotFound.php
new file mode 100755
index 0000000..13e2db7
--- /dev/null
+++ b/lib/Service/CurrencyNotFound.php
@@ -0,0 +1,9 @@
+
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace OCA\AutoCurrency\Service;
+
+class CurrencyNotFound extends \Exception {
+}
diff --git a/lib/Service/FetchCurrenciesService.php b/lib/Service/FetchCurrenciesService.php
new file mode 100644
index 0000000..5a1e14b
--- /dev/null
+++ b/lib/Service/FetchCurrenciesService.php
@@ -0,0 +1,103 @@
+
+// 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
+ */
+ 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;
+ }
+ }
+}
diff --git a/lib/Service/NoteNotFound.php b/lib/Service/ProjectNotFound.php
similarity index 80%
rename from lib/Service/NoteNotFound.php
rename to lib/Service/ProjectNotFound.php
index e952378..fa81f9e 100755
--- a/lib/Service/NoteNotFound.php
+++ b/lib/Service/ProjectNotFound.php
@@ -5,5 +5,5 @@ declare(strict_types=1);
namespace OCA\AutoCurrency\Service;
-class NoteNotFound extends \Exception {
+class ProjectNotFound extends \Exception {
}