Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef170a8ca7 |
@@ -21,3 +21,7 @@ indent_size = 4
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.xml]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
@@ -45,3 +45,7 @@ APP_SECRET=change_this_to_something_unique
|
||||
#================================================================================
|
||||
# unlikely, that you need to change this one
|
||||
CORS_ALLOW_ORIGIN=^https?://localhost(:[0-9]+)?$
|
||||
|
||||
###> symfony/messenger ###
|
||||
MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
|
||||
###< symfony/messenger ###
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
"symfony/translation": "^6.0",
|
||||
"symfony/twig-bundle": "^6.0",
|
||||
"symfony/validator": "^6.0",
|
||||
"symfony/webhook": "^6.0",
|
||||
"symfony/webpack-encore-bundle": "^2.0",
|
||||
"symfony/yaml": "^6.0",
|
||||
"twig/cssinliner-extra": "^3.0",
|
||||
|
||||
234
composer.lock
generated
234
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "9f49e3cfd10e3bdd29e6bd67968ec19b",
|
||||
"content-hash": "9d1ceee2eb5d9ad5d14add5cd65f49d2",
|
||||
"packages": [
|
||||
{
|
||||
"name": "azuyalabs/yasumi",
|
||||
@@ -7725,6 +7725,97 @@
|
||||
],
|
||||
"time": "2025-10-24T13:29:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/messenger",
|
||||
"version": "v6.4.28",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/messenger.git",
|
||||
"reference": "6a758210a2392c4a326de84a6c8c70e7e18296f9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/messenger/zipball/6a758210a2392c4a326de84a6c8c70e7e18296f9",
|
||||
"reference": "6a758210a2392c4a326de84a6c8c70e7e18296f9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"psr/log": "^1|^2|^3",
|
||||
"symfony/clock": "^6.3|^7.0",
|
||||
"symfony/deprecation-contracts": "^2.5|^3"
|
||||
},
|
||||
"conflict": {
|
||||
"symfony/console": "<6.3",
|
||||
"symfony/event-dispatcher": "<5.4",
|
||||
"symfony/event-dispatcher-contracts": "<2.5",
|
||||
"symfony/framework-bundle": "<5.4",
|
||||
"symfony/http-kernel": "<5.4",
|
||||
"symfony/serializer": "<5.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"psr/cache": "^1.0|^2.0|^3.0",
|
||||
"symfony/console": "^6.3|^7.0",
|
||||
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
|
||||
"symfony/event-dispatcher": "^5.4|^6.0|^7.0",
|
||||
"symfony/http-kernel": "^5.4|^6.0|^7.0",
|
||||
"symfony/process": "^5.4|^6.0|^7.0",
|
||||
"symfony/property-access": "^5.4|^6.0|^7.0",
|
||||
"symfony/rate-limiter": "^5.4|^6.0|^7.0",
|
||||
"symfony/routing": "^5.4|^6.0|^7.0",
|
||||
"symfony/serializer": "^5.4|^6.0|^7.0",
|
||||
"symfony/service-contracts": "^2.5|^3",
|
||||
"symfony/stopwatch": "^5.4|^6.0|^7.0",
|
||||
"symfony/validator": "^5.4|^6.0|^7.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Messenger\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Samuel Roze",
|
||||
"email": "samuel.roze@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Helps applications send and receive messages to/from other applications or via message queues",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/messenger/tree/v6.4.28"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-11-06T11:12:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/mime",
|
||||
"version": "v6.4.30",
|
||||
@@ -8595,6 +8686,75 @@
|
||||
],
|
||||
"time": "2025-07-10T08:14:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/remote-event",
|
||||
"version": "v6.4.24",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/remote-event.git",
|
||||
"reference": "920e68c4f09f460609cf39a3540f1aa19da72c21"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/remote-event/zipball/920e68c4f09f460609cf39a3540f1aa19da72c21",
|
||||
"reference": "920e68c4f09f460609cf39a3540f1aa19da72c21",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"symfony/messenger": "^5.4|^6.1|^7.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\RemoteEvent\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Eases handling remote events",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"event"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/remote-event/tree/v6.4.24"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-10T08:14:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/routing",
|
||||
"version": "v6.4.30",
|
||||
@@ -10131,6 +10291,78 @@
|
||||
],
|
||||
"time": "2025-09-11T09:57:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/webhook",
|
||||
"version": "v6.4.24",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/webhook.git",
|
||||
"reference": "95d6f437a17224029983f2bed3cfaca09e530a17"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/webhook/zipball/95d6f437a17224029983f2bed3cfaca09e530a17",
|
||||
"reference": "95d6f437a17224029983f2bed3cfaca09e530a17",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"symfony/http-foundation": "^6.3|^7.0",
|
||||
"symfony/http-kernel": "^6.3|^7.0",
|
||||
"symfony/messenger": "^5.4|^6.1|^7.0",
|
||||
"symfony/remote-event": "^6.3|^7.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Webhook\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Eases sending and consuming webhooks",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"webhook"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/webhook/tree/v6.4.24"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-10T08:14:14+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/webpack-encore-bundle",
|
||||
"version": "v2.4.0",
|
||||
|
||||
22
config/packages/messenger.yaml
Normal file
22
config/packages/messenger.yaml
Normal file
@@ -0,0 +1,22 @@
|
||||
framework:
|
||||
messenger:
|
||||
# Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
|
||||
# failure_transport: failed
|
||||
|
||||
transports:
|
||||
# https://symfony.com/doc/current/messenger.html#transport-configuration
|
||||
# async: '%env(MESSENGER_TRANSPORT_DSN)%'
|
||||
# failed: 'doctrine://default?queue_name=failed'
|
||||
sync: 'sync://'
|
||||
|
||||
routing:
|
||||
# Route your messages to the transports
|
||||
# 'App\Message\YourMessage': async
|
||||
|
||||
# when@test:
|
||||
# framework:
|
||||
# messenger:
|
||||
# transports:
|
||||
# # replace with your transport name here (e.g., my_transport: 'in-memory://')
|
||||
# # For more Messenger testing tools, see https://github.com/zenstruck/messenger-test
|
||||
# async: 'in-memory://'
|
||||
3
config/routes/webhook.yaml
Normal file
3
config/routes/webhook.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
#webhook:
|
||||
# resource: '@FrameworkBundle/Resources/config/routing/webhook.xml'
|
||||
# prefix: /webhook
|
||||
@@ -38,6 +38,13 @@
|
||||
GRANT execute,select,insert,update,delete,create,alter,drop,index,references ON `kimai2_test`.* TO kimai2_test@127.0.0.1;
|
||||
-->
|
||||
<env name="BOOTSTRAP_RESET_DATABASE" value="true"/>
|
||||
|
||||
<!-- ###+ symfony/messenger ### -->
|
||||
<!-- Choose one of the transports below -->
|
||||
<!-- MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages -->
|
||||
<!-- MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages -->
|
||||
<env name="MESSENGER_TRANSPORT_DSN" value="doctrine://default?auto_setup=0"/>
|
||||
<!-- ###- symfony/messenger ### -->
|
||||
</php>
|
||||
<testsuites>
|
||||
<testsuite name="Kimai">
|
||||
|
||||
60
src/Entity/WebhookConfiguration.php
Normal file
60
src/Entity/WebhookConfiguration.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
class WebhookConfiguration
|
||||
{
|
||||
private string $name;
|
||||
private string $url;
|
||||
private string $transport;
|
||||
private string $secret;
|
||||
private string $authentication;
|
||||
|
||||
public function __construct(
|
||||
string $name,
|
||||
string $url,
|
||||
string $transport,
|
||||
#[\SensitiveParameter]
|
||||
string $secret,
|
||||
string $authentication
|
||||
)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->url = $url;
|
||||
$this->transport = $transport;
|
||||
$this->secret = $secret;
|
||||
$this->authentication = $authentication;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getUrl(): string
|
||||
{
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
public function getTransport(): string
|
||||
{
|
||||
return $this->transport;
|
||||
}
|
||||
|
||||
public function getSecret(): string
|
||||
{
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
public function getAuthentication(): string
|
||||
{
|
||||
return $this->authentication;
|
||||
}
|
||||
}
|
||||
32
src/Entity/WebhookEvent.php
Normal file
32
src/Entity/WebhookEvent.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
class WebhookEvent
|
||||
{
|
||||
private string $name;
|
||||
private WebhookConfiguration $configuration;
|
||||
|
||||
public function __construct(string $name, WebhookConfiguration $configuration)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->configuration = $configuration;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getConfiguration(): WebhookConfiguration
|
||||
{
|
||||
return $this->configuration;
|
||||
}
|
||||
}
|
||||
64
src/Serializer/Serializer.php
Normal file
64
src/Serializer/Serializer.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Serializer;
|
||||
|
||||
use JMS\Serializer\Context;
|
||||
use JMS\Serializer\DeserializationContext;
|
||||
use JMS\Serializer\SerializationContext;
|
||||
use JMS\Serializer\Serializer as CoreSerializer;
|
||||
use JMS\Serializer\SerializerInterface as CoreSerializerInterface;
|
||||
|
||||
class Serializer implements SerializerInterface
|
||||
{
|
||||
public function __construct(private readonly CoreSerializerInterface $serializer)
|
||||
{
|
||||
}
|
||||
|
||||
private function prepareContext(Context $jmsContext, array $context = []): void
|
||||
{
|
||||
$groups = ['Default'];
|
||||
if (\array_key_exists('groups', $context)) {
|
||||
$groups = $context['groups'];
|
||||
}
|
||||
|
||||
$jmsContext->setGroups($groups);
|
||||
$jmsContext->enableMaxDepthChecks();
|
||||
}
|
||||
|
||||
public function toArray(mixed $data, array $context = []): array
|
||||
{
|
||||
$jmsContext = SerializationContext::create();
|
||||
$this->prepareContext($jmsContext, $context);
|
||||
|
||||
if ($this->serializer instanceof CoreSerializer) {
|
||||
return $this->serializer->toArray($data, $jmsContext);
|
||||
}
|
||||
|
||||
$json = $this->serializer->serialize($data, 'json', $jmsContext);
|
||||
|
||||
return json_decode($json, true, 512, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
public function serialize(mixed $data, string $format, array $context = []): string
|
||||
{
|
||||
$jmsContext = SerializationContext::create();
|
||||
$this->prepareContext($jmsContext, $context);
|
||||
|
||||
return $this->serializer->serialize($data, $format, $jmsContext);
|
||||
}
|
||||
|
||||
public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed
|
||||
{
|
||||
$jmsContext = DeserializationContext::create();
|
||||
$this->prepareContext($jmsContext, $context);
|
||||
|
||||
return $this->serializer->deserialize($data, $type, $format, $jmsContext);
|
||||
}
|
||||
}
|
||||
17
src/Serializer/SerializerInterface.php
Normal file
17
src/Serializer/SerializerInterface.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Serializer;
|
||||
|
||||
use Symfony\Component\Serializer\SerializerInterface as BaseSerializerInterface;
|
||||
|
||||
interface SerializerInterface extends BaseSerializerInterface
|
||||
{
|
||||
public function toArray(mixed $data, array $context = []): array;
|
||||
}
|
||||
109
src/Webhook/WebhookListener.php
Normal file
109
src/Webhook/WebhookListener.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Webhook;
|
||||
|
||||
use App\Event\ActivityCreatePostEvent;
|
||||
use App\Event\ActivityDeleteEvent;
|
||||
use App\Event\ActivityUpdatePostEvent;
|
||||
use App\Event\CustomerCreatePostEvent;
|
||||
use App\Event\CustomerDeleteEvent;
|
||||
use App\Event\CustomerUpdatePostEvent;
|
||||
use App\Event\InvoiceCreatedEvent;
|
||||
use App\Event\InvoiceDeleteEvent;
|
||||
use App\Event\ProjectCreatePostEvent;
|
||||
use App\Event\ProjectDeleteEvent;
|
||||
use App\Event\ProjectUpdatePostEvent;
|
||||
use App\Event\TeamCreatePostEvent;
|
||||
use App\Event\TeamDeleteEvent;
|
||||
use App\Event\TeamUpdatePostEvent;
|
||||
use App\Event\TimesheetCreatePostEvent;
|
||||
use App\Event\TimesheetStopPostEvent;
|
||||
use App\Event\TimesheetUpdatePostEvent;
|
||||
use App\Event\UserCreatePostEvent;
|
||||
use App\Event\UserDeletePostEvent;
|
||||
use App\Event\UserUpdatePostEvent;
|
||||
use App\Webhook\Attribute\AsWebhook;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
final class WebhookListener implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(private readonly WebhookService $webhookService)
|
||||
{
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
ActivityCreatePostEvent::class => ['triggerWebhook', 1000],
|
||||
ActivityDeleteEvent::class => ['triggerWebhook', 1000],
|
||||
ActivityUpdatePostEvent::class => ['triggerWebhook', 1000],
|
||||
|
||||
CustomerCreatePostEvent::class => ['triggerWebhook', 1000],
|
||||
CustomerDeleteEvent::class => ['triggerWebhook', 1000],
|
||||
CustomerUpdatePostEvent::class => ['triggerWebhook', 1000],
|
||||
|
||||
InvoiceCreatedEvent::class => ['triggerWebhook', 1000],
|
||||
InvoiceDeleteEvent::class => ['triggerWebhook', 1000],
|
||||
|
||||
ProjectCreatePostEvent::class => ['triggerWebhook', 1000],
|
||||
ProjectDeleteEvent::class => ['triggerWebhook', 1000],
|
||||
ProjectUpdatePostEvent::class => ['triggerWebhook', 1000],
|
||||
|
||||
TimesheetCreatePostEvent::class => ['triggerWebhook', 1000],
|
||||
TimesheetStopPostEvent::class => ['triggerWebhook', 1000],
|
||||
TimesheetUpdatePostEvent::class => ['triggerWebhook', 1000],
|
||||
|
||||
UserCreatePostEvent::class => ['triggerWebhook', 1000],
|
||||
UserDeletePostEvent::class => ['triggerWebhook', 1000],
|
||||
UserUpdatePostEvent::class => ['triggerWebhook', 1000],
|
||||
|
||||
TeamCreatePostEvent::class => ['triggerWebhook', 1000],
|
||||
TeamDeleteEvent::class => ['triggerWebhook', 1000],
|
||||
TeamUpdatePostEvent::class => ['triggerWebhook', 1000],
|
||||
];
|
||||
}
|
||||
|
||||
public function triggerWebhook(Event $event): void
|
||||
{
|
||||
$attribute = $this->findAttribute($event);
|
||||
if ($attribute === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->webhookService->hasWebhook($attribute->name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$expressionLanguage = new ExpressionLanguage();
|
||||
$parsed = $expressionLanguage->parse($attribute->payload, ['object']);
|
||||
$payload = $parsed->getNodes()->evaluate([], ['object' => $event]);
|
||||
|
||||
$this->webhookService->trigger($attribute->name, $payload);
|
||||
}
|
||||
|
||||
private function findAttribute(Event $event): ?AsWebhook
|
||||
{
|
||||
try {
|
||||
$reflectionClass = new \ReflectionClass($event);
|
||||
$attributes = $reflectionClass->getAttributes(AsWebhook::class);
|
||||
if (\count($attributes) === 1) {
|
||||
$attribute = $attributes[0];
|
||||
$args = $attribute->getArguments();
|
||||
|
||||
return new AsWebhook($args['name'], $args['description'], $args['payload']);
|
||||
}
|
||||
} catch (\Exception $ex) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
58
src/Webhook/WebhookService.php
Normal file
58
src/Webhook/WebhookService.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Webhook;
|
||||
|
||||
use App\Entity\WebhookConfiguration;
|
||||
use App\Entity\WebhookEvent;
|
||||
use App\Serializer\SerializerInterface;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\RemoteEvent\RemoteEvent;
|
||||
use Symfony\Component\Webhook\Messenger\SendWebhookMessage;
|
||||
use Symfony\Component\Webhook\Subscriber;
|
||||
|
||||
final class WebhookService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly SerializerInterface $serializer,
|
||||
private readonly MessageBusInterface $bus
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function hasWebhook(string $name): bool
|
||||
{
|
||||
return $name === 'user.created';
|
||||
}
|
||||
|
||||
public function trigger(string $name, mixed $payload): void
|
||||
{
|
||||
$content = $this->serializer->toArray($payload, ['groups' => ['Default', 'Entity', 'Expanded']]);
|
||||
|
||||
foreach ($this->findEventsByName($name) as $event) {
|
||||
$configuration = $event->getConfiguration();
|
||||
$subscriber = new Subscriber($configuration->getUrl(), $configuration->getSecret());
|
||||
$remoteEvent = new RemoteEvent($name, $name . '_' . uniqid(microtime()), $content);
|
||||
$message = new SendWebhookMessage($subscriber, $remoteEvent);
|
||||
$this->bus->dispatch($message);
|
||||
}
|
||||
}
|
||||
|
||||
public function findEventsByName(string $name): array
|
||||
{
|
||||
$url = 'TODO';
|
||||
$secret = 'TODO';
|
||||
|
||||
$configuration = new WebhookConfiguration('n8n', $url, 'json', $secret, 'bearer');
|
||||
|
||||
return [
|
||||
new WebhookEvent($name, $configuration),
|
||||
];
|
||||
}
|
||||
}
|
||||
24
symfony.lock
24
symfony.lock
@@ -467,6 +467,18 @@
|
||||
"config/packages/mailer.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/messenger": {
|
||||
"version": "6.4",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.0",
|
||||
"ref": "d8936e2e2230637ef97e5eecc0eea074eecae58b"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/messenger.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/mime": {
|
||||
"version": "v4.3.3"
|
||||
},
|
||||
@@ -598,6 +610,18 @@
|
||||
"ref": "6bdfa1a95f6b2e677ab985cd1af2eae35d62e0f6"
|
||||
}
|
||||
},
|
||||
"symfony/webhook": {
|
||||
"version": "6.4",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.3",
|
||||
"ref": "0bc853a74c98e448116ba53e118a7b6b3cdf9a03"
|
||||
},
|
||||
"files": [
|
||||
"config/routes/webhook.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/webpack-encore-bundle": {
|
||||
"version": "1.0",
|
||||
"recipe": {
|
||||
|
||||
Reference in New Issue
Block a user