diff --git a/app/Api/V1/Controllers/Models/Transaction/DestroyController.php b/app/Api/V1/Controllers/Models/Transaction/DestroyController.php index 95de7b033b..e78c5b676a 100644 --- a/app/Api/V1/Controllers/Models/Transaction/DestroyController.php +++ b/app/Api/V1/Controllers/Models/Transaction/DestroyController.php @@ -80,6 +80,7 @@ class DestroyController extends Controller */ public function destroy(TransactionGroup $transactionGroup): JsonResponse { + Log::debug(sprintf('Now in %s', __METHOD__)); // grab asset account(s) from group: $accounts = []; /** @var TransactionJournal $journal */ diff --git a/app/Api/V1/Controllers/Webhook/ShowController.php b/app/Api/V1/Controllers/Webhook/ShowController.php index 177b960492..817ecb8dcf 100644 --- a/app/Api/V1/Controllers/Webhook/ShowController.php +++ b/app/Api/V1/Controllers/Webhook/ShowController.php @@ -24,12 +24,18 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Webhook; use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Enums\WebhookTrigger; +use FireflyIII\Events\RequestedSendWebhookMessages; +use FireflyIII\Events\StoredTransactionGroup; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Generator\Webhook\MessageGeneratorInterface; +use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\Webhook; use FireflyIII\Repositories\Webhook\WebhookRepositoryInterface; use FireflyIII\Transformers\WebhookTransformer; use Illuminate\Http\JsonResponse; use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Item; @@ -111,4 +117,35 @@ class ShowController extends Controller return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); } + + /** + * This endpoint is documented at: + * https://api-docs.firefly-iii.org/#/webhooks/triggerWebhookTransaction + * + * This method recycles part of the code of the StoredGroupEventHandler. + * + * @param Webhook $webhook + * @param TransactionGroup $group + * @return JsonResponse + */ + public function triggerTransaction(Webhook $webhook, TransactionGroup $group): JsonResponse + { + /** @var MessageGeneratorInterface $engine */ + $engine = app(MessageGeneratorInterface::class); + $engine->setUser(auth()->user()); + + // tell the generator which trigger it should look for + $engine->setTrigger($webhook->trigger); + // tell the generator which objects to process + $engine->setObjects(new Collection([$group])); + // set the webhook to trigger + $engine->setWebhooks(new Collection([$webhook])); + // tell the generator to generate the messages + $engine->generateMessages(); + + // trigger event to send them: + event(new RequestedSendWebhookMessages()); + return response()->json([], 204); + + } } diff --git a/app/Api/V1/Controllers/Webhook/SubmitController.php b/app/Api/V1/Controllers/Webhook/SubmitController.php index c8d9677382..8c5cd1b391 100644 --- a/app/Api/V1/Controllers/Webhook/SubmitController.php +++ b/app/Api/V1/Controllers/Webhook/SubmitController.php @@ -56,8 +56,6 @@ class SubmitController extends Controller * This endpoint is documented at: * https://api-docs.firefly-iii.org/#/webhooks/submitWebook * - * Remove the specified resource from storage. - * * @param Webhook $webhook * * @return JsonResponse diff --git a/app/Events/DestroyedTransactionGroup.php b/app/Events/DestroyedTransactionGroup.php index 76b59e0c30..1b6e8aa4ae 100644 --- a/app/Events/DestroyedTransactionGroup.php +++ b/app/Events/DestroyedTransactionGroup.php @@ -26,6 +26,7 @@ namespace FireflyIII\Events; use FireflyIII\Models\TransactionGroup; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Log; /** * Class DestroyedTransactionGroup. @@ -45,6 +46,7 @@ class DestroyedTransactionGroup extends Event */ public function __construct(TransactionGroup $transactionGroup) { + Log::debug(sprintf('Now in %s', __METHOD__)); $this->transactionGroup = $transactionGroup; } } diff --git a/app/Generator/Webhook/MessageGeneratorInterface.php b/app/Generator/Webhook/MessageGeneratorInterface.php index 5b4831ec0f..3c6fe06f81 100644 --- a/app/Generator/Webhook/MessageGeneratorInterface.php +++ b/app/Generator/Webhook/MessageGeneratorInterface.php @@ -46,6 +46,12 @@ interface MessageGeneratorInterface */ public function setObjects(Collection $objects): void; + /** + * @param Collection $webhooks + * @return void + */ + public function setWebhooks(Collection $webhooks): void; + /** * @param int $trigger */ diff --git a/app/Generator/Webhook/StandardMessageGenerator.php b/app/Generator/Webhook/StandardMessageGenerator.php index d329ba3533..62f47bdbf0 100644 --- a/app/Generator/Webhook/StandardMessageGenerator.php +++ b/app/Generator/Webhook/StandardMessageGenerator.php @@ -53,6 +53,15 @@ class StandardMessageGenerator implements MessageGeneratorInterface private int $version = 0; private Collection $webhooks; + /** + * + */ + public function __construct() + { + $this->objects = new Collection(); + $this->webhooks = new Collection(); + } + /** * */ @@ -60,7 +69,9 @@ class StandardMessageGenerator implements MessageGeneratorInterface { Log::debug(__METHOD__); // get the webhooks: - $this->webhooks = $this->getWebhooks(); + if (0 === $this->webhooks->count()) { + $this->webhooks = $this->getWebhooks(); + } // do some debugging Log::debug( @@ -129,7 +140,9 @@ class StandardMessageGenerator implements MessageGeneratorInterface switch ($class) { default: // Line is ignored because all of Firefly III's Models have an id property. - Log::error(sprintf('Webhook #%d was given %s#%d to deal with but can\'t extract user ID from it.', $webhook->id, $class, $model->id)); // @phpstan-ignore-line + Log::error( + sprintf('Webhook #%d was given %s#%d to deal with but can\'t extract user ID from it.', $webhook->id, $class, $model->id) + ); // @phpstan-ignore-line return; case TransactionGroup::class: @@ -240,4 +253,12 @@ class StandardMessageGenerator implements MessageGeneratorInterface { $this->user = $user; } + + /** + * @inheritDoc + */ + public function setWebhooks(Collection $webhooks): void + { + $this->webhooks = $webhooks; + } } diff --git a/app/Handlers/Events/WebhookEventHandler.php b/app/Handlers/Events/WebhookEventHandler.php index b5c51bc20a..fd8c2fc735 100644 --- a/app/Handlers/Events/WebhookEventHandler.php +++ b/app/Handlers/Events/WebhookEventHandler.php @@ -37,9 +37,9 @@ class WebhookEventHandler */ public function sendWebhookMessages(): void { + Log::debug(sprintf('Now in %s', __METHOD__)); // kick off the job! - $messages = WebhookMessage::where('webhook_messages.sent', 0) - //->where('webhook_messages.errored', 0) + $messages = WebhookMessage::where('webhook_messages.sent',false) ->get(['webhook_messages.*']) ->filter( function (WebhookMessage $message) { @@ -48,7 +48,13 @@ class WebhookEventHandler )->splice(0, 5); Log::debug(sprintf('Found %d webhook message(s) ready to be send.', $messages->count())); foreach ($messages as $message) { - SendWebhookMessage::dispatch($message)->afterResponse(); + if (false === $message->sent) { + Log::debug(sprintf('Send message #%d', $message->id)); + SendWebhookMessage::dispatch($message)->afterResponse(); + } + if (false !== $message->sent) { + Log::debug(sprintf('Skip message #%d', $message->id)); + } } } } diff --git a/app/Http/Middleware/AcceptHeaders.php b/app/Http/Middleware/AcceptHeaders.php index ffbd0f77ba..8debaf520d 100644 --- a/app/Http/Middleware/AcceptHeaders.php +++ b/app/Http/Middleware/AcceptHeaders.php @@ -49,7 +49,7 @@ class AcceptHeaders if ('GET' === $method && !$request->accepts(['application/json', 'application/vdn.api+json'])) { throw new BadHttpHeaderException('Your request must accept either application/json or application/vdn.api+json.'); } - $allowed = ['application/x-www-form-urlencoded', 'application/json']; + $allowed = ['application/x-www-form-urlencoded', 'application/json','']; $submitted = (string)$request->header('Content-Type'); if (('POST' === $method || 'PUT' === $method) && !in_array($submitted, $allowed, true)) { $error = new BadHttpHeaderException(sprintf('Content-Type cannot be "%s"', $submitted)); diff --git a/app/Models/WebhookMessage.php b/app/Models/WebhookMessage.php index 19d62f451a..e74dcc1582 100644 --- a/app/Models/WebhookMessage.php +++ b/app/Models/WebhookMessage.php @@ -26,6 +26,7 @@ namespace FireflyIII\Models; use Eloquent; use FireflyIII\User; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -117,4 +118,15 @@ class WebhookMessage extends Model { return $this->hasMany(WebhookAttempt::class); } + /** + * Get the amount + * + * @return Attribute + */ + protected function sent(): Attribute + { + return Attribute::make( + get: fn ($value) => (bool)$value, + ); + } } diff --git a/app/Repositories/TransactionGroup/TransactionGroupRepository.php b/app/Repositories/TransactionGroup/TransactionGroupRepository.php index 163bdc3416..c88f1fca85 100644 --- a/app/Repositories/TransactionGroup/TransactionGroupRepository.php +++ b/app/Repositories/TransactionGroup/TransactionGroupRepository.php @@ -85,6 +85,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface */ public function destroy(TransactionGroup $group): void { + Log::debug(sprintf('Now in %s', __METHOD__)); $service = new TransactionGroupDestroyService(); $service->destroy($group); } diff --git a/app/Services/Internal/Destroy/JournalDestroyService.php b/app/Services/Internal/Destroy/JournalDestroyService.php index ba8de40a1b..994f066a5b 100644 --- a/app/Services/Internal/Destroy/JournalDestroyService.php +++ b/app/Services/Internal/Destroy/JournalDestroyService.php @@ -42,6 +42,7 @@ class JournalDestroyService */ public function destroy(TransactionJournal $journal): void { + Log::debug(sprintf('Now in %s', __METHOD__)); /** @var Transaction $transaction */ foreach ($journal->transactions()->get() as $transaction) { Log::debug(sprintf('Will now delete transaction #%d', $transaction->id)); diff --git a/app/Services/Internal/Destroy/TransactionGroupDestroyService.php b/app/Services/Internal/Destroy/TransactionGroupDestroyService.php index 2f99a0e941..d18cb35c29 100644 --- a/app/Services/Internal/Destroy/TransactionGroupDestroyService.php +++ b/app/Services/Internal/Destroy/TransactionGroupDestroyService.php @@ -25,6 +25,7 @@ namespace FireflyIII\Services\Internal\Destroy; use FireflyIII\Events\DestroyedTransactionGroup; use FireflyIII\Models\TransactionGroup; +use Illuminate\Support\Facades\Log; /** * Class TransactionGroupDestroyService @@ -38,6 +39,7 @@ class TransactionGroupDestroyService */ public function destroy(TransactionGroup $transactionGroup): void { + Log::debug(sprintf('Now in %s', __METHOD__)); /** @var JournalDestroyService $service */ $service = app(JournalDestroyService::class); foreach ($transactionGroup->transactionJournals as $journal) { diff --git a/app/Services/Webhook/StandardWebhookSender.php b/app/Services/Webhook/StandardWebhookSender.php index 6b1bc1dae3..ba99c844d8 100644 --- a/app/Services/Webhook/StandardWebhookSender.php +++ b/app/Services/Webhook/StandardWebhookSender.php @@ -56,7 +56,8 @@ class StandardWebhookSender implements WebhookSenderInterface // have the signature generator generate a signature. If it fails, the error thrown will // end up in send() to be caught. $signatureGenerator = app(SignatureGeneratorInterface::class); - + $this->message->sent = true; + $this->message->save(); try { $signature = $signatureGenerator->generate($this->message); } catch (FireflyException $e) { @@ -108,7 +109,6 @@ class StandardWebhookSender implements WebhookSenderInterface $client = new Client(); try { $res = $client->request('POST', $this->message->webhook->url, $options); - $this->message->sent = true; } catch (RequestException $e) { Log::error($e->getMessage()); Log::error($e->getTraceAsString()); @@ -127,6 +127,7 @@ class StandardWebhookSender implements WebhookSenderInterface return; } + $this->message->sent = true; $this->message->save(); Log::debug(sprintf('Webhook message #%d was sent. Status code %d', $this->message->id, $res->getStatusCode())); diff --git a/changelog.md b/changelog.md index f10e83c9d2..efdfd96342 100644 --- a/changelog.md +++ b/changelog.md @@ -30,6 +30,7 @@ Lots of new stuff that I invite you to test and break. - #6605 You can search for external ID values - Working beta of the new layout under `/v3/` - New authentication screens that support dark mode. +- There is a page for webhooks. ### Changed - Firefly III requires PHP 8.1 diff --git a/resources/assets/js/components/webhooks/Show.vue b/resources/assets/js/components/webhooks/Show.vue index 2868b3fc4d..e23c80fea8 100644 --- a/resources/assets/js/components/webhooks/Show.vue +++ b/resources/assets/js/components/webhooks/Show.vue @@ -68,7 +68,8 @@ - -