Browse Source

Close to the Spectre happy flow

pull/9/head
James Cole 4 years ago
parent
commit
aced75519b
  1. 7
      .github/mergify.yml
  2. 1
      app/Http/Controllers/Import/AuthenticateController.php
  3. 81
      app/Http/Controllers/Import/ConfigurationController.php
  4. 5
      app/Http/Controllers/Import/ConversionController.php
  5. 30
      app/Http/Controllers/Import/MapController.php
  6. 184
      app/Http/Controllers/Import/Spectre/ConnectionController.php
  7. 4
      app/Http/Controllers/Import/UploadController.php
  8. 30
      app/Http/Middleware/ConnectionControllerMiddleware.php
  9. 111
      app/Http/Middleware/IsReadyForStep.php
  10. 57
      app/Services/CSV/Configuration/Configuration.php
  11. 2
      app/Services/Nordigen/Conversion/Routine/GenerateTransactions.php
  12. 1
      app/Services/Nordigen/TokenManager.php
  13. 7
      app/Services/Session/Constants.php
  14. 75
      app/Services/Spectre/Conversion/RoutineManager.php
  15. 94
      app/Services/Spectre/Request/GetAccountsRequest.php
  16. 96
      app/Services/Spectre/Request/ListConnectionsRequest.php
  17. 95
      app/Services/Spectre/Request/PostConnectSessionsRequest.php
  18. 88
      app/Services/Spectre/Request/PostCustomerRequest.php
  19. 131
      app/Services/Spectre/Response/GetAccountsResponse.php
  20. 131
      app/Services/Spectre/Response/ListConnectionsResponse.php
  21. 46
      app/Services/Spectre/Response/PostConnectSessionResponse.php
  22. 44
      app/Services/Spectre/Response/PostCustomerResponse.php
  23. 100
      resources/views/import/004-configure/index.twig
  24. 97
      resources/views/import/011-connection/index.twig
  25. 2
      resources/views/index.twig
  26. 8
      routes/web.php

7
.github/mergify.yml

@ -0,0 +1,7 @@
pull_request_rules:
- name: Security update by dependabot
conditions:
- author~=^dependabot(|-preview)\[bot\]$
actions:
merge:
method: merge

1
app/Http/Controllers/Import/AuthenticateController.php

