You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

240 lines
9.8 KiB

4 years ago
2 years ago
2 years ago
  1. <?php
  2. /*
  3. * ConversionController.php
  4. * Copyright (c) 2021 james@firefly-iii.org
  5. *
  6. * This file is part of the Firefly III Data Importer
  7. * (https://github.com/firefly-iii/data-importer).
  8. *
  9. * This program is free software: you can redistribute it and/or modify
  10. * it under the terms of the GNU Affero General Public License as
  11. * published by the Free Software Foundation, either version 3 of the
  12. * License, or (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU Affero General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU Affero General Public License
  20. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  21. */
  22. declare(strict_types=1);
  23. namespace App\Http\Controllers\Import;
  24. use App\Exceptions\ImporterErrorException;
  25. use App\Http\Controllers\Controller;
  26. use App\Http\Middleware\ConversionControllerMiddleware;
  27. use App\Services\Camt\Conversion\RoutineManager as CamtRoutineManager;
  28. use App\Services\CSV\Conversion\RoutineManager as CSVRoutineManager;
  29. use App\Services\Nordigen\Conversion\RoutineManager as NordigenRoutineManager;
  30. use App\Services\Session\Constants;
  31. use App\Services\Shared\Conversion\ConversionStatus;
  32. use App\Services\Shared\Conversion\RoutineManagerInterface;
  33. use App\Services\Shared\Conversion\RoutineStatusManager;
  34. use App\Services\Spectre\Conversion\RoutineManager as SpectreRoutineManager;
  35. use App\Support\Http\RestoresConfiguration;
  36. use Illuminate\Http\JsonResponse;
  37. use Illuminate\Http\Request;
  38. /**
  39. * Class ConversionController
  40. */
  41. class ConversionController extends Controller
  42. {
  43. use RestoresConfiguration;
  44. protected const DISK_NAME = 'jobs'; // TODO stored in several places
  45. /**
  46. * StartController constructor.
  47. */
  48. public function __construct()
  49. {
  50. parent::__construct();
  51. app('view')->share('pageTitle', 'Importing data...');
  52. $this->middleware(ConversionControllerMiddleware::class);
  53. }
  54. /**
  55. * @throws ImporterErrorException
  56. */
  57. public function index()
  58. {
  59. // app('log')->debug(sprintf('Now in %s', __METHOD__));
  60. $mainTitle = 'Convert the data';
  61. // create configuration:
  62. $configuration = $this->restoreConfiguration();
  63. app('log')->debug('Will now verify configuration content.');
  64. $jobBackUrl = route('back.mapping');
  65. if (0 === count($configuration->getDoMapping()) && 'file' === $configuration->getFlow()) {
  66. // no mapping, back to roles
  67. app('log')->debug('Pressing "back" will send you to roles.');
  68. $jobBackUrl = route('back.roles');
  69. }
  70. if (0 === count($configuration->getMapping())) {
  71. // back to mapping
  72. app('log')->debug('Pressing "back" will send you to mapping.');
  73. $jobBackUrl = route('back.mapping');
  74. }
  75. // TODO option is not used atm.
  76. // if (true === $configuration->isMapAllData()) {
  77. // app('log')->debug('Pressing "back" will send you to mapping.');
  78. // $jobBackUrl = route('back.mapping');
  79. // }
  80. // job ID may be in session:
  81. $identifier = session()->get(Constants::CONVERSION_JOB_IDENTIFIER);
  82. $routine = null;
  83. $flow = $configuration->getFlow();
  84. app('log')->debug('Will redirect to submission after conversion.');
  85. $nextUrl = route('008-submit.index');
  86. // switch based on flow:
  87. if (!in_array($flow, config('importer.flows'), true)) {
  88. throw new ImporterErrorException(sprintf('Not a supported flow: "%s"', $flow));
  89. }
  90. // @var RoutineManagerInterface $routine
  91. if ('file' === $flow) {
  92. $contentType = $configuration->getContentType();
  93. if ('unknown' === $contentType || 'csv' === $contentType) {
  94. app('log')->debug('Create CSV routine manager.');
  95. $routine = new CSVRoutineManager($identifier);
  96. }
  97. if ('camt' === $contentType) {
  98. app('log')->debug('Create CAMT routine manager.');
  99. $routine = new CamtRoutineManager($identifier);
  100. }
  101. }
  102. if ('nordigen' === $flow) {
  103. app('log')->debug('Create Nordigen routine manager.');
  104. $routine = new NordigenRoutineManager($identifier);
  105. }
  106. if ('spectre' === $flow) {
  107. app('log')->debug('Create Spectre routine manager.');
  108. $routine = new SpectreRoutineManager($identifier);
  109. }
  110. if ($configuration->isMapAllData() && in_array($flow, ['spectre', 'nordigen'], true)) {
  111. app('log')->debug('Will redirect to mapping after conversion.');
  112. $nextUrl = route('006-mapping.index');
  113. }
  114. if (null === $routine) {
  115. throw new ImporterErrorException(sprintf('Could not create routine manager for flow "%s"', $flow));
  116. }
  117. // may be a new identifier! Yay!
  118. $identifier = $routine->getIdentifier();
  119. app('log')->debug(sprintf('Conversion routine manager identifier is "%s"', $identifier));
  120. // store identifier in session so the status can get it.
  121. session()->put(Constants::CONVERSION_JOB_IDENTIFIER, $identifier);
  122. app('log')->debug(sprintf('Stored "%s" under "%s"', $identifier, Constants::CONVERSION_JOB_IDENTIFIER));
  123. return view('import.007-convert.index', compact('mainTitle', 'identifier', 'jobBackUrl', 'flow', 'nextUrl'));
  124. }
  125. public function start(Request $request): JsonResponse
  126. {
  127. app('log')->debug(sprintf('Now at %s', __METHOD__));
  128. $identifier = $request->get('identifier');
  129. $configuration = $this->restoreConfiguration();
  130. $routine = null;
  131. // now create the right class:
  132. $flow = $configuration->getFlow();
  133. if (!in_array($flow, config('importer.flows'), true)) {
  134. throw new ImporterErrorException(sprintf('Not a supported flow: "%s"', $flow));
  135. }
  136. // @var RoutineManagerInterface $routine
  137. if ('file' === $flow) {
  138. $contentType = $configuration->getContentType();
  139. if ('unknown' === $contentType || 'csv' === $contentType) {
  140. $routine = new CSVRoutineManager($identifier);
  141. }
  142. if ('camt' === $contentType) {
  143. $routine = new CamtRoutineManager($identifier); // why do we need this one?
  144. }
  145. }
  146. if ('nordigen' === $flow) {
  147. $routine = new NordigenRoutineManager($identifier);
  148. }
  149. if ('spectre' === $flow) {
  150. $routine = new SpectreRoutineManager($identifier);
  151. }
  152. if (null === $routine) {
  153. throw new ImporterErrorException(sprintf('Could not create routine manager for flow "%s"', $flow));
  154. }
  155. $importJobStatus = RoutineStatusManager::startOrFindConversion($identifier);
  156. RoutineStatusManager::setConversionStatus(ConversionStatus::CONVERSION_RUNNING);
  157. // then push stuff into the routine:
  158. $routine->setConfiguration($configuration);
  159. try {
  160. $transactions = $routine->start();
  161. } catch (ImporterErrorException $e) {
  162. app('log')->error($e->getMessage());
  163. app('log')->error($e->getTraceAsString());
  164. RoutineStatusManager::setConversionStatus(ConversionStatus::CONVERSION_ERRORED);
  165. return response()->json($importJobStatus->toArray());
  166. }
  167. app('log')->debug(sprintf('Conversion routine "%s" was started successfully.', $flow));
  168. if (0 === count($transactions)) {
  169. app('log')->error('[b] Zero transactions!');
  170. RoutineStatusManager::setConversionStatus(ConversionStatus::CONVERSION_ERRORED);
  171. return response()->json($importJobStatus->toArray());
  172. }
  173. app('log')->debug(sprintf('Conversion routine "%s" yielded %d transaction(s).', $flow, count($transactions)));
  174. // save transactions in 'jobs' directory under the same key as the conversion thing.
  175. $disk = \Storage::disk(self::DISK_NAME);
  176. try {
  177. $disk->put(sprintf('%s.json', $identifier), json_encode($transactions, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
  178. } catch (\JsonException $e) {
  179. app('log')->error(sprintf('JSON exception: %s', $e->getMessage()));
  180. app('log')->error($e->getTraceAsString());
  181. RoutineStatusManager::setConversionStatus(ConversionStatus::CONVERSION_ERRORED);
  182. return response()->json($importJobStatus->toArray());
  183. }
  184. app('log')->debug(sprintf('Transactions are stored on disk "%s" in file "%s.json"', self::DISK_NAME, $identifier));
  185. // set done:
  186. RoutineStatusManager::setConversionStatus(ConversionStatus::CONVERSION_DONE);
  187. // set config as complete.
  188. session()->put(Constants::CONVERSION_COMPLETE_INDICATOR, true);
  189. app('log')->debug('Set conversion as complete.');
  190. return response()->json($importJobStatus->toArray());
  191. }
  192. public function status(Request $request): JsonResponse
  193. {
  194. // app('log')->debug(sprintf('Now at %s', __METHOD__));
  195. $identifier = $request->get('identifier');
  196. app('log')->debug(sprintf('Now at %s(%s)', __METHOD__, $identifier));
  197. if (null === $identifier) {
  198. app('log')->warning('Identifier is NULL.');
  199. // no status is known yet because no identifier is in the session.
  200. // As a fallback, return empty status
  201. $fakeStatus = new ConversionStatus();
  202. return response()->json($fakeStatus->toArray());
  203. }
  204. $importJobStatus = RoutineStatusManager::startOrFindConversion($identifier);
  205. return response()->json($importJobStatus->toArray());
  206. }
  207. }