From 97a687e40a22aa0bc6015b83f13a7ecc170e8b06 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 21 Mar 2021 11:06:08 +0100 Subject: [PATCH] Final things. --- .../V1/Controllers/System/CronController.php | 59 +++++++++++++ app/Api/V1/Requests/System/CronRequest.php | 83 ++++++++++++++++++ app/Factory/TransactionGroupFactory.php | 4 + .../Controllers/System/CronController.php | 15 +--- .../Controllers/System/InstallController.php | 4 +- app/Http/Kernel.php | 3 + app/Jobs/CreateRecurringTransactions.php | 4 +- app/Providers/RouteServiceProvider.php | 15 +++- app/Support/Cronjobs/AbstractCronjob.php | 23 +++-- app/Support/Cronjobs/AutoBudgetCronjob.php | 16 ++-- app/Support/Cronjobs/RecurringCronjob.php | 18 ++-- app/Support/Cronjobs/TelemetryCronjob.php | 19 ++-- app/Support/Http/Controllers/CreateStuff.php | 4 +- app/Support/Http/Controllers/CronRunner.php | 86 +++++++++++++------ changelog.md | 4 + routes/api-noauth.php | 13 +++ routes/api.php | 2 + 17 files changed, 305 insertions(+), 67 deletions(-) create mode 100644 app/Api/V1/Controllers/System/CronController.php create mode 100644 app/Api/V1/Requests/System/CronRequest.php create mode 100644 routes/api-noauth.php diff --git a/app/Api/V1/Controllers/System/CronController.php b/app/Api/V1/Controllers/System/CronController.php new file mode 100644 index 0000000000..b4f6895da0 --- /dev/null +++ b/app/Api/V1/Controllers/System/CronController.php @@ -0,0 +1,59 @@ +. + */ + +namespace FireflyIII\Api\V1\Controllers\System; + + +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\System\CronRequest; +use FireflyIII\Support\Http\Controllers\CronRunner; +use Illuminate\Http\JsonResponse; +use Log; + +/** + * Class CronController + */ +class CronController extends Controller +{ + use CronRunner; + + /** + * @param CronRequest $request + * @param string $token + * + * @return JsonResponse + */ + public function cron(CronRequest $request, string $token): JsonResponse + { + $config = $request->getAll(); + + Log::debug(sprintf('Now in %s', __METHOD__)); + + $return = []; + $return['recurring_transactions'] = $this->runRecurring($config['force'], $config['date']); + $return['auto_budgets'] = $this->runAutoBudget($config['force'], $config['date']); + $return['telemetry'] = $this->runTelemetry($config['force'], $config['date']); + + + return response()->json($return); + } + +} \ No newline at end of file diff --git a/app/Api/V1/Requests/System/CronRequest.php b/app/Api/V1/Requests/System/CronRequest.php new file mode 100644 index 0000000000..c60af678c8 --- /dev/null +++ b/app/Api/V1/Requests/System/CronRequest.php @@ -0,0 +1,83 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests\System; + +use Carbon\Carbon; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; + +/** + * Class CronRequest + * + * @codeCoverageIgnore + */ +class CronRequest extends FormRequest +{ + use ConvertsDataTypes; + + /** + * Verify the request. + * + * @return bool + */ + public function authorize(): bool + { + return true; + } + + /** + * Get all data from the request. + * + * @return array + */ + public function getAll(): array + { + $data = [ + 'force' => false, + 'date' => Carbon::now(), + ]; + if ($this->has('force')) { + $data['force'] = $this->boolean('force'); + } + if ($this->has('date')) { + $data['date'] = $this->date('date'); + } + + return $data; + } + + /** + * The rules that the incoming request must be matched against. + * + * @return array + */ + public function rules(): array + { + return [ + 'force' => 'in:true,false', + 'date' => 'date', + ]; + } +} diff --git a/app/Factory/TransactionGroupFactory.php b/app/Factory/TransactionGroupFactory.php index ceba156fe6..e7f6d99e1a 100644 --- a/app/Factory/TransactionGroupFactory.php +++ b/app/Factory/TransactionGroupFactory.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Factory; use FireflyIII\Exceptions\DuplicateTransactionException; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\TransactionGroup; use FireflyIII\User; use Log; @@ -74,6 +75,9 @@ class TransactionGroupFactory if (null !== $title) { $title = substr($title, 0, 1000); } + if(0 === $collection->count()) { + throw new FireflyException('Created zero transaction journals.'); + } $group = new TransactionGroup; $group->user()->associate($this->user); diff --git a/app/Http/Controllers/System/CronController.php b/app/Http/Controllers/System/CronController.php index 9c8a632f06..38ee1b2be0 100644 --- a/app/Http/Controllers/System/CronController.php +++ b/app/Http/Controllers/System/CronController.php @@ -22,26 +22,19 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\System; - -use FireflyIII\Support\Http\Controllers\CronRunner; +use Log; /** * Class CronController */ class CronController { - use CronRunner; - /** * @return string */ - public function cron(): string + public function cron() { - $results = []; - $results[] = $this->runRecurring(); - $results[] = $this->runAutoBudget(); - $results[] = $this->runTelemetry(); - - return implode("
\n", $results); + Log::error('The cron endpoint has moved to GET /api/v1/cron/[token]'); + return response('The cron endpoint has moved to GET /api/v1/cron/[token]', 500); } } diff --git a/app/Http/Controllers/System/InstallController.php b/app/Http/Controllers/System/InstallController.php index 8e1de080a5..af665d0a21 100644 --- a/app/Http/Controllers/System/InstallController.php +++ b/app/Http/Controllers/System/InstallController.php @@ -133,16 +133,16 @@ class InstallController extends Controller */ public function keys(): void { + // switch on PHP version. // switch on PHP version. if (7 === PHP_MAJOR_VERSION) { $rsa = new \phpseclib\Crypt\RSA; $keys = $rsa->createKey(4096); } if (8 === PHP_MAJOR_VERSION) { - $keys = \phpseclib3\Crypt\RSA::createKeys(4096); + $keys = \phpseclib3\Crypt\RSA::createKey(4096); } - [$publicKey, $privateKey] = [ Passport::keyPath('oauth-public.key'), Passport::keyPath('oauth-private.key'), diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 0dcdb13b1c..6bc7fbb51e 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -182,6 +182,9 @@ class Kernel extends HttpKernel //'throttle:60,1', 'bindings', ], + 'apiY' => [ + 'bindings', + ], ]; /** * The priority-sorted list of middleware. diff --git a/app/Jobs/CreateRecurringTransactions.php b/app/Jobs/CreateRecurringTransactions.php index e217eefe7f..c1360d1633 100644 --- a/app/Jobs/CreateRecurringTransactions.php +++ b/app/Jobs/CreateRecurringTransactions.php @@ -110,7 +110,7 @@ class CreateRecurringTransactions implements ShouldQueue // clear cache for user app('preferences')->setForUser($recurrence->user, 'lastActivity', microtime()); - Log::debug(sprintf('Now at recurrence #%d', $recurrence->id)); + Log::debug(sprintf('Now at recurrence #%d of user #%d', $recurrence->id, $recurrence->user_id)); $createdReps = $this->handleRepetitions($recurrence); Log::debug(sprintf('Done with recurrence #%d', $recurrence->id)); $result[$recurrence->user_id] = $result[$recurrence->user_id]->merge($createdReps); @@ -153,6 +153,7 @@ class CreateRecurringTransactions implements ShouldQueue */ private function validRecurrence(Recurrence $recurrence): bool { + Log::debug(sprintf('Now filtering recurrence #%d, owned by user #%d', $recurrence->id, $recurrence->user_id)); // is not active. if (!$this->active($recurrence)) { Log::info(sprintf('Recurrence #%d is not active. Skipped.', $recurrence->id)); @@ -203,6 +204,7 @@ class CreateRecurringTransactions implements ShouldQueue return false; } + Log::debug('Will be included.'); return true; } diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 3e79b55f58..92a4e98af2 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -54,7 +54,7 @@ class RouteServiceProvider extends ServiceProvider public function map(): void { $this->mapApiRoutes(); - + $this->mapCronApiRoutes(); $this->mapWebRoutes(); } @@ -71,6 +71,19 @@ class RouteServiceProvider extends ServiceProvider ->group(base_path('routes/api.php')); } + /** + * Define the "api" routes for the application. + * + * These routes are typically stateless. + */ + protected function mapCronApiRoutes(): void + { + Route::prefix('api/v1/cron') + ->middleware('apiY') + ->namespace($this->namespace) + ->group(base_path('routes/api-noauth.php')); + } + /** * Define the "web" routes for the application. * diff --git a/app/Support/Cronjobs/AbstractCronjob.php b/app/Support/Cronjobs/AbstractCronjob.php index e9f815dbd9..04b22249bb 100644 --- a/app/Support/Cronjobs/AbstractCronjob.php +++ b/app/Support/Cronjobs/AbstractCronjob.php @@ -33,12 +33,15 @@ use Exception; */ abstract class AbstractCronjob { - /** @var int */ - public $timeBetweenRuns = 43200; - /** @var Carbon */ - protected $date; - /** @var bool */ - protected $force; + public int $timeBetweenRuns = 43200; + protected Carbon $date; + protected bool $force; + + public bool $jobFired; + public bool $jobSucceeded; + public bool $jobErrored; + + public ?string $message; /** * AbstractCronjob constructor. @@ -49,12 +52,16 @@ abstract class AbstractCronjob { $this->force = false; $this->date = today(config('app.timezone')); + $this->jobErrored = false; + $this->jobSucceeded = false; + $this->jobFired = false; + $this->message = null; } /** - * @return bool + * */ - abstract public function fire(): bool; + abstract public function fire(): void; /** * @param Carbon $date diff --git a/app/Support/Cronjobs/AutoBudgetCronjob.php b/app/Support/Cronjobs/AutoBudgetCronjob.php index 059491c4f6..aa210a7927 100644 --- a/app/Support/Cronjobs/AutoBudgetCronjob.php +++ b/app/Support/Cronjobs/AutoBudgetCronjob.php @@ -39,7 +39,7 @@ class AutoBudgetCronjob extends AbstractCronjob /** * @inheritDoc */ - public function fire(): bool + public function fire(): void { /** @var Configuration $config */ $config = app('fireflyconfig')->get('last_ab_job', 0); @@ -54,8 +54,8 @@ class AutoBudgetCronjob extends AbstractCronjob Log::info(sprintf('It has been %s since the auto budget cron-job has fired.', $diffForHumans)); if (false === $this->force) { Log::info('The auto budget cron-job will not fire now.'); - - return false; + $this->message = sprintf('It has been %s since the auto budget cron-job has fired. It will not fire now.', $diffForHumans); + return; } // fire job regardless. @@ -69,10 +69,7 @@ class AutoBudgetCronjob extends AbstractCronjob } $this->fireAutoBudget(); - app('preferences')->mark(); - - return true; } /** @@ -85,6 +82,13 @@ class AutoBudgetCronjob extends AbstractCronjob $job = app(CreateAutoBudgetLimits::class); $job->setDate($this->date); $job->handle(); + + // get stuff from job: + $this->jobFired = true; + $this->jobErrored = false; + $this->jobSucceeded = true; + $this->message = 'Auto-budget cron job fired successfully.'; + app('fireflyconfig')->set('last_ab_job', (int)$this->date->format('U')); Log::info('Done with auto budget cron job task.'); } diff --git a/app/Support/Cronjobs/RecurringCronjob.php b/app/Support/Cronjobs/RecurringCronjob.php index ebf4f73525..c27a8366ee 100644 --- a/app/Support/Cronjobs/RecurringCronjob.php +++ b/app/Support/Cronjobs/RecurringCronjob.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Support\Cronjobs; use Carbon\Carbon; +use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Jobs\CreateRecurringTransactions; use FireflyIII\Models\Configuration; @@ -35,16 +36,17 @@ use Log; class RecurringCronjob extends AbstractCronjob { /** - * @return bool * @throws FireflyException */ - public function fire(): bool + public function fire(): void { + Log::debug(sprintf('Now in %s', __METHOD__)); /** @var Configuration $config */ $config = app('fireflyconfig')->get('last_rt_job', 0); $lastTime = (int)$config->data; $diff = time() - $lastTime; $diffForHumans = Carbon::now()->diffForHumans(Carbon::createFromTimestamp($lastTime), true); + if (0 === $lastTime) { Log::info('Recurring transactions cron-job has never fired before.'); } @@ -53,8 +55,9 @@ class RecurringCronjob extends AbstractCronjob Log::info(sprintf('It has been %s since the recurring transactions cron-job has fired.', $diffForHumans)); if (false === $this->force) { Log::info('The cron-job will not fire now.'); + $this->message = sprintf('It has been %s since the recurring transactions cron-job has fired. It will not fire now.', $diffForHumans); - return false; + return; } // fire job regardless. @@ -70,8 +73,6 @@ class RecurringCronjob extends AbstractCronjob $this->fireRecurring(); app('preferences')->mark(); - - return true; } /** @@ -85,6 +86,13 @@ class RecurringCronjob extends AbstractCronjob $job->setDate($this->date); $job->setForce($this->force); $job->handle(); + + // get stuff from job: + $this->jobFired = true; + $this->jobErrored = false; + $this->jobSucceeded = true; + $this->message = 'Recurring transactions cron job fired successfully.'; + app('fireflyconfig')->set('last_rt_job', (int)$this->date->format('U')); Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int)$this->date->format('U'))); Log::info('Done with recurring cron job task.'); diff --git a/app/Support/Cronjobs/TelemetryCronjob.php b/app/Support/Cronjobs/TelemetryCronjob.php index de4df4adae..a7b0ad1f64 100644 --- a/app/Support/Cronjobs/TelemetryCronjob.php +++ b/app/Support/Cronjobs/TelemetryCronjob.php @@ -40,13 +40,15 @@ class TelemetryCronjob extends AbstractCronjob * @inheritDoc * @throws FireflyException */ - public function fire(): bool + public function fire(): void { // do not fire if telemetry is disabled. if (false === config('firefly.send_telemetry') || false === config('firefly.feature_flags.telemetry')) { - Log::warning('Telemetry is disabled. The cron job will do nothing.'); + $msg = 'Telemetry is disabled. The cron job will do nothing.'; + $this->message = $msg; + Log::warning($msg); - return false; + return; } @@ -63,8 +65,8 @@ class TelemetryCronjob extends AbstractCronjob Log::info(sprintf('It has been %s since the telemetry cron-job has fired.', $diffForHumans)); if (false === $this->force) { Log::info('The cron-job will not fire now.'); - - return false; + $this->message = sprintf('It has been %s since the telemetry cron-job has fired. It will not fire now.', $diffForHumans); + return; } // fire job regardless. @@ -80,8 +82,6 @@ class TelemetryCronjob extends AbstractCronjob $this->fireTelemetry(); app('preferences')->mark(); - - return true; } @@ -97,6 +97,11 @@ class TelemetryCronjob extends AbstractCronjob $job->setForce($this->force); $job->handle(); + $this->jobFired = true; + $this->jobErrored = false; + $this->jobSucceeded = true; + $this->message = 'Telemetry cron job fired successfully.'; + // TODO remove old, submitted telemetry data. app('fireflyconfig')->set('last_tm_job', (int)$this->date->format('U')); diff --git a/app/Support/Http/Controllers/CreateStuff.php b/app/Support/Http/Controllers/CreateStuff.php index adc307e205..e9c0f1bbab 100644 --- a/app/Support/Http/Controllers/CreateStuff.php +++ b/app/Support/Http/Controllers/CreateStuff.php @@ -30,7 +30,7 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\User; use Laravel\Passport\Passport; use Log; -use phpseclib3\Crypt\RSA; + /** * Trait CreateStuff @@ -110,7 +110,7 @@ trait CreateStuff $keys = $rsa->createKey(4096); } if (8 === PHP_MAJOR_VERSION) { - $keys = RSA::createKeys(4096); + $keys = \phpseclib3\Crypt\RSA::createKey(4096); } [$publicKey, $privateKey] = [ diff --git a/app/Support/Http/Controllers/CronRunner.php b/app/Support/Http/Controllers/CronRunner.php index 58a28932b1..58de807fef 100644 --- a/app/Support/Http/Controllers/CronRunner.php +++ b/app/Support/Http/Controllers/CronRunner.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Support\Http\Controllers; +use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Support\Cronjobs\AutoBudgetCronjob; use FireflyIII\Support\Cronjobs\RecurringCronjob; @@ -34,60 +35,97 @@ use FireflyIII\Support\Cronjobs\TelemetryCronjob; trait CronRunner { /** - * @return string + * @param bool $force + * @param Carbon $date + * + * @return array */ - protected function runAutoBudget(): string + protected function runAutoBudget(bool $force, Carbon $date): array { /** @var AutoBudgetCronjob $autoBudget */ $autoBudget = app(AutoBudgetCronjob::class); + $autoBudget->setForce($force); + $autoBudget->setDate($date); try { - $result = $autoBudget->fire(); + $autoBudget->fire(); } catch (FireflyException $e) { - return $e->getMessage(); - } - if (false === $result) { - return 'The auto budget cron job did not fire.'; + return [ + 'job_fired' => false, + 'job_succeeded' => false, + 'job_errored' => true, + 'message' => $e->getMessage(), + ]; } - return 'The auto budget cron job fired successfully.'; + return [ + 'job_fired' => $autoBudget->jobFired, + 'job_succeeded' => $autoBudget->jobSucceeded, + 'job_errored' => $autoBudget->jobErrored, + 'message' => $autoBudget->message, + ]; } /** - * @return string + * @param bool $force + * @param Carbon $date + * + * @return array */ - protected function runRecurring(): string + protected function runRecurring(bool $force, Carbon $date): array { /** @var RecurringCronjob $recurring */ $recurring = app(RecurringCronjob::class); + $recurring->setForce($force); + $recurring->setDate($date); try { - $result = $recurring->fire(); + $recurring->fire(); } catch (FireflyException $e) { - return $e->getMessage(); - } - if (false === $result) { - return 'The recurring transaction cron job did not fire. It was fired less than half a day ago.'; + return [ + 'job_fired' => false, + 'job_succeeded' => false, + 'job_errored' => true, + 'message' => $e->getMessage(), + ]; } - return 'The recurring transaction cron job fired successfully.'; + return [ + 'job_fired' => $recurring->jobFired, + 'job_succeeded' => $recurring->jobSucceeded, + 'job_errored' => $recurring->jobErrored, + 'message' => $recurring->message, + ]; + } /** - * @return string + * @param bool $force + * @param Carbon $date + * + * @return array */ - protected function runTelemetry(): string + protected function runTelemetry(bool $force, Carbon $date): array { /** @var TelemetryCronjob $telemetry */ $telemetry = app(TelemetryCronjob::class); + $telemetry->setForce($force); + $telemetry->setDate($date); try { - $result = $telemetry->fire(); + $telemetry->fire(); } catch (FireflyException $e) { - return $e->getMessage(); - } - if (false === $result) { - return 'The telemetry cron job did not fire.'; + return [ + 'job_fired' => false, + 'job_succeeded' => false, + 'job_errored' => true, + 'message' => $e->getMessage(), + ]; } - return 'The telemetry cron job fired successfully.'; + return [ + 'job_fired' => $telemetry->jobFired, + 'job_succeeded' => $telemetry->jobSucceeded, + 'job_errored' => $telemetry->jobErrored, + 'message' => $telemetry->message, + ]; } } diff --git a/changelog.md b/changelog.md index ba3a246a86..7bf5e1551b 100644 --- a/changelog.md +++ b/changelog.md @@ -23,6 +23,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Changed - OAuth settings are visible for LDAP users. - If you set `FIREFLY_III_LAYOUT=v2`, Firefly III will show you the new layout on pages where it's available. +- New favicon. +- Cron job endpoint has changed. ### Deprecated - The current layout will no longer receive fixes and changes. @@ -48,6 +50,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - [Issue 4235](https://github.com/firefly-iii/firefly-iii/issues/4235) Info popup instandard financial report does not apply report's account filter - [Issue 4241](https://github.com/firefly-iii/firefly-iii/issues/4241) Broken chart - PHP configs that have "MB" as size indicator would be parsed badly. +- RSA token generation is now PHP7/8 compatible. ### API @@ -61,6 +64,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - [Issue 4394](https://github.com/firefly-iii/firefly-iii/issues/4394) Storing budgets works again. - [Issue 4426](https://github.com/firefly-iii/firefly-iii/issues/4426) Storing accounts would lead to bad capitalization in liability type. - [Issue 4435](https://github.com/firefly-iii/firefly-iii/issues/4435) Storing piggy banks with object group information would fail. +- Users can submit almost any field without other fields changing as well. ## 5.4.6 (API 1.4.0) - 2020-10-07 diff --git a/routes/api-noauth.php b/routes/api-noauth.php new file mode 100644 index 0000000000..44e99eb4d1 --- /dev/null +++ b/routes/api-noauth.php @@ -0,0 +1,13 @@ + 'FireflyIII\Api\V1\Controllers\System', 'prefix' => '', + 'as' => 'api.v1.cron.'], + static function () { + Route::get('{cliToken}', ['uses' => 'CronController@cron', 'as' => 'index']); + } +); \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 0231085512..7be605edc2 100644 --- a/routes/api.php +++ b/routes/api.php @@ -510,6 +510,8 @@ Route::group( } ); + + // Configuration API routes Route::group( ['namespace' => 'FireflyIII\Api\V1\Controllers\System', 'prefix' => 'configuration',