@ -56,7 +56,6 @@ class AuthenticateController extends Controller
$flow = $request->cookie(Constants::FLOW_COOKIE);
if ('spectre' === $flow) {
die('TODO '. __METHOD__);
$subTitle = 'Spectre';
$validator = new SpectreValidator;
$result = $validator->validate();

81
app/Http/Controllers/Import/ConfigurationController.php

@ -38,6 +38,9 @@ use App\Services\Nordigen\Response\ListAccountsResponse;
use App\Services\Nordigen\Services\AccountInformationCollector;
use App\Services\Nordigen\TokenManager;
use App\Services\Session\Constants;
use App\Services\Spectre\Model\Account as SpectreAccount;
use App\Services\Spectre\Request\GetAccountsRequest as SpectreGetAccountsRequest;
use App\Services\Spectre\Response\GetAccountsResponse as SpectreGetAccountsResponse;
use App\Services\Storage\StorageService;
use App\Support\Token;
use Cache;
@ -123,20 +126,28 @@ class ConfigurationController extends Controller
}
// also get the nordigen / spectre accounts
$nordigenAccounts = [];
$importerAccounts = [];
if ('nordigen' === $flow) {
// list all accounts in Nordigen:
$reference = $configuration->getRequisition(session()->get(Constants::REQUISITION_REFERENCE));
$nordigenAccounts = $this->getNordigenAccounts($reference);
$nordigenAccounts = $this->mergeAccountLists($nordigenAccounts, $accounts);
$importerAccounts = $this->getNordigenAccounts($reference);
$importerAccounts = $this->mergeNordigenAccountLists($importerAccounts, $accounts);
}
if ('spectre' === $flow) {
die('grab config for spectre.');
// get the accounts over at Spectre.
$url = config('spectre.url');
$appId = config('spectre.app_id');
$secret = config('spectre.secret');
$spectreList = new SpectreGetAccountsRequest($url, $appId, $secret);
$spectreList->connection = $configuration->getConnection();
$spectreAccounts = $spectreList->get();
$importerAccounts = $this->mergeSpectreAccountLists($spectreAccounts, $accounts);
}
return view(
'import.004-configure.index',
compact('mainTitle', 'subTitle', 'accounts', 'configuration', 'flow', 'nordigenAccounts')
compact('mainTitle', 'subTitle', 'accounts', 'configuration', 'flow', 'importerAccounts')
);
}
@ -195,7 +206,7 @@ class ConfigurationController extends Controller
// set config as complete.
session()->put(Constants::CONFIG_COMPLETE_INDICATOR, true);
if('nordigen' === $configuration->getFlow()) {
if ('nordigen' === $configuration->getFlow() || 'spectre' === $configuration->getFlow()) {
// at this point, nordigen is ready for data conversion.
session()->put(Constants::READY_FOR_CONVERSION, true);
}
@ -248,6 +259,56 @@ class ConfigurationController extends Controller
return $return;
}
/**
* @param SpectreGetAccountsResponse $spectre
* @param array $firefly
*
* TODO should be a helper
*/
private function mergeSpectreAccountLists(SpectreGetAccountsResponse $spectre, array $firefly): array
{
$return = [];
Log::debug('Now creating Spectre account lists.');
/** @var SpectreAccount $spectreAccount */
foreach ($spectre as $spectreAccount) {
Log::debug(sprintf('Now working on Spectre account "%s": "%s"', $spectreAccount->name, $spectreAccount->id));
$iban = $spectreAccount->iban;
$currency = $spectreAccount->currencyCode;
$entry = [
'import_service' => $spectreAccount,
'firefly' => [],
];
// only iban?
$filteredByIban = $this->filterByIban($firefly, $iban);
if (1 === count($filteredByIban)) {
Log::debug(sprintf('This account (%s) has a single Firefly III counter part (#%d, "%s", same IBAN), so will use that one.', $iban, $filteredByIban[0]->id, $filteredByIban[0]->name));
$entry['firefly'] = $filteredByIban;
$return[] = $entry;
continue;
}
Log::debug(sprintf('Found %d accounts with the same IBAN ("%s")', count($filteredByIban), $iban));
// only currency?
$filteredByCurrency = $this->filterByCurrency($firefly, $currency);
if (count($filteredByCurrency) > 0) {
Log::debug(sprintf('This account (%s) has some Firefly III counter parts with the same currency so will only use those.', $currency));
$entry['firefly'] = $filteredByCurrency;
$return[] = $entry;
continue;
}
Log::debug('No special filtering on the Firefly III account list.');
$entry['firefly'] = $firefly;
$return[] = $entry;
}
return $return;
}
/**
* @param array $nordigen
* @param array $firefly
@ -255,9 +316,9 @@ class ConfigurationController extends Controller
*
* TODO move to some helper.
*/
private function mergeAccountLists(array $nordigen, array $firefly): array
private function mergeNordigenAccountLists(array $nordigen, array $firefly): array
{
Log::debug('Now creating account lists.');
Log::debug('Now creating Nordigen account lists.');
$return = [];
/** @var NordigenAccount $nordigenAccount */
foreach ($nordigen as $nordigenAccount) {
@ -265,8 +326,8 @@ class ConfigurationController extends Controller
$iban = $nordigenAccount->getIban();
$currency = $nordigenAccount->getCurrency();
$entry = [
'nordigen' => $nordigenAccount,
'firefly' => [],
'import_service' => $nordigenAccount,
'firefly' => [],
];
// only iban?

5
app/Http/Controllers/Import/ConversionController.php

@ -31,6 +31,7 @@ use App\Http\Middleware\ConversionControllerMiddleware;
use App\Services\CSV\Configuration\Configuration;
use App\Services\CSV\Conversion\RoutineManager as CSVRoutineManager;
use App\Services\Nordigen\Conversion\RoutineManager as NordigenRoutineManager;
use App\Services\Spectre\Conversion\RoutineManager as SpectreRoutineManager;
use App\Services\Session\Constants;
use App\Services\Shared\Conversion\ConversionStatus;
use App\Services\Shared\Conversion\RoutineManagerInterface;
@ -112,7 +113,7 @@ class ConversionController extends Controller
$routine = new NordigenRoutineManager($identifier);
}
if ('spectre' === $flow) {
throw new ImporterErrorException('Cannot handle. :(');
$routine = new SpectreRoutineManager($identifier);
}
if ($configuration->isMapAllData() && in_array($flow, ['spectre', 'nordigen'], true)) {
$nextUrl = route('006-mapping.index');
@ -168,7 +169,7 @@ class ConversionController extends Controller
$routine = new NordigenRoutineManager($identifier);
}
if ('spectre' === $flow) {
throw new ImporterErrorException('Cannot handle. :(');
throw new ImporterErrorException('Cannot handle. :( 2');
}
$importJobStatus = RoutineStatusManager::startOrFindConversion($identifier);

30
app/Http/Controllers/Import/MapController.php

@ -154,7 +154,7 @@ class MapController extends Controller
$index = 0;
$opposingName = config('csv.import_roles.opposing-name') ?? null;
$opposingName['role'] = 'opposing-name';
$opposingName['values'] = $this->getOpposingAccounts();
$opposingName['values'] = $this->getOpposingNordigenAccounts();
// create the "mapper" class which will get data from Firefly III.
$class = sprintf('App\\Services\\CSV\\Mapper\\%s', $opposingName['mapper']);
@ -170,7 +170,30 @@ class MapController extends Controller
$data[] = $opposingName;
}
if ('spectre' === $configuration->getFlow()) {
die('spectre');
$roles = [];
$data = [];
// index 0, opposing account name:
$index = 0;
$opposingName = config('csv.import_roles.opposing-name') ?? null;
$opposingName['role'] = 'opposing-name';
$opposingName['values'] = $this->getOpposingSpectreAccounts();
// create the "mapper" class which will get data from Firefly III.
$class = sprintf('App\\Services\\CSV\\Mapper\\%s', $opposingName['mapper']);
if (!class_exists($class)) {
throw new InvalidArgumentException(sprintf('Class %s does not exist.', $class));
}
Log::debug(sprintf('Associated class is %s', $class));
/** @var MapperInterface $object */
$object = app($class);
$opposingName['mapping_data'] = $object->getMap();
$opposingName['mapped'] = $existingMapping[$index] ?? [];
$data[] = $opposingName;
// index 1: category (TODO)
}
// if nothing to map, just set mappable to true and go to the next step:
@ -298,9 +321,10 @@ class MapController extends Controller
* @throws ImporterErrorException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*
* TODO move to helper or something
*/
private function getOpposingAccounts(): array
private function getOpposingNordigenAccounts(): array
{
Log::debug(sprintf('Now in %s', __METHOD__));
$downloadIdentifier = session()->get(Constants::CONVERSION_JOB_IDENTIFIER);

184
app/Http/Controllers/Import/Spectre/ConnectionController.php

@ -0,0 +1,184 @@
<?php
declare(strict_types=1);
/*
* ConnectionController.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of the Firefly III Data Importer
* (https://github.com/firefly-iii/data-importer).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Import\Spectre;
use App\Exceptions\ImporterErrorException;
use App\Http\Controllers\Controller;
use App\Http\Middleware\ConnectionControllerMiddleware;
use App\Services\CSV\Configuration\Configuration;
use App\Services\Session\Constants;
use App\Services\Spectre\Model\Customer;
use App\Services\Spectre\Request\ListConnectionsRequest;
use App\Services\Spectre\Request\ListCustomersRequest;
use App\Services\Spectre\Request\PostConnectSessionsRequest;
use App\Services\Spectre\Request\PostCustomerRequest;
use App\Services\Spectre\Response\ErrorResponse;
use App\Services\Spectre\Response\PostConnectSessionResponse;
use App\Services\Spectre\Response\PostCustomerResponse;
use App\Services\Storage\StorageService;
use Illuminate\Http\Request;
use JsonException;
use Log;
/**
* Class ConnectionController
*/
class ConnectionController extends Controller
{
/**
*
*/
public function __construct()
{
parent::__construct();
$this->middleware(ConnectionControllerMiddleware::class);
app('view')->share('pageTitle', 'Connection selection nice ey?');
}
/**
*
* @throws ImporterErrorException
*/
public function index()
{
$mainTitle = 'Spectre';
$subTitle = 'Select your financial organisation';
$url = config('spectre.url');
// TODO or cookie value
$appId = config('spectre.app_id');
$secret = config('spectre.secret');
// check if already has the correct customer:
$hasCustomer = false;
$request = new ListCustomersRequest($url, $appId, $secret);
$list = $request->get();
$identifier = null;
if ($list instanceof ErrorResponse) {
throw new ImporterErrorException(sprintf('%s: %s', $list->class, $list->message));
}
/** @var Customer $item */
foreach ($list as $item) {
if (config('spectre.customer_identifier', 'default_ff3_customer') === $item->identifier) {
$hasCustomer = true;
$identifier = $item->id;
}
}
if (false === $hasCustomer) {
// create new one
$request = new PostCustomerRequest($url, $appId, $secret);
$request->identifier = config('spectre.customer_identifier', 'default_ff3_customer');
/** @var PostCustomerResponse $customer */
$customer = $request->post();
$identifier = $customer->customer->id;
}
// store identifier in config
// skip next time?
$configuration = Configuration::fromArray([]);
if (session()->has(Constants::CONFIGURATION)) {
$configuration = Configuration::fromArray(session()->get(Constants::CONFIGURATION));
}
$configuration->setIdentifier($identifier);
// save config
$json = '[]';
try {
$json = json_encode($configuration->toArray(), JSON_THROW_ON_ERROR, 512);
} catch (JsonException $e) {
Log::error($e->getMessage());
}
StorageService::storeContent($json);
session()->put(Constants::CONFIGURATION, $configuration->toArray());
Log::debug('About to get connections.');
$request = new ListConnectionsRequest($url, $appId, $secret);
$request->customer = (string) $identifier;
$list = $request->get();
if ($list instanceof ErrorResponse) {
throw new ImporterErrorException(sprintf('%s: %s', $list->class, $list->message));
}
return view('import.011-connection.index', compact('mainTitle', 'subTitle', 'list', 'identifier', 'configuration'));
}
/**
* @param Request $request
*/
public function post(Request $request)
{
$connectionId = $request->get('spectre_connection_id');
if ('00' === $connectionId) {
// get identifier
$configuration = Configuration::fromArray([]);
if (session()->has(Constants::CONFIGURATION)) {
$configuration = Configuration::fromArray(session()->get(Constants::CONFIGURATION));
}
// make a new connection.
// TODO grab from cookie
$url = config('spectre.url');
$appId = config('spectre.app_id');
$secret = config('spectre.secret');
$newToken = new PostConnectSessionsRequest($url, $appId, $secret);
$newToken->customer = $configuration->getIdentifier();
$newToken->url = route('import.callback.index');
/** @var PostConnectSessionResponse $result */
$result = $newToken->post();
return redirect($result->connect_url);
}
// store connection in config, go to fancy JS page.
// store identifier in config
// skip next time?
$configuration = Configuration::fromArray([]);
if (session()->has(Constants::CONFIGURATION)) {
$configuration = Configuration::fromArray(session()->get(Constants::CONFIGURATION));
}
$configuration->setConnection($connectionId);
// save config
$json = '[]';
try {
$json = json_encode($configuration->toArray(), JSON_THROW_ON_ERROR, 512);
} catch (JsonException $e) {
Log::error($e->getMessage());
}
StorageService::storeContent($json);
session()->put(Constants::CONFIGURATION, $configuration->toSessionArray());
session()->put(Constants::CONNECTION_SELECTED_INDICATOR, true);
// redirect to job configuration
return redirect(route('004-configure.index'));
}
}

4
app/Http/Controllers/Import/UploadController.php

@ -186,7 +186,9 @@ class UploadController extends Controller
return redirect(route('009-selection.index'));
}
if ('spectre' === $flow) {
die('Spectre redirect flow after config upload');
// redirect to spectre
session()->put(Constants::HAS_UPLOAD, true);
return redirect(route('011-connections.index'));
}
return redirect(route('004-configure.index'));

30
app/Http/Middleware/ConnectionControllerMiddleware.php

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/*
* ConnectionControllerMiddleware.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of the Firefly III Data Importer
* (https://github.com/firefly-iii/data-importer).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Http\Middleware;
class ConnectionControllerMiddleware
{
protected const STEP = 'select-connection';
use IsReadyForStep;
}

111
app/Http/Middleware/IsReadyForStep.php

@ -76,7 +76,7 @@ trait IsReadyForStep
return $this->isReadyForNordigenStep();
}
if ('spectre' === $flow) {
die('TODO ' . __METHOD__);
return $this->isReadyForSpectreStep();
}
return $this->isReadyForBasicStep();
}
@ -96,6 +96,72 @@ trait IsReadyForStep
}
}
/**
* @return bool
* @throws ImporterErrorException
*/
private function isReadyForSpectreStep(): bool
{
Log::debug(sprintf('isReadyForSpectreStep("%s")', self::STEP));
switch (self::STEP) {
default:
throw new ImporterErrorException(sprintf('isReadyForSpectreStep: Cannot handle Spectre step "%s"', self::STEP));
case 'service-validation':
case 'authenticate':
return true;
case 'conversion':
if (session()->has(Constants::READY_FOR_SUBMISSION) && true === session()->get(Constants::READY_FOR_SUBMISSION)) {
Log::debug('Spectre: Return false, ready for submission.');
return false;
}
// if/else is in reverse!
if (session()->has(Constants::READY_FOR_CONVERSION) && true === session()->get(Constants::READY_FOR_CONVERSION)) {
return true;
}
// will probably never return false, but OK.
return false;
case 'upload-files':
if (session()->has(Constants::HAS_UPLOAD) && true === session()->get(Constants::HAS_UPLOAD)) {
return false;
}
return true;
case 'select-connection':
if (session()->has(Constants::HAS_UPLOAD) && true === session()->get(Constants::HAS_UPLOAD)) {
return true;
}
return false;
case 'configuration':
if (session()->has(Constants::CONNECTION_SELECTED_INDICATOR) && true === session()->get(Constants::CONNECTION_SELECTED_INDICATOR)) {
return true;
}
return false;
case 'define-roles':
return false;
case 'map':
// mapping must be complete, or not ready for this step.
if (session()->has(Constants::MAPPING_COMPLETE_INDICATOR) && true === session()->get(Constants::MAPPING_COMPLETE_INDICATOR)) {
Log::debug('Spectre: Return false, not ready for step [1].');
return false;
}
// conversion complete?
if (session()->has(Constants::CONVERSION_COMPLETE_INDICATOR) && true === session()->get(Constants::CONVERSION_COMPLETE_INDICATOR)) {
Log::debug('Spectre: Return true, ready for step [4].');
return true;
}
// must already have the conversion, or not ready for this step:
if (session()->has(Constants::READY_FOR_CONVERSION) && true === session()->get(Constants::READY_FOR_CONVERSION)) {
Log::debug('Spectre: Return false, not yet ready for step [2].');
return false;
}
// otherwise return false.
Log::debug('Spectre: Return true, ready for step [3].');
return true;
}
}
/**
* @return bool
* @throws ImporterErrorException
@ -245,11 +311,51 @@ trait IsReadyForStep
return $this->redirectToCorrectNordigenStep();
}
if ('spectre' === $flow) {
die('TODO ' . __METHOD__);
return $this->redirectToCorrectSpectreStep();
}
return $this->redirectToBasicStep();
}
/**
* @return RedirectResponse
* @throws ImporterErrorException
*/
private function redirectToCorrectSpectreStep(): RedirectResponse
{
Log::debug(sprintf('redirectToCorrectSpectreStep("%s")', self::STEP));
switch (self::STEP) {
default:
throw new ImporterErrorException(sprintf('redirectToCorrectSpectreStep: Cannot handle basic step "%s"', self::STEP));
case 'upload-files':
// assume files are uploaded, go to step 11 (connection selection)
// back to selection
$route = route('011-connections.index');
Log::debug(sprintf('Return redirect to "%s"', $route));
return redirect($route);
case 'define-roles':
// will always push to mapping, and mapping will send them to
// the right step.
$route = route('006-mapping.index');
Log::debug(sprintf('Return redirect to "%s"', $route));
return redirect($route);
case 'map':
// if no conversion yet, go there first
// must already have the conversion, or not ready for this step:
if (session()->has(Constants::READY_FOR_CONVERSION) && true === session()->get(Constants::READY_FOR_CONVERSION)) {
Log::debug('Spectre: Is ready for conversion, so send to conversion.');
$route = route('007-convert.index');
Log::debug(sprintf('Spectre: Return redirect to "%s"', $route));
return redirect($route);
}
Log::debug('Spectre: Is ready for submit.');
// otherwise go to import right away
$route = route('008-submit.index');
Log::debug(sprintf('Spectre: Return redirect to "%s"', $route));
return redirect($route);
}
}
/**
* @return RedirectResponse
* @throws ImporterErrorException
@ -263,6 +369,7 @@ trait IsReadyForStep
}
}
/**
* @return RedirectResponse
* @throws ImporterErrorException

57
app/Services/CSV/Configuration/Configuration.php

@ -57,6 +57,10 @@ class Configuration
// spectre + nordigen configuration
private array $accounts;
// spectre configuration
private string $identifier;
private string $connection;
// date range settings
private string $dateRange;
private int $dateRangeNumber;
@ -115,6 +119,10 @@ class Configuration
// spectre + nordigen configuration
$this->accounts = [];
// spectre
$this->identifier = '0';
$this->connection = '0';
// mapping for spectre + nordigen
$this->mapAllData = false;
@ -169,6 +177,10 @@ class Configuration
// mapping for spectre + nordigen
$object->mapAllData = $array['map_all_data'] ?? false;
// spectre
$object->identifier = $array['identifier'] ?? '0';
$object->connection = $array['connection'] ?? '0';
// nordigen:
$object->nordigenCountry = $array['nordigen_country'] ?? '';
$object->nordigenBank = $array['nordigen_bank'] ?? '';
@ -268,6 +280,10 @@ class Configuration
$object->dateNotBefore = $data['date_not_before'] ?? '';
$object->dateNotAfter = $data['date_not_after'] ?? '';
// spectre
$object->identifier = $data['identifier'] ?? '0';
$object->connection = $data['connection'] ?? '0';
// settings for spectre + nordigen
$object->mapAllData = $data['map_all_data'] ?? false;
$object->accounts = $data['accounts'] ?? [];
@ -382,6 +398,11 @@ class Configuration
$object->mapAllData = $array['map_all_data'] ?? false;
$object->accounts = $array['accounts'] ?? [];
// spectre
$object->identifier = $array['identifier'] ?? '0';
$object->connection = $array['connection'] ?? '0';
// date range settings
$object->dateRange = $array['date_range'] ?? 'all';
$object->dateRangeNumber = $array['date_range_number'] ?? 30;
@ -492,6 +513,15 @@ class Configuration
return $array;
}
/**
* @param string $identifier
*/
public function setIdentifier(string $identifier): void
{
$this->identifier = $identifier;
}
/**
* @return array
*/
@ -516,6 +546,10 @@ class Configuration
'version' => $this->version,
'flow' => $this->flow,
// spectre
'identifier' => $this->identifier,
'connection' => $this->connection,
// mapping for spectre + nordigen
'map_all_data' => $this->mapAllData,
@ -580,6 +614,29 @@ class Configuration
return $this->headers;
}
/**
* @return string
*/
public function getConnection(): string
{
return $this->connection;
}
/**
* @return string
*/
public function getIdentifier(): string
{
return $this->identifier;
}
/**
* @param string $connection
*/
public function setConnection(string $connection): void
{
$this->connection = $connection;
}
/**
* @return bool
*/

2
app/Services/Nordigen/Conversion/Routine/GenerateTransactions.php

@ -202,7 +202,7 @@ class GenerateTransactions
$return = [
'apply_rules' => $this->configuration->isRules(),
'error_if_duplicate_hash' => true,
'error_if_duplicate_hash' => !$this->configuration->isIgnoreDuplicateTransactions(),
'transactions' => [
[
'type' => 'withdrawal', // reverse

1
app/Services/Nordigen/TokenManager.php

@ -78,6 +78,7 @@ class TokenManager
// get complete set!
try {
// TODO use cookie values
$identifier = config('nordigen.id');
$key = config('nordigen.key');
self::getNewTokenSet($identifier, $key);

7
app/Services/Session/Constants.php

@ -59,9 +59,14 @@ class Constants
public const MAPPING_COMPLETE_INDICATOR = 'mapping_config_complete';
public const CONVERSION_COMPLETE_INDICATOR = 'conversion_complete';
public const SUBMISSION_COMPLETE_INDICATOR = 'submission_complete';
public const SELECTED_BANK_COUNTRY = 'selected_bank_country';
public const READY_FOR_SUBMISSION = 'ready_for_submission';
// spectre specific steps:
public const CONNECTION_SELECTED_INDICATOR = 'connection_selected_ind';
// nordigen specific steps
public const SELECTED_BANK_COUNTRY = 'selected_bank_country';
// nordigen specific constants
public const REQUISITION_REFERENCE = 'requisition_reference';

75
app/Services/Spectre/Conversion/RoutineManager.php

@ -0,0 +1,75 @@
<?php
/*
* RoutineManager.php
* Copyright (c) 2021 james@firefly-iii.org
*
* This file is part of the Firefly III Data Importer
* (https://github.com/firefly-iii/data-importer).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace App\Services\Spectre\Conversion;
use App\Services\CSV\Configuration\Configuration;
use App\Services\Shared\Conversion\GeneratesIdentifier;
use App\Services\Shared\Conversion\RoutineManagerInterface;
/**
* Class RoutineManager
*/
class RoutineManager implements RoutineManagerInterface
{
use GeneratesIdentifier;
private Configuration $configuration;
/**
*
*/
public function __construct(?string $identifier)
{
if (null === $identifier) {
$this->generateIdentifier();
}
if (null !== $identifier) {
$this->identifier = $identifier;
}
}
/**
* @inheritDoc
*/
public function setConfiguration(Configuration $configuration): void
{
// save config
$this->configuration = $configuration;
}
/**
* @inheritDoc
*/
public function start(): array
{
// TODO: Implement start() method.
}
/**
* @inheritDoc
*/
public function getIdentifier(): string
{
// TODO: Implement getIdentifier() method.
}
}

94
app/Services/Spectre/Request/GetAccountsRequest.php

@ -0,0 +1,94 @@
<?php
/**
* GetAccountsRequest.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of the Firefly III Spectre importer
* (https://github.com/firefly-iii/spectre-importer).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Services\Spectre\Request;
use App\Exceptions\ImporterErrorException;
use App\Services\Spectre\Response\ErrorResponse;
use App\Services\Spectre\Response\GetAccountsResponse;
use Log;
use App\Services\Shared\Response\Response;
/**
* Class GetAccountsRequest
* TODO is not yet paginated.
*/
class GetAccountsRequest extends Request
{
public string $connection;
/**
* ListConnectionsRequest constructor.
*
* @param string $url
* @param string $appId
* @param string $secret
*/
public function __construct(string $url, string $appId, string $secret)
{
$this->type = 'all';
$this->setBase($url);
$this->setAppId($appId);
$this->setSecret($secret);
$this->setUrl('accounts');
}
/**
* @inheritDoc
*/
public function get(): Response
{
Log::debug('GetAccountsRequest::get()');
$this->setParameters(
[
'connection_id' => $this->connection,
]
);
try {
$response = $this->authenticatedGet();
} catch (ImporterErrorException $e) {
// JSON thing.
return new ErrorResponse($e->json ?? []);
}
return new GetAccountsResponse($response['data']);
}
/**
* @inheritDoc
*/
public function post(): Response
{
// TODO: Implement post() method.
}
/**
* @inheritDoc
*/
public function put(): Response
{
// TODO: Implement put() method.
}
}

96
app/Services/Spectre/Request/ListConnectionsRequest.php

@ -0,0 +1,96 @@
<?php
/**
* ListConnectionsRequest.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of the Firefly III Spectre importer
* (https://github.com/firefly-iii/spectre-importer).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Services\Spectre\Request;
use App\Exceptions\ImporterErrorException;
use App\Services\Spectre\Response\ErrorResponse;
use App\Services\Shared\Response\Response;
use App\Services\Spectre\Response\ListConnectionsResponse;
use JsonException;
use Log;
/**
* Class ListConnectionsRequest
* TODO is not yet paginated.
*/
class ListConnectionsRequest extends Request
{
/** @var string */
public string $customer;
/**
* ListConnectionsRequest constructor.
*
* @param string $url
* @param string $appId
* @param string $secret
*/
public function __construct(string $url, string $appId, string $secret)
{
$this->setBase($url);
$this->setAppId($appId);
$this->setSecret($secret);
$this->setUrl('connections');
}
/**
* @inheritDoc
* @throws JsonException
*/
public function get(): Response
{
Log::debug('ListConnectionsRequest::get()');
$this->setParameters(
[
'customer_id' => $this->customer,
]
);
try {
$response = $this->authenticatedGet();
} catch (ImporterErrorException $e) {
// JSON thing.
return new ErrorResponse($e->json ?? []);
}
return new ListConnectionsResponse($response['data']);
}
/**
* @inheritDoc
*/
public function post(): Response
{
// TODO: Implement post() method.
}
/**
* @inheritDoc
*/
public function put(): Response
{
// TODO: Implement put() method.
}
}

95
app/Services/Spectre/Request/PostConnectSessionsRequest.php

@ -0,0 +1,95 @@
<?php
/**
* PostConnectSessionsRequest.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of the Firefly III Spectre importer
* (https://github.com/firefly-iii/spectre-importer).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Services\Spectre\Request;
use App\Services\Spectre\Response\PostConnectSessionResponse;
use App\Services\Shared\Response\Response;
/**
* Class PostConnectSessionsRequest
*/
class PostConnectSessionsRequest extends Request
{
public string $customer;
public string $url;
/**
* PostConnectSessionsRequest constructor.
*
* @param string $url
* @param string $appId
* @param string $secret
*/
public function __construct(string $url, string $appId, string $secret)
{
$this->setBase($url);
$this->setAppId($appId);
$this->setSecret($secret);
$this->setUrl('connect_sessions/create');
}
/**
* @inheritDoc
*/
public function get(): Response
{
// TODO: Implement get() method.
}
/**
* @inheritDoc
*/
public function post(): Response
{
$body = [
'data' => [
'customer_id' => $this->customer,
'consent' => [
'scopes' => ['account_details', 'transactions_details'],
'daily_refresh' => true,
'include_fake_providers' => true,
'show_consent_confirmation' => true,
'credentials_strategy' => 'ask',
],
'attempt' => [
'return_to' => $this->url,
],
],
];
$response = $this->sendUnsignedSpectrePost($body);
return new PostConnectSessionResponse($response['data']);
}
/**
* @inheritDoc
*/
public function put(): Response
{
// TODO: Implement put() method.
}
}

88
app/Services/Spectre/Request/PostCustomerRequest.php

@ -0,0 +1,88 @@
<?php
/**
* PostCustomerRequest.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of the Firefly III Spectre importer
* (https://github.com/firefly-iii/spectre-importer).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Services\Spectre\Request;
use App\Exceptions\ImporterErrorException;
use App\Services\Shared\Response\Response;
use App\Services\Spectre\Response\PostCustomerResponse;
/**
* Class PostCustomerRequest
*/
class PostCustomerRequest extends Request
{
/** @var string */
public string $identifier;
/**
* PostCustomerRequest constructor.
*
* @param string $url
* @param string $appId
* @param string $secret
*/
public function __construct(string $url, string $appId, string $secret)
{
$this->setBase($url);
$this->setAppId($appId);
$this->setSecret($secret);
$this->setUrl('customers');
}
/**
* @inheritDoc
*/
public function get(): Response
{
// TODO: Implement get() method.
}
/**
* @inheritDoc
*/
public function post(): Response
{
if (null === $this->identifier) {
throw new ImporterErrorException('No identifier for PostCustomerRequest');
}
$data = [
'data' => [
'identifier' => $this->identifier,
],
];
$response = $this->sendSignedSpectrePost($data);
return new PostCustomerResponse($response['data']);
}
/**
* @inheritDoc
*/
public function put(): Response
{
// TODO: Implement put() method.
}
}

131
app/Services/Spectre/Response/GetAccountsResponse.php

@ -0,0 +1,131 @@
<?php
/**
* GetAccountsResponse.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of the Firefly III Spectre importer
* (https://github.com/firefly-iii/spectre-importer).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Services\Spectre\Response;
use App\Services\Shared\Response\Response;
use App\Services\Spectre\Model\Account;
use Countable;
use Illuminate\Support\Collection;
use Iterator;
/**
* Class GetAccountsResponse
*/
class GetAccountsResponse extends Response implements Iterator, Countable
{
/** @var Collection */
private Collection $collection;
/** @var int */
private int $position = 0;
/**
* @inheritDoc
*/
public function __construct(array $data)
{
$this->collection = new Collection;
foreach ($data as $entry) {
$model = Account::fromArray($entry);
$this->collection->push($model);
}
}
/**
* Count elements of an object.
*
* @link https://php.net/manual/en/countable.count.php
* @return int The custom count as an integer.
* </p>
* <p>
* The return value is cast to an integer.
* @since 5.1.0
*/
public function count(): int
{
return $this->collection->count();
}
/**
* Return the current element.
*
* @link https://php.net/manual/en/iterator.current.php
* @return Account
* @since 5.0.0
*/
public function current(): Account
{
return $this->collection->get($this->position);
}
/**
* Return the key of the current element.
*
* @link https://php.net/manual/en/iterator.key.php
* @return int
* @since 5.0.0
*/
public function key(): int
{
return $this->position;
}
/**
* Move forward to next element.
*
* @link https://php.net/manual/en/iterator.next.php
* @return void Any returned value is ignored.
* @since 5.0.0
*/
public function next(): void
{
$this->position++;
}
/**
* Rewind the Iterator to the first element.
*
* @link https://php.net/manual/en/iterator.rewind.php
* @return void Any returned value is ignored.
* @since 5.0.0
*/
public function rewind(): void
{
$this->position = 0;
}
/**
* Checks if current position is valid.
*
* @link https://php.net/manual/en/iterator.valid.php
* @return bool The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure.
* @since 5.0.0
*/
public function valid(): bool
{
return $this->collection->has($this->position);
}
}

131
app/Services/Spectre/Response/ListConnectionsResponse.php

@ -0,0 +1,131 @@
<?php
/**
* ListConnectionsResponse.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of the Firefly III Spectre importer
* (https://github.com/firefly-iii/spectre-importer).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Services\Spectre\Response;
use App\Services\Shared\Response\Response;
use App\Services\Spectre\Model\Connection;
use Countable;
use Illuminate\Support\Collection;
use Iterator;
/**
* Class ListConnectionsResponse
*/
class ListConnectionsResponse extends Response implements Iterator, Countable
{
private Collection $collection;
private int $position = 0;
/**
* @inheritDoc
*/
public function __construct(array $data)
{
$this->collection = new Collection;
/** @var array $row */
foreach ($data as $row) {
$model = Connection::fromArray($row);
$this->collection->push($model);
}
}
/**
* Count elements of an object.
*
* @link https://php.net/manual/en/countable.count.php
* @return int The custom count as an integer.
* </p>
* <p>
* The return value is cast to an integer.
* @since 5.1.0
*/
public function count(): int
{
return $this->collection->count();
}
/**
* Return the current element.
*
* @link https://php.net/manual/en/iterator.current.php
* @return Connection
* @since 5.0.0
*/
public function current(): Connection
{
return $this->collection->get($this->position);
}
/**
* Return the key of the current element.
*
* @link https://php.net/manual/en/iterator.key.php
* @return int
* @since 5.0.0
*/
public function key(): int
{
return $this->position;
}
/**
* Move forward to next element.
*
* @link https://php.net/manual/en/iterator.next.php
* @return void Any returned value is ignored.
* @since 5.0.0
*/
public function next(): void
{
$this->position++;
}
/**
* Rewind the Iterator to the first element.
*
* @link https://php.net/manual/en/iterator.rewind.php
* @return void Any returned value is ignored.
* @since 5.0.0
*/
public function rewind(): void
{
$this->position = 0;
}
/**
* Checks if current position is valid.
*
* @link https://php.net/manual/en/iterator.valid.php
* @return bool The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure.
* @since 5.0.0
*/
public function valid(): bool
{
return $this->collection->has($this->position);
}
}

46
app/Services/Spectre/Response/PostConnectSessionResponse.php

@ -0,0 +1,46 @@
<?php
/**
* PostConnectSessionResponse.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of the Firefly III Spectre importer
* (https://github.com/firefly-iii/spectre-importer).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Services\Spectre\Response;
use App\Services\Shared\Response\Response;
/**
* Class PostConnectSessionResponse
*/
class PostConnectSessionResponse extends Response
{
/** @var string */
public string $connect_url;
/**
* @inheritDoc
*/
public function __construct(array $data)
{
$this->connect_url = (string)$data['connect_url'];
}
}

44
app/Services/Spectre/Response/PostCustomerResponse.php

@ -0,0 +1,44 @@
<?php
/**
* PostCustomerResponse.php
* Copyright (c) 2020 james@firefly-iii.org
*
* This file is part of the Firefly III Spectre importer
* (https://github.com/firefly-iii/spectre-importer).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace App\Services\Spectre\Response;
use App\Services\Shared\Response\Response;
use App\Services\Spectre\Model\Customer;
/**
* Class PostCustomerResponse
*/
class PostCustomerResponse extends Response
{
public Customer $customer;
/**
* @inheritDoc
*/
public function __construct(array $data)
{
$this->customer = Customer::fromArray($data);
}
}

100
resources/views/import/004-configure/index.twig

@ -74,44 +74,66 @@
Form
</div>
<div class="card-body">
{% if 'nordigen' == flow %}
<h4>Nordigen import options</h4>
{% if 'nordigen' == flow or 'spectre' == flow %}
<h4>{{ flow|capitalize }} import options</h4>
<div class="form-group row">
<div class="col-sm-3">Accounts</div>
<div class="col-sm-9">
<table class="table table-sm table-bordered table-striped">
<thead>
<tr>
<th>Nordigen</th>
<th>{{ flow|capitalize }}</th>
<th>&nbsp;</th>
<th>Firefly III</th>
</tr>
</thead>
<tbody>
{% for information in nordigenAccounts %}
{% for information in importerAccounts %}
<tr>
<td>
<input
id="do_import_{{ information.nordigen.identifier }}"
type="checkbox"
{% if 'disabled' == information.nordigen.status %}
disabled="disabled"
{% endif %}
name="do_import[{{ information.nordigen.identifier }}]"
value="1"
aria-describedby="accountsHelp"
{% if(information.firefly|length==0) %}disabled="disabled"{% endif %}
/> <label class="form-check-label"
{% if '' != information.nordigen.iban %}title="IBAN: {{ information.nordigen.iban }}"{% endif %}
for="do_import_{{ information.nordigen.identifier }}">
Account "{{ information.nordigen.getFullName }}"
{% if 'nordigen' == flow %}
<input
id="do_import_{{ information.import_service.identifier }}"
type="checkbox"
{% if 'disabled' == information.import_service.status %}
disabled="disabled"
{% endif %}
name="do_import[{{ information.import_service.identifier }}]"
value="1"
aria-describedby="accountsHelp"
{% if(information.firefly|length==0) %}disabled="disabled"{% endif %}
/> <label class="form-check-label"
{% if '' != information.import_service.iban %}title="IBAN: {{ information.import_service.iban }}"{% endif %}
for="do_import_{{ information.import_service.identifier }}">
Account "{{ information.import_service.getFullName }}"
</label>
{% if 'disabled' != information.import_service.status and '' != information.import_service.iban %}
<br><small>
IBAN: {{ information.import_service.iban }}</small>{% endif %}
{% if 'disabled' == information.import_service.status %}<br>
<small class="text-danger">(this account is
disabled)</small>{% endif %}
{% endif %}
{% if 'spectre' == flow %}
<input
id="do_import_{{ information.import_service.id }}"
type="checkbox"
name="do_import[{{ information.import_service.id }}]"
value="1"
aria-describedby="accountsHelp"
{% if(information.firefly|length==0) %}disabled="disabled"{% endif %}
/> <label class="form-check-label"
{% if '' != information.import_service.iban %}title="IBAN: {{ information.import_service.iban }}"{% endif %}
for="do_import_{{ information.import_service.id }}">
Account "{{ information.import_service.name }}"
</label>
{% if 'disabled' != information.nordigen.status and '' != information.nordigen.iban %}<br><small>IBAN: {{ information.nordigen.iban }}</small>{% endif %}
{% if 'disabled' == information.nordigen.status %}<br><small class="text-danger">(this account is disabled)</small>{% endif %}
{% if '' != information.import_service.iban %}
<br><small>
IBAN: {{ information.import_service.iban }}</small>{% endif %}
{% endif %}
</td>
<td>
{% if 'disabled' != information.nordigen.status %}
{% if 'disabled' != information.import_service.status %}
{% if(information.firefly|length!=0) %}&rarr;{% endif %}
{% endif %}
</td>
@ -121,18 +143,18 @@
<span class="text-danger">Cannot be imported</span>
{% endif %}
{% if(information.firefly|length>0) %}
{% if 'disabled' != information.nordigen.status %}
<select style="width:100%;"
class="custom-select custom-select-sm form-control"
name="accounts[{{ information.nordigen.identifier }}]">
{% for ff3Account in information.firefly %}
{# {% if configuration.getAccounts[spectreAccount.id] == ff3Account.id %}selected{% endif %} #}
<option value="{{ ff3Account.id }}"
label="{{ ff3Account.name }}{% if ff3Account.iban %} ({{ ff3Account.iban }}){% endif %}">
{{ ff3Account.name }}{% if ff3Account.iban %} ({{ ff3Account.iban }}){% endif %}
</option>
{% endfor %}
</select>
{% if 'disabled' != information.import_service.status %}
<select style="width:100%;"
class="custom-select custom-select-sm form-control"
name="accounts[{{ information.import_service.identifier }}]">
{% for ff3Account in information.firefly %}
{# {% if configuration.getAccounts[spectreAccount.id] == ff3Account.id %}selected{% endif %} #}
<option value="{{ ff3Account.id }}"
label="{{ ff3Account.name }}{% if ff3Account.iban %} ({{ ff3Account.iban }}){% endif %}">
{{ ff3Account.name }}{% if ff3Account.iban %} ({{ ff3Account.iban }}){% endif %}
</option>
{% endfor %}
</select>
{% endif %}
{% endif %}
</td>
@ -307,7 +329,8 @@
Yes
</label>
<small id="mapAllDataHelp" class="form-text text-muted">
<br>You get the opportunity to link your TODO data to existing Firefly III
<br>You get the opportunity to link your TODO data to existing Firefly
III
data, for a cleaner import.
</small>
</div>
@ -322,7 +345,8 @@
type="radio" name="date_range" value="all"
{% if configuration.getDateRange == 'all' %}checked{% endif %}
aria-describedby="rangeHelp"/>
<label class="form-check-label" for="date_range_all">Import everything</label>
<label class="form-check-label" for="date_range_all">Import
everything</label>
</div>
<div class="form-check">
@ -601,7 +625,9 @@
$('#date_range_partial_settings').hide();
$('#date_range_range_settings').hide();
}
}``
}
``
function updateDateExample() {
$('#date_example').text('...');

97
resources/views/import/011-connection/index.twig

@ -0,0 +1,97 @@
{% extends "./layout/default" %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-lg-12">
&nbsp;
</div>
</div>
<div class="row">
<div class="col-lg-12">
<h1>{{ mainTitle }}</h1>
<h2>{{ subTitle }}</h2>
</div>
</div>
<form method="post" action="{{ route('011-connections.post') }}" accept-charset="UTF-8" id="store">
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
<div class="row">
<div class="col-lg-12">
<p class="lead">Select the connection to use or make a new connection</p>
<p>
Spectre creates connections; a representation of the connection to your financial institution.
Select below which one the importer must use, or opt to create a new connection if no connections are visible.
Please read
<a href="https://docs.firefly-iii.org/other-data-importers/usage/filter_import/"
target="_blank">the documentation for this page</a> if you want to know more.
</p>
</div>
</div>
<!-- list all connections -->
<div class="row">
<div class="col-lg-12">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>&nbsp;</th>
<th>Bank</th>
<th>Last used</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for item in list %}
<tr>
<td>
<input
{% if item.status == 'disabled' %}disabled{% endif %}
{% if configuration.getConnection == item.id %}checked="checked"{% endif %}
id="{{ item.id }}" type="radio" {% if list|length == 1%}checked{% endif%} name="spectre_connection_id" value="{{ item.id }}">
</td>
<td>
<label for="{{ item.id }}">
{{ item.providerName }} ({{ item.countryCode }})
</label>
</td>
<td>
{{ item.lastSuccess.format("Y-m-d H:i:s") }}<br>
{{ item.updatedAt.format("Y-m-d H:i:s") }}<br>
</td>
<td>
{{ item.status }}
</td>
</tr>
{% endfor %}
<tr>
<td>
<input id="new_login" type="radio" name="spectre_connection_id" value="00" {% if configuration.getConnection == 0 %}checked="checked"{% endif %}>
</td>
<td colspan="3">
<label for="new_login"><em>
Create a new connection
</em>
</label>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<!-- go back to upload -->
<a href="{{ route('back.upload') }}" class="btn btn-secondary">&larr; Go back to upload</a>
<br>
<small class="text-muted">Changes on this page will not be saved.</small>
</div>
<div class="col-lg-6">
<button type="submit" class="float-right btn btn-primary">Submit &rarr;</button>
</div>
</div>
</form>
</div>
{% endblock %}
{% block scripts %}
{% endblock %}

2
resources/views/index.twig

@ -93,7 +93,7 @@
<div class="col-lg-4">
<div class="card">
<div class="card-header">
Import from Nordigen (img)
Import from Nordigen
</div>
<div class="card-body">
<p id="nordigen_result" style="display: none;"></p>

8
routes/web.php

@ -83,8 +83,12 @@ Route::get('/import/selection', ['uses' => 'Import\Nordigen\SelectionController@
Route::post('/import/selection', ['uses' => 'Import\Nordigen\SelectionController@postIndex', 'as' => '009-selection.post']);
// step 10: Get redirected to + callback from Nordigen for permission:
Route::get('/import/link/build', ['uses' => 'Import\Nordigen\LinkController@build', 'as' => '010-build-link.index']);
Route::get('/import/link/callback', ['uses' => 'Import\Nordigen\LinkController@callback', 'as' => '010-build-link.callback']);
Route::get('/import/link-nordigen/build', ['uses' => 'Import\Nordigen\LinkController@build', 'as' => '010-build-link.index']);
Route::get('/import/link-nordigen/callback', ['uses' => 'Import\Nordigen\LinkController@callback', 'as' => '010-build-link.callback']);
// step 11: list tokens (can be skipped)
Route::get('/import/spectre-connections', ['uses' => 'Import\Spectre\ConnectionController@index', 'as' => '011-connections.index']);
Route::post('/import/spectre-connections/submit', ['uses' => 'Import\Spectre\ConnectionController@post', 'as' => '011-connections.post']);
// routes to go back to other steps (also takes care of session vars)
Route::get('/back/start', 'NavController@toStart')->name('back.start');

Loading…
Cancel
Save