diff --git a/resources/container.php b/resources/container.php index b2099a43c..6e579ba1e 100644 --- a/resources/container.php +++ b/resources/container.php @@ -13,6 +13,8 @@ use Fusio\Impl\Repository as ImplRepository; use Fusio\Impl\Service\Action\Producer; use Fusio\Impl\Service\Event\Dispatcher; +use Fusio\Impl\Service\Tenant\LimiterInterface; +use Fusio\Impl\Tenant\UnlimitedLimiter; use Psr\Cache\CacheItemPoolInterface; use Psr\SimpleCache\CacheInterface; use PSX\Api; @@ -96,6 +98,9 @@ $services->set(Framework\Loader\ContextFactory::class); $services->alias(ContextFactoryInterface::class, Framework\Loader\ContextFactory::class); + $services->set(UnlimitedLimiter::class); + $services->alias(LimiterInterface::class, UnlimitedLimiter::class); + // psx $services->set(Framework\Loader\RoutingParser\DatabaseParser::class); $services->set(Framework\Loader\RoutingParser\CompositeParser::class); diff --git a/src/Authorization/UserContext.php b/src/Authorization/UserContext.php index 88bbbcd17..4aa90fa4d 100644 --- a/src/Authorization/UserContext.php +++ b/src/Authorization/UserContext.php @@ -69,14 +69,14 @@ public static function newContext(int $userId, ?int $appId = null, ?string $tena return new UserContext($userId, $appId, $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1', $tenantId); } - public static function newAnonymousContext(): self + public static function newAnonymousContext(?string $tenantId = null): self { - return self::newContext(1, 1); + return self::newContext(1, 1, $tenantId); } - public static function newCommandContext(): self + public static function newCommandContext(?string $tenantId = null): self { - return self::newContext(1, 1); + return self::newContext(1, 1, $tenantId); } public static function newActionContext(ContextInterface $context): self diff --git a/src/Backend/Action/Event/Subscription/Create.php b/src/Backend/Action/Webhook/Create.php similarity index 74% rename from src/Backend/Action/Event/Subscription/Create.php rename to src/Backend/Action/Webhook/Create.php index a4de3e867..4857a2fa7 100644 --- a/src/Backend/Action/Event/Subscription/Create.php +++ b/src/Backend/Action/Webhook/Create.php @@ -18,17 +18,15 @@ * limitations under the License. */ -namespace Fusio\Impl\Backend\Action\Event\Subscription; +namespace Fusio\Impl\Backend\Action\Webhook; -use Fusio\Engine\Action\RuntimeInterface; -use Fusio\Engine\ActionAbstract; use Fusio\Engine\ActionInterface; use Fusio\Engine\ContextInterface; use Fusio\Engine\ParametersInterface; use Fusio\Engine\RequestInterface; use Fusio\Impl\Authorization\UserContext; -use Fusio\Impl\Service\Event; -use Fusio\Model\Backend\EventSubscriptionCreate; +use Fusio\Impl\Service; +use Fusio\Model\Backend\WebhookCreate; use PSX\Http\Environment\HttpResponse; /** @@ -40,27 +38,27 @@ */ class Create implements ActionInterface { - private Event\Subscription $subscriptionService; + private Service\Webhook $webhookService; - public function __construct(Event\Subscription $subscriptionService) + public function __construct(Service\Webhook $webhookService) { - $this->subscriptionService = $subscriptionService; + $this->webhookService = $webhookService; } public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed { $body = $request->getPayload(); - assert($body instanceof EventSubscriptionCreate); + assert($body instanceof WebhookCreate); - $this->subscriptionService->create( + $this->webhookService->create( $body, UserContext::newActionContext($context) ); return new HttpResponse(201, [], [ 'success' => true, - 'message' => 'Subscription successfully created', + 'message' => 'Webhook successfully created', ]); } } diff --git a/src/Consumer/Action/Event/Subscription/Delete.php b/src/Backend/Action/Webhook/Delete.php similarity index 74% rename from src/Consumer/Action/Event/Subscription/Delete.php rename to src/Backend/Action/Webhook/Delete.php index 11e3116f0..cb7302a68 100644 --- a/src/Consumer/Action/Event/Subscription/Delete.php +++ b/src/Backend/Action/Webhook/Delete.php @@ -18,16 +18,14 @@ * limitations under the License. */ -namespace Fusio\Impl\Consumer\Action\Event\Subscription; +namespace Fusio\Impl\Backend\Action\Webhook; -use Fusio\Engine\Action\RuntimeInterface; -use Fusio\Engine\ActionAbstract; use Fusio\Engine\ActionInterface; use Fusio\Engine\ContextInterface; use Fusio\Engine\ParametersInterface; use Fusio\Engine\RequestInterface; use Fusio\Impl\Authorization\UserContext; -use Fusio\Impl\Service\Consumer\Subscription; +use Fusio\Impl\Service; /** * Delete @@ -38,23 +36,23 @@ */ class Delete implements ActionInterface { - private Subscription $subscriptionService; + private Service\Webhook $webhookService; - public function __construct(Subscription $subscriptionService) + public function __construct(Service\Webhook $webhookService) { - $this->subscriptionService = $subscriptionService; + $this->webhookService = $webhookService; } public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed { - $this->subscriptionService->delete( - (int) $request->get('subscription_id'), + $this->webhookService->delete( + $request->get('webhook_id'), UserContext::newActionContext($context) ); return [ 'success' => true, - 'message' => 'Subscription successfully deleted', + 'message' => 'Webhook successfully deleted', ]; } } diff --git a/src/Backend/Action/Event/Subscription/Get.php b/src/Backend/Action/Webhook/Get.php similarity index 88% rename from src/Backend/Action/Event/Subscription/Get.php rename to src/Backend/Action/Webhook/Get.php index 8e0244082..f645609dc 100644 --- a/src/Backend/Action/Event/Subscription/Get.php +++ b/src/Backend/Action/Webhook/Get.php @@ -18,7 +18,7 @@ * limitations under the License. */ -namespace Fusio\Impl\Backend\Action\Event\Subscription; +namespace Fusio\Impl\Backend\Action\Webhook; use Fusio\Engine\Action\RuntimeInterface; use Fusio\Engine\ActionAbstract; @@ -39,9 +39,9 @@ */ class Get implements ActionInterface { - private View\Event\Subscription $view; + private View\Webhook $view; - public function __construct(View\Event\Subscription $view) + public function __construct(View\Webhook $view) { $this->view = $view; } @@ -49,12 +49,12 @@ public function __construct(View\Event\Subscription $view) public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed { $subscription = $this->view->getEntity( - (int) $request->get('subscription_id'), + $request->get('webhook_id'), $context ); if (empty($subscription)) { - throw new StatusCode\NotFoundException('Could not find subscription'); + throw new StatusCode\NotFoundException('Could not find webhook'); } return $subscription; diff --git a/src/Backend/Action/Event/Subscription/GetAll.php b/src/Backend/Action/Webhook/GetAll.php similarity index 91% rename from src/Backend/Action/Event/Subscription/GetAll.php rename to src/Backend/Action/Webhook/GetAll.php index 44efd70f0..9645c5eb4 100644 --- a/src/Backend/Action/Event/Subscription/GetAll.php +++ b/src/Backend/Action/Webhook/GetAll.php @@ -18,7 +18,7 @@ * limitations under the License. */ -namespace Fusio\Impl\Backend\Action\Event\Subscription; +namespace Fusio\Impl\Backend\Action\Webhook; use Fusio\Engine\Action\RuntimeInterface; use Fusio\Engine\ActionAbstract; @@ -39,9 +39,9 @@ */ class GetAll implements ActionInterface { - private View\Event\Subscription $view; + private View\Webhook $view; - public function __construct(View\Event\Subscription $view) + public function __construct(View\Webhook $view) { $this->view = $view; } diff --git a/src/Backend/Action/Event/Subscription/Update.php b/src/Backend/Action/Webhook/Update.php similarity index 72% rename from src/Backend/Action/Event/Subscription/Update.php rename to src/Backend/Action/Webhook/Update.php index a345b26f4..9c2462ab0 100644 --- a/src/Backend/Action/Event/Subscription/Update.php +++ b/src/Backend/Action/Webhook/Update.php @@ -18,17 +18,15 @@ * limitations under the License. */ -namespace Fusio\Impl\Backend\Action\Event\Subscription; +namespace Fusio\Impl\Backend\Action\Webhook; -use Fusio\Engine\Action\RuntimeInterface; -use Fusio\Engine\ActionAbstract; use Fusio\Engine\ActionInterface; use Fusio\Engine\ContextInterface; use Fusio\Engine\ParametersInterface; use Fusio\Engine\RequestInterface; use Fusio\Impl\Authorization\UserContext; -use Fusio\Impl\Service\Event; -use Fusio\Model\Backend\EventSubscriptionUpdate; +use Fusio\Impl\Service; +use Fusio\Model\Backend\WebhookUpdate; /** * Update @@ -39,28 +37,28 @@ */ class Update implements ActionInterface { - private Event\Subscription $subscriptionService; + private Service\Webhook $webhookService; - public function __construct(Event\Subscription $subscriptionService) + public function __construct(Service\Webhook $webhookService) { - $this->subscriptionService = $subscriptionService; + $this->webhookService = $webhookService; } public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed { $body = $request->getPayload(); - assert($body instanceof EventSubscriptionUpdate); + assert($body instanceof WebhookUpdate); - $this->subscriptionService->update( - (int) $request->get('subscription_id'), + $this->webhookService->update( + $request->get('webhook_id'), $body, UserContext::newActionContext($context) ); return [ 'success' => true, - 'message' => 'Subscription successfully updated', + 'message' => 'Webhook successfully updated', ]; } } diff --git a/src/Backend/View/Event/Subscription.php b/src/Backend/View/Event/Subscription.php deleted file mode 100644 index d5521b99e..000000000 --- a/src/Backend/View/Event/Subscription.php +++ /dev/null @@ -1,127 +0,0 @@ - - * - * Copyright 2015-2023 Christoph Kappestein - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace Fusio\Impl\Backend\View\Event; - -use Fusio\Engine\ContextInterface; -use Fusio\Impl\Backend\Filter\QueryFilter; -use Fusio\Impl\Table; -use PSX\Nested\Builder; -use PSX\Nested\Reference; -use PSX\Sql\Condition; -use PSX\Sql\ViewAbstract; - -/** - * Subscription - * - * @author Christoph Kappestein - * @license http://www.apache.org/licenses/LICENSE-2.0 - * @link https://www.fusio-project.org - */ -class Subscription extends ViewAbstract -{ - public function getCollection(QueryFilter $filter, ContextInterface $context) - { - $startIndex = $filter->getStartIndex(); - $count = $filter->getCount(); - - $condition = $filter->getCondition([QueryFilter::COLUMN_SEARCH => Table\Generated\EventSubscriptionTable::COLUMN_ENDPOINT], 'subscription'); - $condition->equals('event.' . Table\Generated\EventTable::COLUMN_TENANT_ID, $context->getTenantId()); - $condition->equals('event.' . Table\Generated\EventTable::COLUMN_CATEGORY_ID, $context->getUser()->getCategoryId() ?: 1); - $condition->in('subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_STATUS, [Table\Event\Subscription::STATUS_ACTIVE]); - - $queryBuilder = $this->connection->createQueryBuilder() - ->select([ - 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_ID, - 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_EVENT_ID, - 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_USER_ID, - 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_ENDPOINT, - ]) - ->from('fusio_event_subscription', 'subscription') - ->innerJoin('subscription', 'fusio_event', 'event', 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_EVENT_ID . ' = event.' . Table\Generated\EventTable::COLUMN_ID) - ->orderBy('subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_ID, 'DESC') - ->where($condition->getExpression($this->connection->getDatabasePlatform())) - ->setParameters($condition->getValues()) - ->setFirstResult($startIndex) - ->setMaxResults($count); - - $countBuilder = $this->connection->createQueryBuilder() - ->select(['COUNT(*) AS cnt']) - ->from('fusio_event_subscription', 'subscription') - ->innerJoin('subscription', 'fusio_event', 'event', 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_EVENT_ID . ' = event.' . Table\Generated\EventTable::COLUMN_ID) - ->where($condition->getExpression($this->connection->getDatabasePlatform())) - ->setParameters($condition->getValues()); - - $builder = new Builder($this->connection); - - $definition = [ - 'totalResults' => $builder->doValue($countBuilder->getSQL(), $countBuilder->getParameters(), $builder->fieldInteger('cnt')), - 'startIndex' => $startIndex, - 'itemsPerPage' => $count, - 'entry' => $builder->doCollection($queryBuilder->getSQL(), $queryBuilder->getParameters(), [ - 'id' => $builder->fieldInteger(Table\Generated\EventSubscriptionTable::COLUMN_ID), - 'eventId' => $builder->fieldInteger(Table\Generated\EventSubscriptionTable::COLUMN_EVENT_ID), - 'userId' => $builder->fieldInteger(Table\Generated\EventSubscriptionTable::COLUMN_USER_ID), - 'endpoint' => Table\Generated\EventSubscriptionTable::COLUMN_ENDPOINT, - ]), - ]; - - return $builder->build($definition); - } - - public function getEntity(int $id, ContextInterface $context) - { - $condition = Condition::withAnd(); - $condition->equals('subscription.' . Table\Generated\EventTable::COLUMN_ID, $id); - $condition->equals('event.' . Table\Generated\EventTable::COLUMN_TENANT_ID, $context->getTenantId()); - $condition->equals('event.' . Table\Generated\EventTable::COLUMN_CATEGORY_ID, $context->getUser()->getCategoryId() ?: 1); - - $queryBuilder = $this->connection->createQueryBuilder() - ->select([ - 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_ID, - 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_EVENT_ID, - 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_USER_ID, - 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_ENDPOINT, - ]) - ->from('fusio_event_subscription', 'subscription') - ->innerJoin('subscription', 'fusio_event', 'event', 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_EVENT_ID . ' = event.' . Table\Generated\EventTable::COLUMN_ID) - ->where($condition->getExpression($this->connection->getDatabasePlatform())) - ->setParameters($condition->getValues()); - - $builder = new Builder($this->connection); - - $definition = $builder->doEntity($queryBuilder->getSQL(), $queryBuilder->getParameters(), [ - 'id' => $builder->fieldInteger(Table\Generated\EventSubscriptionTable::COLUMN_ID), - 'eventId' => $builder->fieldInteger(Table\Generated\EventSubscriptionTable::COLUMN_EVENT_ID), - 'userId' => $builder->fieldInteger(Table\Generated\EventSubscriptionTable::COLUMN_USER_ID), - 'endpoint' => Table\Generated\EventSubscriptionTable::COLUMN_ENDPOINT, - 'responses' => $builder->doCollection([$this->getTable(Table\Event\Response::class), 'getAllBySubscription'], [new Reference('id')], [ - 'id' => $builder->fieldInteger(Table\Generated\EventResponseTable::COLUMN_ID), - 'status' => $builder->fieldInteger(Table\Generated\EventResponseTable::COLUMN_STATUS), - 'attempts' => $builder->fieldInteger(Table\Generated\EventResponseTable::COLUMN_ATTEMPTS), - 'code' => $builder->fieldInteger(Table\Generated\EventResponseTable::COLUMN_CODE), - 'body' => Table\Generated\EventResponseTable::COLUMN_BODY, - 'executeDate' => $builder->fieldDateTime(Table\Generated\EventResponseTable::COLUMN_EXECUTE_DATE), - ]), - ]); - - return $builder->build($definition); - } -} diff --git a/src/Backend/View/Webhook.php b/src/Backend/View/Webhook.php new file mode 100644 index 000000000..b6ab28635 --- /dev/null +++ b/src/Backend/View/Webhook.php @@ -0,0 +1,90 @@ + + * + * Copyright 2015-2023 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Backend\View; + +use Fusio\Engine\ContextInterface; +use Fusio\Impl\Backend\Filter\QueryFilter; +use Fusio\Impl\Table; +use PSX\Nested\Builder; +use PSX\Nested\Reference; +use PSX\Sql\OrderBy; +use PSX\Sql\ViewAbstract; + +/** + * Webhook + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +class Webhook extends ViewAbstract +{ + public function getCollection(QueryFilter $filter, ContextInterface $context) + { + $startIndex = $filter->getStartIndex(); + $count = $filter->getCount(); + $sortBy = $filter->getSortBy(Table\Generated\WebhookTable::COLUMN_ID); + $sortOrder = $filter->getSortOrder(OrderBy::DESC); + + $condition = $filter->getCondition([QueryFilter::COLUMN_SEARCH => Table\Generated\WebhookTable::COLUMN_NAME]); + $condition->equals(Table\Generated\WebhookTable::COLUMN_TENANT_ID, $context->getTenantId()); + + $builder = new Builder($this->connection); + + $definition = [ + 'totalResults' => $this->getTable(Table\Webhook::class)->getCount($condition), + 'startIndex' => $startIndex, + 'itemsPerPage' => $count, + 'entry' => $builder->doCollection([$this->getTable(Table\Webhook::class), 'findAll'], [$condition, $startIndex, $count, $sortBy, $sortOrder], [ + 'id' => $builder->fieldInteger(Table\Generated\WebhookTable::COLUMN_ID), + 'eventId' => $builder->fieldInteger(Table\Generated\WebhookTable::COLUMN_EVENT_ID), + 'userId' => $builder->fieldInteger(Table\Generated\WebhookTable::COLUMN_USER_ID), + 'name' => Table\Generated\WebhookTable::COLUMN_NAME, + 'endpoint' => Table\Generated\WebhookTable::COLUMN_ENDPOINT, + ]), + ]; + + return $builder->build($definition); + } + + public function getEntity(string $id, ContextInterface $context) + { + $builder = new Builder($this->connection); + + $definition = $builder->doEntity([$this->getTable(Table\Webhook::class), 'findOneByIdentifier'], [$context->getTenantId(), $id], [ + 'id' => $builder->fieldInteger(Table\Generated\WebhookTable::COLUMN_ID), + 'eventId' => $builder->fieldInteger(Table\Generated\WebhookTable::COLUMN_EVENT_ID), + 'userId' => $builder->fieldInteger(Table\Generated\WebhookTable::COLUMN_USER_ID), + 'name' => Table\Generated\WebhookTable::COLUMN_NAME, + 'endpoint' => Table\Generated\WebhookTable::COLUMN_ENDPOINT, + 'responses' => $builder->doCollection([$this->getTable(Table\Webhook\Response::class), 'getAllByWebhook'], [new Reference('id')], [ + 'id' => $builder->fieldInteger(Table\Generated\WebhookResponseTable::COLUMN_ID), + 'status' => $builder->fieldInteger(Table\Generated\WebhookResponseTable::COLUMN_STATUS), + 'attempts' => $builder->fieldInteger(Table\Generated\WebhookResponseTable::COLUMN_ATTEMPTS), + 'code' => $builder->fieldInteger(Table\Generated\WebhookResponseTable::COLUMN_CODE), + 'body' => Table\Generated\WebhookResponseTable::COLUMN_BODY, + 'executeDate' => $builder->fieldDateTime(Table\Generated\WebhookResponseTable::COLUMN_EXECUTE_DATE), + ]), + ]); + + return $builder->build($definition); + } +} diff --git a/src/Command/System/UserAddCommand.php b/src/Command/System/UserAddCommand.php index b75651ac2..378abe261 100644 --- a/src/Command/System/UserAddCommand.php +++ b/src/Command/System/UserAddCommand.php @@ -64,11 +64,14 @@ protected function configure(): void ->addOption('role', 'r', InputOption::VALUE_OPTIONAL, 'Role of the account [1=Administrator, 2=Backend, 3=Consumer]') ->addOption('username', 'u', InputOption::VALUE_OPTIONAL, 'The username') ->addOption('email', 'e', InputOption::VALUE_OPTIONAL, 'The email') - ->addOption('password', 'p', InputOption::VALUE_OPTIONAL, 'The password'); + ->addOption('password', 'p', InputOption::VALUE_OPTIONAL, 'The password') + ->addOption('tenant', 't', InputOption::VALUE_OPTIONAL, 'The tenant id'); } protected function execute(InputInterface $input, OutputInterface $output): int { + $tenantId = $input->getOption('tenant'); + /** @var QuestionHelper $helper */ $helper = $this->getHelper('question'); @@ -87,8 +90,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $name = $input->getOption('username'); if ($name === null) { $question = new Question('Enter the username: '); - $question->setValidator(function ($value) { - $this->validator->assertName($value); + $question->setValidator(function ($value) use ($tenantId) { + $this->validator->assertName($value, $tenantId); return $value; }); @@ -98,15 +101,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw new RuntimeException('Provided an invalid name'); } - $this->validator->assertName($name); + $this->validator->assertName($name, $tenantId); } // email $email = $input->getOption('email'); if ($email === null) { $question = new Question('Enter the email: '); - $question->setValidator(function ($value) { - $this->validator->assertEmail($value); + $question->setValidator(function ($value) use ($tenantId) { + $this->validator->assertEmail($value, $tenantId); return $value; }); @@ -116,7 +119,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw new RuntimeException('Provided an invalid email'); } - $this->validator->assertEmail($email); + $this->validator->assertEmail($email, $tenantId); } // password diff --git a/src/Consumer/Action/App/Delete.php b/src/Consumer/Action/App/Delete.php index 0be55555f..007d92720 100644 --- a/src/Consumer/Action/App/Delete.php +++ b/src/Consumer/Action/App/Delete.php @@ -48,7 +48,7 @@ public function __construct(App $appService) public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed { $this->appService->delete( - (int) $request->get('app_id'), + $request->get('app_id'), UserContext::newActionContext($context) ); diff --git a/src/Consumer/Action/App/Update.php b/src/Consumer/Action/App/Update.php index 48fce6beb..25dcf9612 100644 --- a/src/Consumer/Action/App/Update.php +++ b/src/Consumer/Action/App/Update.php @@ -53,7 +53,7 @@ public function handle(RequestInterface $request, ParametersInterface $configura assert($body instanceof AppUpdate); $this->appService->update( - (int) $request->get('app_id'), + $request->get('app_id'), $body, UserContext::newActionContext($context) ); diff --git a/src/Consumer/Action/Event/Subscription/Create.php b/src/Consumer/Action/Webhook/Create.php similarity index 74% rename from src/Consumer/Action/Event/Subscription/Create.php rename to src/Consumer/Action/Webhook/Create.php index 8a4751803..3cb40777f 100644 --- a/src/Consumer/Action/Event/Subscription/Create.php +++ b/src/Consumer/Action/Webhook/Create.php @@ -18,17 +18,15 @@ * limitations under the License. */ -namespace Fusio\Impl\Consumer\Action\Event\Subscription; +namespace Fusio\Impl\Consumer\Action\Webhook; -use Fusio\Engine\Action\RuntimeInterface; -use Fusio\Engine\ActionAbstract; use Fusio\Engine\ActionInterface; use Fusio\Engine\ContextInterface; use Fusio\Engine\ParametersInterface; use Fusio\Engine\RequestInterface; use Fusio\Impl\Authorization\UserContext; -use Fusio\Impl\Service\Consumer\Subscription; -use Fusio\Model\Consumer\EventSubscriptionCreate; +use Fusio\Impl\Service\Consumer\Webhook; +use Fusio\Model\Consumer\WebhookCreate; use PSX\Http\Environment\HttpResponse; /** @@ -40,27 +38,27 @@ */ class Create implements ActionInterface { - private Subscription $subscriptionService; + private Webhook $webhookService; - public function __construct(Subscription $subscriptionService) + public function __construct(Webhook $webhookService) { - $this->subscriptionService = $subscriptionService; + $this->webhookService = $webhookService; } public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed { $body = $request->getPayload(); - assert($body instanceof EventSubscriptionCreate); + assert($body instanceof WebhookCreate); - $this->subscriptionService->create( + $this->webhookService->create( $body, UserContext::newActionContext($context) ); return new HttpResponse(201, [], [ 'success' => true, - 'message' => 'Subscription successfully created', + 'message' => 'Webhook successfully created', ]); } } diff --git a/src/Backend/Action/Event/Subscription/Delete.php b/src/Consumer/Action/Webhook/Delete.php similarity index 78% rename from src/Backend/Action/Event/Subscription/Delete.php rename to src/Consumer/Action/Webhook/Delete.php index c4a01e840..0fa1258b9 100644 --- a/src/Backend/Action/Event/Subscription/Delete.php +++ b/src/Consumer/Action/Webhook/Delete.php @@ -18,7 +18,7 @@ * limitations under the License. */ -namespace Fusio\Impl\Backend\Action\Event\Subscription; +namespace Fusio\Impl\Consumer\Action\Webhook; use Fusio\Engine\Action\RuntimeInterface; use Fusio\Engine\ActionAbstract; @@ -27,7 +27,7 @@ use Fusio\Engine\ParametersInterface; use Fusio\Engine\RequestInterface; use Fusio\Impl\Authorization\UserContext; -use Fusio\Impl\Service\Event; +use Fusio\Impl\Service\Consumer\Webhook; /** * Delete @@ -38,23 +38,23 @@ */ class Delete implements ActionInterface { - private Event\Subscription $subscriptionService; + private Webhook $webhookService; - public function __construct(Event\Subscription $subscriptionService) + public function __construct(Webhook $webhookService) { - $this->subscriptionService = $subscriptionService; + $this->webhookService = $webhookService; } public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed { - $this->subscriptionService->delete( - (int) $request->get('subscription_id'), + $this->webhookService->delete( + $request->get('webhook_id'), UserContext::newActionContext($context) ); return [ 'success' => true, - 'message' => 'Subscription successfully deleted', + 'message' => 'Webhook successfully deleted', ]; } } diff --git a/src/Consumer/Action/Event/Subscription/Get.php b/src/Consumer/Action/Webhook/Get.php similarity index 77% rename from src/Consumer/Action/Event/Subscription/Get.php rename to src/Consumer/Action/Webhook/Get.php index 806ebbab4..ed8d0168e 100644 --- a/src/Consumer/Action/Event/Subscription/Get.php +++ b/src/Consumer/Action/Webhook/Get.php @@ -18,17 +18,14 @@ * limitations under the License. */ -namespace Fusio\Impl\Consumer\Action\Event\Subscription; +namespace Fusio\Impl\Consumer\Action\Webhook; -use Fusio\Engine\Action\RuntimeInterface; -use Fusio\Engine\ActionAbstract; use Fusio\Engine\ActionInterface; use Fusio\Engine\ContextInterface; use Fusio\Engine\ParametersInterface; use Fusio\Engine\RequestInterface; use Fusio\Impl\Consumer\View; use PSX\Http\Exception as StatusCode; -use PSX\Sql\TableManagerInterface; /** * Get @@ -39,24 +36,24 @@ */ class Get implements ActionInterface { - private View\Event\Subscription $view; + private View\Webhook $view; - public function __construct(View\Event\Subscription $view) + public function __construct(View\Webhook $view) { $this->view = $view; } public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed { - $subscription = $this->view->getEntity( - (int) $request->get('subscription_id'), + $webhook = $this->view->getEntity( + $request->get('webhook_id'), $context, ); - if (empty($subscription)) { - throw new StatusCode\NotFoundException('Could not find subscription'); + if (empty($webhook)) { + throw new StatusCode\NotFoundException('Could not find webhook'); } - return $subscription; + return $webhook; } } diff --git a/src/Consumer/Action/Event/Subscription/GetAll.php b/src/Consumer/Action/Webhook/GetAll.php similarity index 85% rename from src/Consumer/Action/Event/Subscription/GetAll.php rename to src/Consumer/Action/Webhook/GetAll.php index 649d60eac..5e9f94c32 100644 --- a/src/Consumer/Action/Event/Subscription/GetAll.php +++ b/src/Consumer/Action/Webhook/GetAll.php @@ -18,17 +18,14 @@ * limitations under the License. */ -namespace Fusio\Impl\Consumer\Action\Event\Subscription; +namespace Fusio\Impl\Consumer\Action\Webhook; -use Fusio\Engine\Action\RuntimeInterface; -use Fusio\Engine\ActionAbstract; use Fusio\Engine\ActionInterface; use Fusio\Engine\ContextInterface; use Fusio\Engine\ParametersInterface; use Fusio\Engine\RequestInterface; use Fusio\Impl\Backend\Filter\QueryFilter; use Fusio\Impl\Consumer\View; -use PSX\Sql\TableManagerInterface; /** * GetAll @@ -39,9 +36,9 @@ */ class GetAll implements ActionInterface { - private View\Event\Subscription $view; + private View\Webhook $view; - public function __construct(View\Event\Subscription $view) + public function __construct(View\Webhook $view) { $this->view = $view; } diff --git a/src/Consumer/Action/Event/Subscription/Update.php b/src/Consumer/Action/Webhook/Update.php similarity index 71% rename from src/Consumer/Action/Event/Subscription/Update.php rename to src/Consumer/Action/Webhook/Update.php index 05406c40c..2a5d64b54 100644 --- a/src/Consumer/Action/Event/Subscription/Update.php +++ b/src/Consumer/Action/Webhook/Update.php @@ -18,17 +18,15 @@ * limitations under the License. */ -namespace Fusio\Impl\Consumer\Action\Event\Subscription; +namespace Fusio\Impl\Consumer\Action\Webhook; -use Fusio\Engine\Action\RuntimeInterface; -use Fusio\Engine\ActionAbstract; use Fusio\Engine\ActionInterface; use Fusio\Engine\ContextInterface; use Fusio\Engine\ParametersInterface; use Fusio\Engine\RequestInterface; use Fusio\Impl\Authorization\UserContext; -use Fusio\Impl\Service\Consumer\Subscription; -use Fusio\Model\Consumer\EventSubscriptionUpdate; +use Fusio\Impl\Service\Consumer\Webhook; +use Fusio\Model\Consumer\WebhookUpdate; /** * Update @@ -39,28 +37,28 @@ */ class Update implements ActionInterface { - private Subscription $subscriptionService; + private Webhook $webhookService; - public function __construct(Subscription $subscriptionService) + public function __construct(Webhook $webhookService) { - $this->subscriptionService = $subscriptionService; + $this->webhookService = $webhookService; } public function handle(RequestInterface $request, ParametersInterface $configuration, ContextInterface $context): mixed { $body = $request->getPayload(); - assert($body instanceof EventSubscriptionUpdate); + assert($body instanceof WebhookUpdate); - $this->subscriptionService->update( - (int) $request->get('subscription_id'), + $this->webhookService->update( + $request->get('webhook_id'), $body, UserContext::newActionContext($context) ); return [ 'success' => true, - 'message' => 'Subscription successfully updated', + 'message' => 'Webhook successfully updated', ]; } } diff --git a/src/Consumer/View/Event/Subscription.php b/src/Consumer/View/Webhook.php similarity index 58% rename from src/Consumer/View/Event/Subscription.php rename to src/Consumer/View/Webhook.php index 17069e901..3bfee3bec 100644 --- a/src/Consumer/View/Event/Subscription.php +++ b/src/Consumer/View/Webhook.php @@ -18,7 +18,7 @@ * limitations under the License. */ -namespace Fusio\Impl\Consumer\View\Event; +namespace Fusio\Impl\Consumer\View; use Fusio\Engine\ContextInterface; use Fusio\Impl\Backend\Filter\QueryFilter; @@ -29,13 +29,13 @@ use PSX\Sql\ViewAbstract; /** - * Subscription + * Webhook * * @author Christoph Kappestein * @license http://www.apache.org/licenses/LICENSE-2.0 * @link https://www.fusio-project.org */ -class Subscription extends ViewAbstract +class Webhook extends ViewAbstract { public function getCollection(QueryFilter $filter, ContextInterface $context) { @@ -43,21 +43,21 @@ public function getCollection(QueryFilter $filter, ContextInterface $context) $count = $filter->getCount(); $condition = Condition::withAnd(); - $condition->equals('subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_USER_ID, $context->getUser()->getId()); - $condition->in('subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_STATUS, [Table\Event\Subscription::STATUS_ACTIVE]); + $condition->equals('webhook.' . Table\Generated\WebhookTable::COLUMN_USER_ID, $context->getUser()->getId()); + $condition->in('webhook.' . Table\Generated\WebhookTable::COLUMN_STATUS, [Table\Webhook::STATUS_ACTIVE]); $condition->equals('event.' . Table\Generated\EventTable::COLUMN_TENANT_ID, $context->getTenantId()); $condition->equals('event.' . Table\Generated\EventTable::COLUMN_CATEGORY_ID, $context->getUser()->getCategoryId() ?: 1); $queryBuilder = $this->connection->createQueryBuilder() ->select([ - 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_ID, - 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_STATUS, - 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_ENDPOINT, + 'webhook.' . Table\Generated\WebhookTable::COLUMN_ID, + 'webhook.' . Table\Generated\WebhookTable::COLUMN_STATUS, + 'webhook.' . Table\Generated\WebhookTable::COLUMN_ENDPOINT, 'event.' . Table\Generated\EventTable::COLUMN_NAME, ]) - ->from('fusio_event_subscription', 'subscription') - ->innerJoin('subscription', 'fusio_event', 'event', 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_EVENT_ID . ' = event.' . Table\Generated\EventTable::COLUMN_ID) - ->orderBy('subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_ID, 'DESC') + ->from('fusio_webhook', 'webhook') + ->innerJoin('webhook', 'fusio_event', 'event', 'webhook.' . Table\Generated\WebhookTable::COLUMN_EVENT_ID . ' = event.' . Table\Generated\EventTable::COLUMN_ID) + ->orderBy('webhook.' . Table\Generated\WebhookTable::COLUMN_ID, 'DESC') ->where($condition->getExpression($this->connection->getDatabasePlatform())) ->setParameters($condition->getValues()) ->setFirstResult($startIndex) @@ -65,8 +65,8 @@ public function getCollection(QueryFilter $filter, ContextInterface $context) $countBuilder = $this->connection->createQueryBuilder() ->select(['COUNT(*) AS cnt']) - ->from('fusio_event_subscription', 'subscription') - ->innerJoin('subscription', 'fusio_event', 'event', 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_EVENT_ID . ' = event.' . Table\Generated\EventTable::COLUMN_ID) + ->from('fusio_webhook', 'webhook') + ->innerJoin('webhook', 'fusio_event', 'event', 'webhook.' . Table\Generated\WebhookTable::COLUMN_EVENT_ID . ' = event.' . Table\Generated\EventTable::COLUMN_ID) ->where($condition->getExpression($this->connection->getDatabasePlatform())) ->setParameters($condition->getValues()); @@ -77,49 +77,49 @@ public function getCollection(QueryFilter $filter, ContextInterface $context) 'startIndex' => $startIndex, 'itemsPerPage' => $count, 'entry' => $builder->doCollection($queryBuilder->getSQL(), $queryBuilder->getParameters(), [ - 'id' => $builder->fieldInteger(Table\Generated\EventSubscriptionTable::COLUMN_ID), - 'status' => $builder->fieldInteger(Table\Generated\EventSubscriptionTable::COLUMN_STATUS), + 'id' => $builder->fieldInteger(Table\Generated\WebhookTable::COLUMN_ID), + 'status' => $builder->fieldInteger(Table\Generated\WebhookTable::COLUMN_STATUS), 'event' => Table\Generated\EventTable::COLUMN_NAME, - 'endpoint' => Table\Generated\EventSubscriptionTable::COLUMN_ENDPOINT, + 'endpoint' => Table\Generated\WebhookTable::COLUMN_ENDPOINT, ]), ]; return $builder->build($definition); } - public function getEntity(int $subscriptionId, ContextInterface $context) + public function getEntity(int $webhookId, ContextInterface $context) { $condition = Condition::withAnd(); - $condition->equals('subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_ID, $subscriptionId); - $condition->equals('subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_USER_ID, $context->getUser()->getId()); + $condition->equals('webhook.' . Table\Generated\WebhookTable::COLUMN_ID, $webhookId); + $condition->equals('webhook.' . Table\Generated\WebhookTable::COLUMN_USER_ID, $context->getUser()->getId()); $condition->equals('event.' . Table\Generated\EventTable::COLUMN_TENANT_ID, $context->getTenantId()); $condition->equals('event.' . Table\Generated\EventTable::COLUMN_CATEGORY_ID, $context->getUser()->getCategoryId() ?: 1); $queryBuilder = $this->connection->createQueryBuilder() ->select([ - 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_ID, - 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_STATUS, - 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_ENDPOINT, + 'webhook.' . Table\Generated\WebhookTable::COLUMN_ID, + 'webhook.' . Table\Generated\WebhookTable::COLUMN_STATUS, + 'webhook.' . Table\Generated\WebhookTable::COLUMN_ENDPOINT, 'event.' . Table\Generated\EventTable::COLUMN_NAME, ]) - ->from('fusio_event_subscription', 'subscription') - ->innerJoin('subscription', 'fusio_event', 'event', 'subscription.' . Table\Generated\EventSubscriptionTable::COLUMN_EVENT_ID . ' = event.' . Table\Generated\EventTable::COLUMN_ID) + ->from('fusio_webhook', 'webhook') + ->innerJoin('webhook', 'fusio_event', 'event', 'webhook.' . Table\Generated\WebhookTable::COLUMN_EVENT_ID . ' = event.' . Table\Generated\EventTable::COLUMN_ID) ->where($condition->getExpression($this->connection->getDatabasePlatform())) ->setParameters($condition->getValues()); $builder = new Builder($this->connection); $definition = $builder->doEntity($queryBuilder->getSQL(), $queryBuilder->getParameters(), [ - 'id' => $builder->fieldInteger(Table\Generated\EventSubscriptionTable::COLUMN_ID), - 'status' => $builder->fieldInteger(Table\Generated\EventSubscriptionTable::COLUMN_STATUS), + 'id' => $builder->fieldInteger(Table\Generated\WebhookTable::COLUMN_ID), + 'status' => $builder->fieldInteger(Table\Generated\WebhookTable::COLUMN_STATUS), 'event' => Table\Generated\EventTable::COLUMN_NAME, - 'endpoint' => Table\Generated\EventSubscriptionTable::COLUMN_ENDPOINT, - 'responses' => $builder->doCollection([$this->getTable(Table\Event\Response::class), 'getAllBySubscription'], [new Reference(Table\Generated\EventSubscriptionTable::COLUMN_ID)], [ - 'status' => $builder->fieldInteger(Table\Generated\EventResponseTable::COLUMN_STATUS), - 'attempts' => $builder->fieldInteger(Table\Generated\EventResponseTable::COLUMN_ATTEMPTS), - 'code' => $builder->fieldInteger(Table\Generated\EventResponseTable::COLUMN_CODE), - 'body' => $builder->fieldInteger(Table\Generated\EventResponseTable::COLUMN_BODY), - 'executeDate' => $builder->fieldDateTime(Table\Generated\EventResponseTable::COLUMN_EXECUTE_DATE), + 'endpoint' => Table\Generated\WebhookTable::COLUMN_ENDPOINT, + 'responses' => $builder->doCollection([$this->getTable(Table\Webhook\Response::class), 'getAllByWebhook'], [new Reference(Table\Generated\WebhookTable::COLUMN_ID)], [ + 'status' => $builder->fieldInteger(Table\Generated\WebhookResponseTable::COLUMN_STATUS), + 'attempts' => $builder->fieldInteger(Table\Generated\WebhookResponseTable::COLUMN_ATTEMPTS), + 'code' => $builder->fieldInteger(Table\Generated\WebhookResponseTable::COLUMN_CODE), + 'body' => $builder->fieldInteger(Table\Generated\WebhookResponseTable::COLUMN_BODY), + 'executeDate' => $builder->fieldDateTime(Table\Generated\WebhookResponseTable::COLUMN_EXECUTE_DATE), ]), ]); diff --git a/src/Event/Event/Subscription/CreatedEvent.php b/src/Event/Webhook/CreatedEvent.php similarity index 75% rename from src/Event/Event/Subscription/CreatedEvent.php rename to src/Event/Webhook/CreatedEvent.php index 3ff7129e5..487cca71e 100644 --- a/src/Event/Event/Subscription/CreatedEvent.php +++ b/src/Event/Webhook/CreatedEvent.php @@ -18,11 +18,11 @@ * limitations under the License. */ -namespace Fusio\Impl\Event\Event\Subscription; +namespace Fusio\Impl\Event\Webhook; use Fusio\Impl\Authorization\UserContext; use Fusio\Impl\Event\EventAbstract; -use Fusio\Model\Backend\EventSubscriptionCreate; +use Fusio\Model\Backend\WebhookCreate; /** * CreatedEvent @@ -33,17 +33,17 @@ */ class CreatedEvent extends EventAbstract { - private EventSubscriptionCreate $subscription; + private WebhookCreate $webhook; - public function __construct(EventSubscriptionCreate $subscription, UserContext $context) + public function __construct(WebhookCreate $webhook, UserContext $context) { parent::__construct($context); - $this->subscription = $subscription; + $this->webhook = $webhook; } - public function getSubscription(): EventSubscriptionCreate + public function getWebhook(): WebhookCreate { - return $this->subscription; + return $this->webhook; } } diff --git a/src/Event/Event/Subscription/DeletedEvent.php b/src/Event/Webhook/DeletedEvent.php similarity index 81% rename from src/Event/Event/Subscription/DeletedEvent.php rename to src/Event/Webhook/DeletedEvent.php index 9aa89b374..acda4d8ba 100644 --- a/src/Event/Event/Subscription/DeletedEvent.php +++ b/src/Event/Webhook/DeletedEvent.php @@ -18,11 +18,11 @@ * limitations under the License. */ -namespace Fusio\Impl\Event\Event\Subscription; +namespace Fusio\Impl\Event\Webhook; use Fusio\Impl\Authorization\UserContext; use Fusio\Impl\Event\EventAbstract; -use Fusio\Impl\Table\Generated\EventSubscriptionRow; +use Fusio\Impl\Table\Generated\WebhookRow; /** * DeletedEvent @@ -33,16 +33,16 @@ */ class DeletedEvent extends EventAbstract { - private EventSubscriptionRow $existing; + private WebhookRow $existing; - public function __construct(EventSubscriptionRow $existing, UserContext $context) + public function __construct(WebhookRow $existing, UserContext $context) { parent::__construct($context); $this->existing = $existing; } - public function getExisting(): EventSubscriptionRow + public function getExisting(): WebhookRow { return $this->existing; } diff --git a/src/Event/Event/Subscription/UpdatedEvent.php b/src/Event/Webhook/UpdatedEvent.php similarity index 67% rename from src/Event/Event/Subscription/UpdatedEvent.php rename to src/Event/Webhook/UpdatedEvent.php index d2d175789..43cbd42c5 100644 --- a/src/Event/Event/Subscription/UpdatedEvent.php +++ b/src/Event/Webhook/UpdatedEvent.php @@ -18,12 +18,12 @@ * limitations under the License. */ -namespace Fusio\Impl\Event\Event\Subscription; +namespace Fusio\Impl\Event\Webhook; use Fusio\Impl\Authorization\UserContext; use Fusio\Impl\Event\EventAbstract; -use Fusio\Impl\Table\Generated\EventSubscriptionRow; -use Fusio\Model\Backend\EventSubscriptionUpdate; +use Fusio\Impl\Table\Generated\WebhookRow; +use Fusio\Model\Backend\WebhookUpdate; /** * UpdatedEvent @@ -34,23 +34,23 @@ */ class UpdatedEvent extends EventAbstract { - private EventSubscriptionUpdate $subscription; - private EventSubscriptionRow $existing; + private WebhookUpdate $webhook; + private WebhookRow $existing; - public function __construct(EventSubscriptionUpdate $subscription, EventSubscriptionRow $existing, UserContext $context) + public function __construct(WebhookUpdate $webhook, WebhookRow $existing, UserContext $context) { parent::__construct($context); - $this->subscription = $subscription; - $this->existing = $existing; + $this->webhook = $webhook; + $this->existing = $existing; } - public function getSubscription(): EventSubscriptionUpdate + public function getWebhook(): WebhookUpdate { - return $this->subscription; + return $this->webhook; } - public function getExisting(): EventSubscriptionRow + public function getExisting(): WebhookRow { return $this->existing; } diff --git a/src/EventListener/AuditListener.php b/src/EventListener/AuditListener.php index ebaaddd1e..ede4d2c74 100644 --- a/src/EventListener/AuditListener.php +++ b/src/EventListener/AuditListener.php @@ -245,35 +245,35 @@ public function onEventUpdate(Event\Event\UpdatedEvent $event): void ); } - public function onEventSubscriptionCreate(Event\Event\Subscription\CreatedEvent $event): void + public function onWebhookCreate(Event\Webhook\CreatedEvent $event): void { $this->log( $event->getContext(), - $event->getSubscription()->getId(), - 'event.subscription.create', - sprintf('Created event subscription %s', $event->getSubscription()->getEndpoint() ?? ''), - $event->getSubscription() + $event->getWebhook()->getId(), + 'webhook.create', + sprintf('Created webhook %s', $event->getWebhook()->getName() ?? ''), + $event->getWebhook() ); } - public function onEventSubscriptionDelete(Event\Event\Subscription\DeletedEvent $event): void + public function onWebhookDelete(Event\Webhook\DeletedEvent $event): void { $this->log( $event->getContext(), $event->getExisting()->getId(), - 'event.subscription.delete', - sprintf('Deleted event subscription %s', $event->getExisting()->getEndpoint()) + 'webhook.delete', + sprintf('Deleted webhook %s', $event->getExisting()->getEndpoint()) ); } - public function onEventSubscriptionUpdate(Event\Event\Subscription\UpdatedEvent $event): void + public function onWebhookUpdate(Event\Webhook\UpdatedEvent $event): void { $this->log( $event->getContext(), - $event->getSubscription()->getId(), - 'event.subscription.update', - sprintf('Updated event subscription %s', $event->getSubscription()->getEndpoint() ?? ''), - $event->getSubscription() + $event->getWebhook()->getId(), + 'webhook.update', + sprintf('Updated webhook %s', $event->getWebhook()->getName() ?? ''), + $event->getWebhook() ); } @@ -537,10 +537,6 @@ public static function getSubscribedEvents(): array Event\Event\DeletedEvent::class => 'onEventDelete', Event\Event\UpdatedEvent::class => 'onEventUpdate', - Event\Event\Subscription\CreatedEvent::class => 'onEventSubscriptionCreate', - Event\Event\Subscription\DeletedEvent::class => 'onEventSubscriptionDelete', - Event\Event\Subscription\UpdatedEvent::class => 'onEventSubscriptionUpdate', - Event\Plan\CreatedEvent::class => 'onPlanCreate', Event\Plan\DeletedEvent::class => 'onPlanDelete', Event\Plan\UpdatedEvent::class => 'onPlanUpdate', @@ -569,6 +565,10 @@ public static function getSubscribedEvents(): array Event\User\CreatedEvent::class => 'onUserCreate', Event\User\DeletedEvent::class => 'onUserDelete', Event\User\UpdatedEvent::class => 'onUserUpdate', + + Event\Webhook\CreatedEvent::class => 'onWebhookCreate', + Event\Webhook\DeletedEvent::class => 'onWebhookDelete', + Event\Webhook\UpdatedEvent::class => 'onWebhookUpdate', ]; } } diff --git a/src/Exception/UsageLimitExceededException.php b/src/Exception/UsageLimitExceededException.php new file mode 100644 index 000000000..6f90a2d26 --- /dev/null +++ b/src/Exception/UsageLimitExceededException.php @@ -0,0 +1,34 @@ + + * + * Copyright 2015-2023 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Exception; + +use PSX\Http\Exception\ForbiddenException; + +/** + * UsageLimitExceededException + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +class UsageLimitExceededException extends ForbiddenException +{ +} diff --git a/src/Installation/DataBag.php b/src/Installation/DataBag.php index 774443670..c93791727 100644 --- a/src/Installation/DataBag.php +++ b/src/Installation/DataBag.php @@ -64,8 +64,8 @@ public function __construct() 'fusio_app_scope' => [], 'fusio_token' => [], 'fusio_cronjob_error' => [], - 'fusio_event_subscription' => [], - 'fusio_event_response' => [], + 'fusio_webhook' => [], + 'fusio_webhook_response' => [], 'fusio_log_error' => [], 'fusio_plan_usage' => [], 'fusio_rate_allocation' => [], @@ -324,18 +324,6 @@ public function addEvent(string $category, string $name, string $description = ' ]; } - public function addEventResponse(int $subscription, ?string $executeDate = null, ?string $insertDate = null, ?string $tenantId = null): void - { - $this->data['fusio_event_response'][] = [ - 'subscription_id' => $subscription, - 'status' => 2, - 'code' => 200, - 'attempts' => 1, - 'execute_date' => (new \DateTime($executeDate ?? 'now'))->format('Y-m-d H:i:s'), - 'insert_date' => (new \DateTime($insertDate ?? 'now'))->format('Y-m-d H:i:s'), - ]; - } - public function addIdentity(string $app, string $name, string $icon, string $class, string $clientId, string $clientSecret, string $authorizationUri, string $tokenUri, string $userInfoUri, string $idProperty = 'id', string $nameProperty = 'name', string $emailProperty = 'email', ?string $insertDate = null, ?string $tenantId = null): void { $this->data['fusio_identity'][$name] = [ @@ -371,16 +359,30 @@ public function addIdentityRequest(string $identity, string $state, ?string $ins ]; } - public function addEventSubscription(string $event, string $user, string $endpoint, ?string $tenantId = null): void + public function addWebhook(string $event, string $user, string $name, string $endpoint, ?string $tenantId = null): void { - $this->data['fusio_event_subscription'][] = [ + $this->data['fusio_webhook'][$name] = [ + 'tenant_id' => $tenantId, 'event_id' => $this->getReference('fusio_event', $event, $tenantId), 'user_id' => $this->getReference('fusio_user', $user, $tenantId), 'status' => 1, + 'name' => $name, 'endpoint' => $endpoint ]; } + public function addWebhookResponse(int $webhookId, ?string $executeDate = null, ?string $insertDate = null): void + { + $this->data['fusio_webhook_response'][] = [ + 'webhook_id' => $webhookId, + 'status' => 2, + 'code' => 200, + 'attempts' => 1, + 'execute_date' => (new \DateTime($executeDate ?? 'now'))->format('Y-m-d H:i:s'), + 'insert_date' => (new \DateTime($insertDate ?? 'now'))->format('Y-m-d H:i:s'), + ]; + } + public function addLog(string $category, string $app, string $operation, ?string $tenantId = null): void { $this->data['fusio_log'][] = [ diff --git a/src/Installation/NewInstallation.php b/src/Installation/NewInstallation.php index db99b3b2c..867337b35 100644 --- a/src/Installation/NewInstallation.php +++ b/src/Installation/NewInstallation.php @@ -562,52 +562,6 @@ private static function getOperations(): array outgoing: Model\Backend\Dashboard::class, throws: [401 => Model\Common\Message::class, 500 => Model\Common\Message::class], ), - 'event.getAllSubscriptions' => new Operation( - action: Backend\Action\Event\Subscription\GetAll::class, - httpMethod: 'GET', - httpPath: '/event/subscription', - httpCode: 200, - outgoing: Model\Backend\EventSubscriptionCollection::class, - parameters: ['startIndex' => TypeFactory::getInteger(), 'count' => TypeFactory::getInteger(), 'search' => TypeFactory::getString()], - throws: [401 => Model\Common\Message::class, 500 => Model\Common\Message::class], - ), - 'event.createSubscription' => new Operation( - action: Backend\Action\Event\Subscription\Create::class, - httpMethod: 'POST', - httpPath: '/event/subscription', - httpCode: 201, - outgoing: Model\Common\Message::class, - incoming: Model\Backend\EventSubscriptionCreate::class, - throws: [400 => Model\Common\Message::class, 401 => Model\Common\Message::class, 500 => Model\Common\Message::class], - eventName: 'fusio.event.subscription.create', - ), - 'event.getSubscription' => new Operation( - action: Backend\Action\Event\Subscription\Get::class, - httpMethod: 'GET', - httpPath: '/event/subscription/$subscription_id<[0-9]+>', - httpCode: 200, - outgoing: Model\Backend\EventSubscription::class, - throws: [401 => Model\Common\Message::class, 404 => Model\Common\Message::class, 500 => Model\Common\Message::class], - ), - 'event.updateSubscription' => new Operation( - action: Backend\Action\Event\Subscription\Update::class, - httpMethod: 'PUT', - httpPath: '/event/subscription/$subscription_id<[0-9]+>', - httpCode: 200, - outgoing: Model\Common\Message::class, - incoming: Model\Backend\EventSubscriptionUpdate::class, - throws: [400 => Model\Common\Message::class, 401 => Model\Common\Message::class, 404 => Model\Common\Message::class, 410 => Model\Common\Message::class, 500 => Model\Common\Message::class], - eventName: 'fusio.event.subscription.update', - ), - 'event.deleteSubscription' => new Operation( - action: Backend\Action\Event\Subscription\Delete::class, - httpMethod: 'DELETE', - httpPath: '/event/subscription/$subscription_id<[0-9]+>', - httpCode: 200, - outgoing: Model\Common\Message::class, - throws: [401 => Model\Common\Message::class, 404 => Model\Common\Message::class, 410 => Model\Common\Message::class, 500 => Model\Common\Message::class], - eventName: 'fusio.event.subscription.delete', - ), 'event.getAll' => new Operation( action: Backend\Action\Event\GetAll::class, httpMethod: 'GET', @@ -1385,6 +1339,52 @@ private static function getOperations(): array throws: [401 => Model\Common\Message::class, 404 => Model\Common\Message::class, 410 => Model\Common\Message::class, 500 => Model\Common\Message::class], eventName: 'fusio.user.delete', ), + 'webhook.getAll' => new Operation( + action: Backend\Action\Webhook\GetAll::class, + httpMethod: 'GET', + httpPath: '/webhook', + httpCode: 200, + outgoing: Model\Backend\WebhookCollection::class, + parameters: ['startIndex' => TypeFactory::getInteger(), 'count' => TypeFactory::getInteger(), 'search' => TypeFactory::getString()], + throws: [401 => Model\Common\Message::class, 500 => Model\Common\Message::class], + ), + 'webhook.create' => new Operation( + action: Backend\Action\Webhook\Create::class, + httpMethod: 'POST', + httpPath: '/webhook', + httpCode: 201, + outgoing: Model\Common\Message::class, + incoming: Model\Backend\WebhookCreate::class, + throws: [400 => Model\Common\Message::class, 401 => Model\Common\Message::class, 500 => Model\Common\Message::class], + eventName: 'fusio.webhook.create', + ), + 'webhook.get' => new Operation( + action: Backend\Action\Webhook\Get::class, + httpMethod: 'GET', + httpPath: '/webhook/$webhook_id<[0-9]+>', + httpCode: 200, + outgoing: Model\Backend\Webhook::class, + throws: [401 => Model\Common\Message::class, 404 => Model\Common\Message::class, 500 => Model\Common\Message::class], + ), + 'webhook.update' => new Operation( + action: Backend\Action\Webhook\Update::class, + httpMethod: 'PUT', + httpPath: '/webhook/$webhook_id<[0-9]+>', + httpCode: 200, + outgoing: Model\Common\Message::class, + incoming: Model\Backend\WebhookUpdate::class, + throws: [400 => Model\Common\Message::class, 401 => Model\Common\Message::class, 404 => Model\Common\Message::class, 410 => Model\Common\Message::class, 500 => Model\Common\Message::class], + eventName: 'fusio.webhook.update', + ), + 'webhook.delete' => new Operation( + action: Backend\Action\Webhook\Delete::class, + httpMethod: 'DELETE', + httpPath: '/webhook/$webhook_id<[0-9]+>', + httpCode: 200, + outgoing: Model\Common\Message::class, + throws: [401 => Model\Common\Message::class, 404 => Model\Common\Message::class, 410 => Model\Common\Message::class, 500 => Model\Common\Message::class], + eventName: 'fusio.webhook.delete', + ), ], 'consumer' => [ 'app.getAll' => new Operation( @@ -1536,45 +1536,45 @@ private static function getOperations(): array parameters: ['startIndex' => TypeFactory::getInteger(), 'count' => TypeFactory::getInteger(), 'search' => TypeFactory::getString()], throws: [401 => Model\Common\Message::class, 500 => Model\Common\Message::class], ), - 'subscription.getAll' => new Operation( - action: Consumer\Action\Event\Subscription\GetAll::class, + 'webhook.getAll' => new Operation( + action: Consumer\Action\Webhook\GetAll::class, httpMethod: 'GET', - httpPath: '/subscription', + httpPath: '/webhook', httpCode: 200, - outgoing: Model\Consumer\EventSubscriptionCollection::class, + outgoing: Model\Consumer\WebhookCollection::class, parameters: ['startIndex' => TypeFactory::getInteger(), 'count' => TypeFactory::getInteger(), 'search' => TypeFactory::getString()], throws: [401 => Model\Common\Message::class, 500 => Model\Common\Message::class], ), - 'subscription.create' => new Operation( - action: Consumer\Action\Event\Subscription\Create::class, + 'webhook.create' => new Operation( + action: Consumer\Action\Webhook\Create::class, httpMethod: 'POST', - httpPath: '/subscription', + httpPath: '/webhook', httpCode: 201, outgoing: Model\Common\Message::class, - incoming: Model\Consumer\EventSubscriptionCreate::class, + incoming: Model\Consumer\WebhookCreate::class, throws: [400 => Model\Common\Message::class, 401 => Model\Common\Message::class, 500 => Model\Common\Message::class], ), - 'subscription.get' => new Operation( - action: Consumer\Action\Event\Subscription\Get::class, + 'webhook.get' => new Operation( + action: Consumer\Action\Webhook\Get::class, httpMethod: 'GET', - httpPath: '/subscription/$subscription_id<[0-9]+>', + httpPath: '/webhook/$webhook_id<[0-9]+>', httpCode: 200, - outgoing: Model\Consumer\EventSubscription::class, + outgoing: Model\Consumer\Webhook::class, throws: [401 => Model\Common\Message::class, 404 => Model\Common\Message::class, 410 => Model\Common\Message::class, 500 => Model\Common\Message::class], ), - 'subscription.update' => new Operation( - action: Consumer\Action\Event\Subscription\Update::class, + 'webhook.update' => new Operation( + action: Consumer\Action\Webhook\Update::class, httpMethod: 'PUT', - httpPath: '/subscription/$subscription_id<[0-9]+>', + httpPath: '/webhook/$webhook_id<[0-9]+>', httpCode: 200, outgoing: Model\Common\Message::class, - incoming: Model\Consumer\EventSubscriptionUpdate::class, + incoming: Model\Consumer\WebhookUpdate::class, throws: [400 => Model\Common\Message::class, 401 => Model\Common\Message::class, 404 => Model\Common\Message::class, 410 => Model\Common\Message::class, 500 => Model\Common\Message::class], ), - 'subscription.delete' => new Operation( - action: Consumer\Action\Event\Subscription\Delete::class, + 'webhook.delete' => new Operation( + action: Consumer\Action\Webhook\Delete::class, httpMethod: 'DELETE', - httpPath: '/subscription/$subscription_id<[0-9]+>', + httpPath: '/webhook/$webhook_id<[0-9]+>', httpCode: 200, outgoing: Model\Common\Message::class, throws: [401 => Model\Common\Message::class, 404 => Model\Common\Message::class, 410 => Model\Common\Message::class, 500 => Model\Common\Message::class], diff --git a/src/Messenger/TriggerEvent.php b/src/Messenger/TriggerEvent.php index d53e146a2..d03ef98d4 100644 --- a/src/Messenger/TriggerEvent.php +++ b/src/Messenger/TriggerEvent.php @@ -29,15 +29,22 @@ */ class TriggerEvent { + private ?string $tenantId; private string $eventName; private mixed $payload; - public function __construct(string $eventName, mixed $payload) + public function __construct(?string $tenantId, string $eventName, mixed $payload) { + $this->tenantId = $tenantId; $this->eventName = $eventName; $this->payload = $payload; } + public function getTenantId(): ?string + { + return $this->tenantId; + } + public function getEventName(): string { return $this->eventName; diff --git a/src/MessengerHandler/SendHttpRequestHandler.php b/src/MessengerHandler/SendHttpRequestHandler.php index 57f56ff1f..839a338ef 100644 --- a/src/MessengerHandler/SendHttpRequestHandler.php +++ b/src/MessengerHandler/SendHttpRequestHandler.php @@ -42,10 +42,10 @@ class SendHttpRequestHandler { public const MAX_ATTEMPTS = 3; - private Table\Event\Response $responseTable; + private Table\Webhook\Response $responseTable; private ClientInterface $httpClient; - public function __construct(Table\Event\Response $responseTable, ClientInterface $httpClient) + public function __construct(Table\Webhook\Response $responseTable, ClientInterface $httpClient) { $this->responseTable = $responseTable; $this->httpClient = $httpClient; @@ -54,17 +54,17 @@ public function __construct(Table\Event\Response $responseTable, ClientInterface public function __invoke(SendHttpRequest $httpRequest): void { $existing = $this->responseTable->find($httpRequest->getResponseId()); - if (!$existing instanceof Table\Generated\EventResponseRow) { + if (!$existing instanceof Table\Generated\WebhookResponseRow) { return; } - if ($existing->getStatus() !== Table\Event\Response::STATUS_PENDING) { + if ($existing->getStatus() !== Table\Webhook\Response::STATUS_PENDING) { return; } $headers = [ 'Content-Type' => 'application/json', - 'User-Agent' => Base::getUserAgent(), + 'User-Agent' => Base::getUserAgent(), ]; $request = new Request(Url::parse($httpRequest->getEndpoint()), 'POST', $headers, Parser::encode($httpRequest->getPayload())); @@ -74,14 +74,14 @@ public function __invoke(SendHttpRequest $httpRequest): void $attempts = $existing->getAttempts() + 1; if (($code >= 200 && $code < 400) || $code == 410) { - $status = Table\Event\Response::STATUS_DONE; + $status = Table\Webhook\Response::STATUS_DONE; } else { - $status = Table\Event\Response::STATUS_PENDING; + $status = Table\Webhook\Response::STATUS_PENDING; } // mark response as exceeded in case max attempts is reached if ($attempts >= self::MAX_ATTEMPTS) { - $status = Table\Event\Response::STATUS_EXCEEDED; + $status = Table\Webhook\Response::STATUS_EXCEEDED; } $existing->setStatus($status); @@ -91,7 +91,7 @@ public function __invoke(SendHttpRequest $httpRequest): void $existing->setExecuteDate(LocalDateTime::now()); $this->responseTable->update($existing); - if ($status === Table\Event\Response::STATUS_PENDING) { + if ($status === Table\Webhook\Response::STATUS_PENDING) { throw new \RuntimeException('Request is still pending'); } } diff --git a/src/MessengerHandler/TriggerEventHandler.php b/src/MessengerHandler/WebhookSendHandler.php similarity index 69% rename from src/MessengerHandler/TriggerEventHandler.php rename to src/MessengerHandler/WebhookSendHandler.php index ee653356a..3a8e82e07 100644 --- a/src/MessengerHandler/TriggerEventHandler.php +++ b/src/MessengerHandler/WebhookSendHandler.php @@ -28,47 +28,47 @@ use Symfony\Component\Messenger\MessageBusInterface; /** - * TriggerEventHandler + * WebhookEventHandler * * @author Christoph Kappestein * @license http://www.apache.org/licenses/LICENSE-2.0 * @link https://www.fusio-project.org */ #[AsMessageHandler] -class TriggerEventHandler +class WebhookSendHandler { + private Table\Webhook $webhookTable; + private Table\Webhook\Response $responseTable; private Table\Event $eventTable; - private Table\Event\Subscription $subscriptionTable; - private Table\Event\Response $responseTable; private MessageBusInterface $messageBus; - public function __construct(Table\Event $eventTable, Table\Event\Subscription $subscriptionTable, Table\Event\Response $responseTable, MessageBusInterface $messageBus) + public function __construct(Table\Webhook $webhookTable, Table\Webhook\Response $responseTable, Table\Event $eventTable, MessageBusInterface $messageBus) { - $this->eventTable = $eventTable; - $this->subscriptionTable = $subscriptionTable; + $this->webhookTable = $webhookTable; $this->responseTable = $responseTable; + $this->eventTable = $eventTable; $this->messageBus = $messageBus; } public function __invoke(TriggerEvent $event): void { - $existing = $this->eventTable->findOneByName($event->getEventName()); + $existing = $this->eventTable->findOneByTenantAndName($event->getTenantId(), $event->getEventName()); if (!$existing instanceof Table\Generated\EventRow) { return; } - $subscriptions = $this->subscriptionTable->getSubscriptionsForEvent($existing->getId()); - foreach ($subscriptions as $subscription) { - $row = new Table\Generated\EventResponseRow(); - $row->setSubscriptionId($subscription['id']); - $row->setStatus(Table\Event\Response::STATUS_PENDING); + $webhooks = $this->webhookTable->getWebhooksForEvent($existing->getId()); + foreach ($webhooks as $webhook) { + $row = new Table\Generated\WebhookResponseRow(); + $row->setWebhookId($webhook['id']); + $row->setStatus(Table\Webhook\Response::STATUS_PENDING); $row->setAttempts(0); $row->setInsertDate(LocalDateTime::now()); $this->responseTable->create($row); $responseId = $this->responseTable->getLastInsertId(); - $this->messageBus->dispatch(new SendHttpRequest($responseId, $subscription['endpoint'], $event->getPayload())); + $this->messageBus->dispatch(new SendHttpRequest($responseId, $webhook['endpoint'], $event->getPayload())); } } } diff --git a/src/Migrations/Version20230508210151.php b/src/Migrations/Version20230508210151.php index bc35542fd..9109add21 100644 --- a/src/Migrations/Version20230508210151.php +++ b/src/Migrations/Version20230508210151.php @@ -178,29 +178,6 @@ public function up(Schema $schema) : void $eventTable->addUniqueIndex(['tenant_id', 'name']); } - if (!$schema->hasTable('fusio_event_response')) { - $eventResponseTable = $schema->createTable('fusio_event_response'); - $eventResponseTable->addColumn('id', 'integer', ['autoincrement' => true]); - $eventResponseTable->addColumn('subscription_id', 'integer'); - $eventResponseTable->addColumn('status', 'integer'); - $eventResponseTable->addColumn('attempts', 'integer'); - $eventResponseTable->addColumn('code', 'integer', ['notnull' => false]); - $eventResponseTable->addColumn('body', 'text', ['notnull' => false]); - $eventResponseTable->addColumn('execute_date', 'datetime', ['notnull' => false]); - $eventResponseTable->addColumn('insert_date', 'datetime'); - $eventResponseTable->setPrimaryKey(['id']); - } - - if (!$schema->hasTable('fusio_event_subscription')) { - $eventSubscriptionTable = $schema->createTable('fusio_event_subscription'); - $eventSubscriptionTable->addColumn('id', 'integer', ['autoincrement' => true]); - $eventSubscriptionTable->addColumn('event_id', 'integer'); - $eventSubscriptionTable->addColumn('user_id', 'integer'); - $eventSubscriptionTable->addColumn('status', 'integer'); - $eventSubscriptionTable->addColumn('endpoint', 'string', ['length' => 255]); - $eventSubscriptionTable->setPrimaryKey(['id']); - } - if (!$schema->hasTable('fusio_identity')) { $identityTable = $schema->createTable('fusio_identity'); $identityTable->addColumn('id', 'integer', ['autoincrement' => true]); @@ -516,6 +493,31 @@ public function up(Schema $schema) : void $userScopeTable->addUniqueIndex(['user_id', 'scope_id']); } + if (!$schema->hasTable('fusio_webhook')) { + $webhookTable = $schema->createTable('fusio_webhook'); + $webhookTable->addColumn('id', 'integer', ['autoincrement' => true]); + $webhookTable->addColumn('tenant_id', 'string', ['length' => 64, 'notnull' => false, 'default' => null]); + $webhookTable->addColumn('event_id', 'integer'); + $webhookTable->addColumn('user_id', 'integer'); + $webhookTable->addColumn('status', 'integer'); + $webhookTable->addColumn('name', 'string', ['length' => 32]); + $webhookTable->addColumn('endpoint', 'string', ['length' => 255]); + $webhookTable->setPrimaryKey(['id']); + } + + if (!$schema->hasTable('fusio_webhook_response')) { + $webhookResponseTable = $schema->createTable('fusio_webhook_response'); + $webhookResponseTable->addColumn('id', 'integer', ['autoincrement' => true]); + $webhookResponseTable->addColumn('webhook_id', 'integer'); + $webhookResponseTable->addColumn('status', 'integer'); + $webhookResponseTable->addColumn('attempts', 'integer'); + $webhookResponseTable->addColumn('code', 'integer', ['notnull' => false]); + $webhookResponseTable->addColumn('body', 'text', ['notnull' => false]); + $webhookResponseTable->addColumn('execute_date', 'datetime', ['notnull' => false]); + $webhookResponseTable->addColumn('insert_date', 'datetime'); + $webhookResponseTable->setPrimaryKey(['id']); + } + if (isset($appTable)) { $appTable->addForeignKeyConstraint($schema->getTable('fusio_user'), ['user_id'], ['id'], [], 'app_user_id'); } @@ -530,15 +532,6 @@ public function up(Schema $schema) : void $tokenTable->addForeignKeyConstraint($schema->getTable('fusio_user'), ['user_id'], ['id'], [], 'app_token_user_id'); } - if (isset($eventResponseTable)) { - $eventResponseTable->addForeignKeyConstraint($schema->getTable('fusio_event_subscription'), ['subscription_id'], ['id'], [], 'event_response_subscription_id'); - } - - if (isset($eventSubscriptionTable)) { - $eventSubscriptionTable->addForeignKeyConstraint($schema->getTable('fusio_event'), ['event_id'], ['id'], [], 'event_subscription_event_id'); - $eventSubscriptionTable->addForeignKeyConstraint($schema->getTable('fusio_user'), ['user_id'], ['id'], [], 'event_subscription_user_id'); - } - if (isset($identityRequestTable)) { $identityRequestTable->addForeignKeyConstraint($schema->getTable('fusio_identity'), ['identity_id'], ['id'], [], 'identity_request_identity_id'); } @@ -577,6 +570,15 @@ public function up(Schema $schema) : void $userScopeTable->addForeignKeyConstraint($schema->getTable('fusio_scope'), ['scope_id'], ['id'], [], 'user_scope_scope_id'); $userScopeTable->addForeignKeyConstraint($schema->getTable('fusio_user'), ['user_id'], ['id'], [], 'user_scope_user_id'); } + + if (isset($webhookTable)) { + $webhookTable->addForeignKeyConstraint($schema->getTable('fusio_event'), ['event_id'], ['id'], [], 'webhook_event_id'); + $webhookTable->addForeignKeyConstraint($schema->getTable('fusio_user'), ['user_id'], ['id'], [], 'webhook_user_id'); + } + + if (isset($webhookResponseTable)) { + $webhookResponseTable->addForeignKeyConstraint($schema->getTable('fusio_webhook'), ['webhook_id'], ['id'], [], 'webhook_response_webhook_id'); + } } public function down(Schema $schema) : void @@ -591,8 +593,6 @@ public function down(Schema $schema) : void $schema->dropTable('fusio_cronjob'); $schema->dropTable('fusio_cronjob_error'); $schema->dropTable('fusio_event'); - $schema->dropTable('fusio_event_response'); - $schema->dropTable('fusio_event_subscription'); $schema->dropTable('fusio_identity'); $schema->dropTable('fusio_identity_request'); $schema->dropTable('fusio_log'); @@ -611,6 +611,8 @@ public function down(Schema $schema) : void $schema->dropTable('fusio_user'); $schema->dropTable('fusio_user_grant'); $schema->dropTable('fusio_user_scope'); + $schema->dropTable('fusio_webhook'); + $schema->dropTable('fusio_webhook_response'); } /** diff --git a/src/Migrations/Version20230922194158.php b/src/Migrations/Version20230922194158.php index 14ca6f806..da5888333 100644 --- a/src/Migrations/Version20230922194158.php +++ b/src/Migrations/Version20230922194158.php @@ -27,12 +27,14 @@ public function up(Schema $schema): void $schema->dropTable('fusio_event_trigger'); } - $eventResponseTable = $schema->getTable('fusio_event_response'); - if (!$eventResponseTable->hasColumn('body')) { - $eventResponseTable->removeForeignKey('event_response_trigger_id'); - $eventResponseTable->dropColumn('trigger_id'); - $eventResponseTable->dropColumn('error'); - $eventResponseTable->addColumn('body', 'text', ['notnull' => false]); + if ($schema->hasTable('fusio_event_response')) { + $eventResponseTable = $schema->getTable('fusio_event_response'); + if (!$eventResponseTable->hasColumn('body')) { + $eventResponseTable->removeForeignKey('event_response_trigger_id'); + $eventResponseTable->dropColumn('trigger_id'); + $eventResponseTable->dropColumn('error'); + $eventResponseTable->addColumn('body', 'text', ['notnull' => false]); + } } } diff --git a/src/Migrations/Version20240121100724.php b/src/Migrations/Version20240121100724.php index 1598ee079..7fa0c048e 100644 --- a/src/Migrations/Version20240121100724.php +++ b/src/Migrations/Version20240121100724.php @@ -35,6 +35,24 @@ public function up(Schema $schema): void $schema->renameTable('fusio_app_token', 'fusio_token'); } + + if ($schema->hasTable('fusio_event_subscription')) { + $webhookTable = $schema->getTable('fusio_event_subscription'); + $webhookTable->addColumn('tenant_id', 'string', ['length' => 64, 'notnull' => false, 'default' => null]); + $webhookTable->addColumn('name', 'string', ['length' => 32]); + $webhookTable->addIndex(['tenant_id']); + + $schema->renameTable('fusio_event_subscription', 'fusio_webhook'); + } + + if ($schema->hasTable('fusio_event_response')) { + $webhookResponseTable = $schema->getTable('fusio_event_response'); + $webhookResponseTable->addColumn('webhook_id', 'integer'); + $webhookResponseTable->dropColumn('subscription_id'); + $webhookResponseTable->addForeignKeyConstraint($schema->getTable('fusio_webhook'), ['webhook_id'], ['id'], [], 'webhook_response_webhook_id'); + + $schema->renameTable('fusio_event_response', 'fusio_webhook_response'); + } } public function down(Schema $schema): void diff --git a/src/Service/Action.php b/src/Service/Action.php index 25a211ddc..af7efb4c7 100644 --- a/src/Service/Action.php +++ b/src/Service/Action.php @@ -61,7 +61,7 @@ public function __construct(Table\Action $actionTable, Factory\ActionInterface $ public function create(int $categoryId, ActionCreate $action, UserContext $context): int { - $this->validator->assert($action); + $this->validator->assert($action, $context->getTenantId()); $name = $action->getName(); $class = $action->getClass(); @@ -118,7 +118,7 @@ public function update(string $actionId, ActionUpdate $action, UserContext $cont throw new StatusCode\GoneException('Action was deleted'); } - $this->validator->assert($action, $existing); + $this->validator->assert($action, $context->getTenantId(), $existing); $name = $action->getName() ?? $existing->getName(); $class = $action->getClass() ?? $existing->getClass(); diff --git a/src/Service/Action/Validator.php b/src/Service/Action/Validator.php index d1c9ddaaf..e207df6e9 100644 --- a/src/Service/Action/Validator.php +++ b/src/Service/Action/Validator.php @@ -21,6 +21,7 @@ namespace Fusio\Impl\Service\Action; use Fusio\Impl\Service\System\FrameworkConfig; +use Fusio\Impl\Service\Tenant\UsageLimiter; use Fusio\Impl\Table; use Fusio\Impl\Table\Generated\ActionRow; use Fusio\Model\Backend\Action; @@ -37,20 +38,24 @@ class Validator { private Table\Action $actionTable; private FrameworkConfig $frameworkConfig; + private UsageLimiter $usageLimiter; - public function __construct(Table\Action $actionTable, FrameworkConfig $frameworkConfig) + public function __construct(Table\Action $actionTable, FrameworkConfig $frameworkConfig, UsageLimiter $usageLimiter) { $this->actionTable = $actionTable; $this->frameworkConfig = $frameworkConfig; + $this->usageLimiter = $usageLimiter; } - public function assert(Action $action, ?ActionRow $existing = null): void + public function assert(Action $action, ?string $tenantId, ?ActionRow $existing = null): void { + $this->usageLimiter->assertActionCount($tenantId); + $this->assertExcluded($action); $name = $action->getName(); if ($name !== null) { - $this->assertName($name, $existing); + $this->assertName($name, $tenantId, $existing); } else { if ($existing === null) { throw new StatusCode\BadRequestException('Action name must not be empty'); @@ -67,13 +72,13 @@ public function assert(Action $action, ?ActionRow $existing = null): void } } - private function assertName(string $name, ?ActionRow $existing = null): void + private function assertName(string $name, ?string $tenantId, ?ActionRow $existing = null): void { if (empty($name) || !preg_match('/^[a-zA-Z0-9\\-\\_]{3,255}$/', $name)) { throw new StatusCode\BadRequestException('Invalid action name'); } - if (($existing === null || $name !== $existing->getName()) && $this->actionTable->findOneByName($name)) { + if (($existing === null || $name !== $existing->getName()) && $this->actionTable->findOneByTenantAndName($tenantId, $name)) { throw new StatusCode\BadRequestException('Action already exists'); } } diff --git a/src/Service/App.php b/src/Service/App.php index 299eb04bc..9282bb623 100644 --- a/src/Service/App.php +++ b/src/Service/App.php @@ -58,7 +58,7 @@ public function __construct(Table\App $appTable, Table\Scope $scopeTable, Table\ public function create(AppCreate $app, UserContext $context): int { - $this->validator->assert($app); + $this->validator->assert($app, $context->getTenantId()); // parse parameters $parameters = $app->getParameters(); @@ -117,7 +117,7 @@ public function update(string $appId, AppUpdate $app, UserContext $context): int throw new StatusCode\GoneException('App was deleted'); } - $this->validator->assert($app, $existing); + $this->validator->assert($app, $context->getTenantId(), $existing); // parse parameters $parameters = $app->getParameters(); diff --git a/src/Service/App/Validator.php b/src/Service/App/Validator.php index 47b09a959..0931406b1 100644 --- a/src/Service/App/Validator.php +++ b/src/Service/App/Validator.php @@ -20,6 +20,7 @@ namespace Fusio\Impl\Service\App; +use Fusio\Impl\Service\Tenant\UsageLimiter; use Fusio\Impl\Table; use Fusio\Model\Backend\App; use PSX\Http\Exception as StatusCode; @@ -36,18 +37,22 @@ class Validator { private Table\App $appTable; private Table\User $userTable; + private UsageLimiter $usageLimiter; - public function __construct(Table\App $appTable, Table\User $userTable) + public function __construct(Table\App $appTable, Table\User $userTable, UsageLimiter $usageLimiter) { $this->appTable = $appTable; $this->userTable = $userTable; + $this->usageLimiter = $usageLimiter; } - public function assert(App $app, ?Table\Generated\AppRow $existing = null): void + public function assert(App $app, ?string $tenantId, ?Table\Generated\AppRow $existing = null): void { + $this->usageLimiter->assertAppCount($tenantId); + $userId = $app->getUserId(); if ($userId !== null) { - $this->assertUser($userId); + $this->assertUser($userId, $tenantId); } else { if ($existing === null) { throw new StatusCode\BadRequestException('App name must not be empty'); @@ -58,7 +63,7 @@ public function assert(App $app, ?Table\Generated\AppRow $existing = null): void $name = $app->getName(); if ($name !== null) { - $this->assertName($name, $userId, $existing); + $this->assertName($name, $tenantId, $userId, $existing); } else { if ($existing === null) { throw new StatusCode\BadRequestException('App name must not be empty'); @@ -66,15 +71,15 @@ public function assert(App $app, ?Table\Generated\AppRow $existing = null): void } } - private function assertUser(int $userId): void + private function assertUser(int $userId, ?string $tenantId): void { - $user = $this->userTable->find($userId); + $user = $this->userTable->findOneByTenantAndId($tenantId, $userId); if (empty($user)) { throw new StatusCode\BadRequestException('Provided user id does not exist'); } } - private function assertName(string $name, int $userId, ?Table\Generated\AppRow $existing = null): void + private function assertName(string $name, ?string $tenantId, int $userId, ?Table\Generated\AppRow $existing = null): void { if (empty($name) || !preg_match('/^[a-zA-Z0-9\\-\\_]{3,64}$/', $name)) { throw new StatusCode\BadRequestException('Invalid action name'); @@ -82,6 +87,7 @@ private function assertName(string $name, int $userId, ?Table\Generated\AppRow $ if ($existing === null || $name !== $existing->getName()) { $condition = Condition::withAnd(); + $condition->equals(Table\Generated\AppTable::COLUMN_TENANT_ID, $tenantId); $condition->equals(Table\Generated\AppTable::COLUMN_USER_ID, $userId); $condition->notEquals(Table\Generated\AppTable::COLUMN_STATUS, Table\App::STATUS_DELETED); $condition->equals(Table\Generated\AppTable::COLUMN_NAME, $name); diff --git a/src/Service/Category.php b/src/Service/Category.php index fa5c2cb31..f10d0baf8 100644 --- a/src/Service/Category.php +++ b/src/Service/Category.php @@ -53,7 +53,7 @@ public function __construct(Table\Category $categoryTable, Category\Validator $v public function create(CategoryCreate $category, UserContext $context): int { - $this->validator->assert($category); + $this->validator->assert($category, $context->getTenantId()); try { $this->categoryTable->beginTransaction(); @@ -91,7 +91,7 @@ public function update(string $categoryId, CategoryUpdate $category, UserContext throw new StatusCode\GoneException('Category was deleted'); } - $this->validator->assert($category, $existing); + $this->validator->assert($category, $context->getTenantId(), $existing); try { $this->categoryTable->beginTransaction(); diff --git a/src/Service/Category/Validator.php b/src/Service/Category/Validator.php index a0c4d05b9..afd099745 100644 --- a/src/Service/Category/Validator.php +++ b/src/Service/Category/Validator.php @@ -20,6 +20,7 @@ namespace Fusio\Impl\Service\Category; +use Fusio\Impl\Service\Tenant\UsageLimiter; use Fusio\Impl\Table; use Fusio\Model\Backend\Category; use PSX\Http\Exception as StatusCode; @@ -35,29 +36,33 @@ class Validator { private Table\Category $categoryTable; + private UsageLimiter $usageLimiter; - public function __construct(Table\Category $categoryTable) + public function __construct(Table\Category $categoryTable, UsageLimiter $usageLimiter) { $this->categoryTable = $categoryTable; + $this->usageLimiter = $usageLimiter; } - public function assert(Category $category, ?Table\Generated\CategoryRow $existing = null): void + public function assert(Category $category, ?string $tenantId, ?Table\Generated\CategoryRow $existing = null): void { + $this->usageLimiter->assertCategoryCount($tenantId); + $name = $category->getName(); if ($name !== null) { - $this->assertName($name, $existing); + $this->assertName($name, $tenantId, $existing); } elseif ($existing === null) { throw new StatusCode\BadRequestException('Category name must not be empty'); } } - private function assertName(string $name, ?Table\Generated\CategoryRow $existing = null): void + private function assertName(string $name, ?string $tenantId, ?Table\Generated\CategoryRow $existing = null): void { if (empty($name) || !preg_match('/^[a-zA-Z0-9\\-\\_]{3,255}$/', $name)) { throw new StatusCode\BadRequestException('Invalid category name'); } - if (($existing === null || $name !== $existing->getName()) && $this->categoryTable->findOneByName($name)) { + if (($existing === null || $name !== $existing->getName()) && $this->categoryTable->findOneByTenantAndName($tenantId, $name)) { throw new StatusCode\BadRequestException('Category already exists'); } } diff --git a/src/Service/Connection.php b/src/Service/Connection.php index 157c8e287..4b321ccf9 100644 --- a/src/Service/Connection.php +++ b/src/Service/Connection.php @@ -66,7 +66,7 @@ public function __construct(Table\Connection $connectionTable, Connection\Valida public function create(ConnectionCreate $connection, UserContext $context): int { - $this->validator->assert($connection); + $this->validator->assert($connection, $context->getTenantId()); $name = $connection->getName(); $class = $connection->getClass(); @@ -129,7 +129,7 @@ public function update(string $connectionId, ConnectionUpdate $connection, UserC throw new StatusCode\GoneException('Connection was deleted'); } - $this->validator->assert($connection, $existing); + $this->validator->assert($connection, $context->getTenantId(), $existing); $class = $connection->getClass(); diff --git a/src/Service/Connection/Validator.php b/src/Service/Connection/Validator.php index 3486d509e..5908960bf 100644 --- a/src/Service/Connection/Validator.php +++ b/src/Service/Connection/Validator.php @@ -21,6 +21,7 @@ namespace Fusio\Impl\Service\Connection; use Fusio\Impl\Service\System\FrameworkConfig; +use Fusio\Impl\Service\Tenant\UsageLimiter; use Fusio\Impl\Table; use Fusio\Model\Backend\Connection; use PSX\Http\Exception as StatusCode; @@ -36,32 +37,36 @@ class Validator { private Table\Connection $connectionTable; private FrameworkConfig $frameworkConfig; + private UsageLimiter $usageLimiter; - public function __construct(Table\Connection $connectionTable, FrameworkConfig $frameworkConfig) + public function __construct(Table\Connection $connectionTable, FrameworkConfig $frameworkConfig, UsageLimiter $usageLimiter) { $this->connectionTable = $connectionTable; $this->frameworkConfig = $frameworkConfig; + $this->usageLimiter = $usageLimiter; } - public function assert(Connection $connection, ?Table\Generated\ConnectionRow $existing = null): void + public function assert(Connection $connection, ?string $tenantId, ?Table\Generated\ConnectionRow $existing = null): void { + $this->usageLimiter->assertConnectionCount($tenantId); + $this->assertExcluded($connection); $name = $connection->getName(); if ($name !== null) { - $this->assertName($name, $existing); + $this->assertName($name, $tenantId, $existing); } elseif ($existing === null) { throw new StatusCode\BadRequestException('Connection name must not be empty'); } } - private function assertName(string $name, ?Table\Generated\ConnectionRow $existing = null): void + private function assertName(string $name, ?string $tenantId, ?Table\Generated\ConnectionRow $existing = null): void { if (empty($name) || !preg_match('/^[a-zA-Z0-9\\-\\_]{3,255}$/', $name)) { throw new StatusCode\BadRequestException('Invalid connection name'); } - if (($existing === null || $name !== $existing->getName()) && $this->connectionTable->findOneByName($name)) { + if (($existing === null || $name !== $existing->getName()) && $this->connectionTable->findOneByTenantAndName($tenantId, $name)) { throw new StatusCode\BadRequestException('Connection already exists'); } } diff --git a/src/Service/Consumer/App.php b/src/Service/Consumer/App.php index bdb40c316..9dca847f8 100644 --- a/src/Service/Consumer/App.php +++ b/src/Service/Consumer/App.php @@ -30,7 +30,7 @@ use PSX\Sql\Condition; /** - * Developer + * App * * @author Christoph Kappestein * @license http://www.apache.org/licenses/LICENSE-2.0 @@ -79,9 +79,9 @@ public function create(AppCreate $app, UserContext $context): int return $this->appService->create($backendApp, $context); } - public function update(int $appId, AppUpdate $app, UserContext $context): int + public function update(string $appId, AppUpdate $app, UserContext $context): int { - $existing = $this->appTable->find($appId); + $existing = $this->appTable->findOneByIdentifier($context->getTenantId(), $appId); if (empty($existing)) { throw new StatusCode\NotFoundException('Could not find app'); } @@ -104,23 +104,21 @@ public function update(int $appId, AppUpdate $app, UserContext $context): int $backendApp->setUrl($app->getUrl()); $backendApp->setScopes($scopes); - return $this->appService->update((string) $appId, $backendApp, $context); + return $this->appService->update((string) $existing->getId(), $backendApp, $context); } - public function delete(int $appId, UserContext $context): int + public function delete(string $appId, UserContext $context): int { - $userId = $context->getUserId(); - $app = $this->appTable->find($appId); - - if (empty($app)) { + $existing = $this->appTable->findOneByIdentifier($context->getTenantId(), $appId); + if (empty($existing)) { throw new StatusCode\NotFoundException('Could not find app'); } - if ($app->getUserId() != $userId) { + if ($existing->getUserId() != $context->getUserId()) { throw new StatusCode\BadRequestException('App does not belong to the user'); } - return $this->appService->delete((string) $appId, $context); + return $this->appService->delete((string) $existing->getId(), $context); } protected function getValidUserScopes(?string $tenantId, int $userId, ?array $scopes): array diff --git a/src/Service/Consumer/Subscription.php b/src/Service/Consumer/Subscription.php deleted file mode 100644 index e6fd94f1b..000000000 --- a/src/Service/Consumer/Subscription.php +++ /dev/null @@ -1,124 +0,0 @@ - - * - * Copyright 2015-2023 Christoph Kappestein - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace Fusio\Impl\Service\Consumer; - -use Fusio\Impl\Authorization\UserContext; -use Fusio\Impl\Service; -use Fusio\Impl\Table; -use Fusio\Model; -use Fusio\Model\Consumer\EventSubscriptionCreate; -use Fusio\Model\Consumer\EventSubscriptionUpdate; -use PSX\Http\Exception as StatusCode; -use PSX\Sql\Condition; - -/** - * Subscription - * - * @author Christoph Kappestein - * @license http://www.apache.org/licenses/LICENSE-2.0 - * @link https://www.fusio-project.org - */ -class Subscription -{ - private Service\Event\Subscription $subscriptionService; - private Service\Config $configService; - private Table\Event\Subscription $subscriptionTable; - private Table\Event $eventTable; - - public function __construct(Service\Event\Subscription $subscriptionService, Service\Config $configService, Table\Event\Subscription $subscriptionTable, Table\Event $eventTable) - { - $this->subscriptionService = $subscriptionService; - $this->configService = $configService; - $this->subscriptionTable = $subscriptionTable; - $this->eventTable = $eventTable; - } - - public function create(EventSubscriptionCreate $subscription, UserContext $context): int - { - // check max subscription count - $count = $this->subscriptionTable->getSubscriptionCount($context->getUserId()); - if ($count > $this->configService->getValue('consumer_subscription')) { - throw new StatusCode\BadRequestException('Max subscription count reached'); - } - - // check whether the event exists - $condition = Condition::withAnd(); - $condition->equals(Table\Generated\EventTable::COLUMN_NAME, $subscription->getEvent()); - - $event = $this->eventTable->findOneBy($condition); - if (empty($event)) { - throw new StatusCode\BadRequestException('Event does not exist'); - } - - $this->assertUrl($subscription->getEndpoint()); - - $backendSubscription = new Model\Backend\EventSubscriptionCreate(); - $backendSubscription->setUserId($context->getUserId()); - $backendSubscription->setEventId($event->getId()); - $backendSubscription->setEndpoint($subscription->getEndpoint()); - - return $this->subscriptionService->create($backendSubscription, $context); - } - - public function update(int $subscriptionId, EventSubscriptionUpdate $subscription, UserContext $context): int - { - $existing = $this->subscriptionTable->find($subscriptionId); - if (empty($existing)) { - throw new StatusCode\NotFoundException('Could not find subscription'); - } - - if ($existing->getUserId() != $context->getUserId()) { - throw new StatusCode\BadRequestException('Subscription does not belong to the user'); - } - - $this->assertUrl($subscription->getEndpoint()); - - $backendSubscription = new Model\Backend\EventSubscriptionUpdate(); - $backendSubscription->setEndpoint($subscription->getEndpoint()); - - return $this->subscriptionService->update($subscriptionId, $backendSubscription, $context); - } - - public function delete(int $subscriptionId, UserContext $context): int - { - $subscription = $this->subscriptionTable->find($subscriptionId); - if (empty($subscription)) { - throw new StatusCode\NotFoundException('Could not find subscription'); - } - - if ($subscription->getUserId() != $context->getUserId()) { - throw new StatusCode\BadRequestException('Subscription does not belong to the user'); - } - - return $this->subscriptionService->delete($subscriptionId, $context); - } - - private function assertUrl(?string $url): void - { - if (empty($url)) { - throw new StatusCode\BadRequestException('The endpoint contains no value'); - } - - if (!filter_var($url, FILTER_VALIDATE_URL)) { - throw new StatusCode\BadRequestException('The endpoint has an invalid url format'); - } - } -} diff --git a/src/Service/Consumer/Token.php b/src/Service/Consumer/Token.php new file mode 100644 index 000000000..67fc023cc --- /dev/null +++ b/src/Service/Consumer/Token.php @@ -0,0 +1,33 @@ + + * + * Copyright 2015-2023 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Service\Consumer; + +/** + * Token + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +class Token +{ + // @TODO create customized token +} diff --git a/src/Service/Consumer/Webhook.php b/src/Service/Consumer/Webhook.php new file mode 100644 index 000000000..0e4d7da51 --- /dev/null +++ b/src/Service/Consumer/Webhook.php @@ -0,0 +1,112 @@ + + * + * Copyright 2015-2023 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Service\Consumer; + +use Fusio\Impl\Authorization\UserContext; +use Fusio\Impl\Service; +use Fusio\Impl\Table; +use Fusio\Model; +use Fusio\Model\Consumer\WebhookCreate; +use Fusio\Model\Consumer\WebhookUpdate; +use PSX\Http\Exception as StatusCode; +use PSX\Sql\Condition; + +/** + * Webhook + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +class Webhook +{ + private Service\Webhook $webhookService; + private Service\Config $configService; + private Table\Webhook $webhookTable; + private Table\Event $eventTable; + + public function __construct(Service\Webhook $webhookService, Service\Config $configService, Table\Webhook $webhookTable, Table\Event $eventTable) + { + $this->webhookService = $webhookService; + $this->configService = $configService; + $this->webhookTable = $webhookTable; + $this->eventTable = $eventTable; + } + + public function create(WebhookCreate $webhook, UserContext $context): int + { + // check max webhook count + $count = $this->webhookTable->getWebhookCount($context->getTenantId(), $context->getUserId()); + if ($count > $this->configService->getValue('consumer_subscription')) { + throw new StatusCode\BadRequestException('Max webhook count reached'); + } + + // check whether the event exists + $condition = Condition::withAnd(); + $condition->equals(Table\Generated\EventTable::COLUMN_TENANT_ID, $context->getTenantId()); + $condition->equals(Table\Generated\EventTable::COLUMN_NAME, $webhook->getEvent()); + + $event = $this->eventTable->findOneBy($condition); + if (empty($event)) { + throw new StatusCode\BadRequestException('Event does not exist'); + } + + $backendWebhook = new Model\Backend\WebhookCreate(); + $backendWebhook->setUserId($context->getUserId()); + $backendWebhook->setEventId($event->getId()); + $backendWebhook->setName($webhook->getName()); + $backendWebhook->setEndpoint($webhook->getEndpoint()); + + return $this->webhookService->create($backendWebhook, $context); + } + + public function update(string $webhookId, WebhookUpdate $webhook, UserContext $context): int + { + $existing = $this->webhookTable->findOneByIdentifier($context->getTenantId(), $webhookId); + if (empty($existing)) { + throw new StatusCode\NotFoundException('Could not find webhook'); + } + + if ($existing->getUserId() != $context->getUserId()) { + throw new StatusCode\BadRequestException('Webhook does not belong to the user'); + } + + $backendWebhook = new Model\Backend\WebhookUpdate(); + $backendWebhook->setName($webhook->getName()); + $backendWebhook->setEndpoint($webhook->getEndpoint()); + + return $this->webhookService->update((string) $existing->getId(), $backendWebhook, $context); + } + + public function delete(string $webhookId, UserContext $context): int + { + $existing = $this->webhookTable->findOneByIdentifier($context->getTenantId(), $webhookId); + if (empty($existing)) { + throw new StatusCode\NotFoundException('Could not find webhook'); + } + + if ($existing->getUserId() != $context->getUserId()) { + throw new StatusCode\BadRequestException('Webhook does not belong to the user'); + } + + return $this->webhookService->delete((string) $existing->getId(), $context); + } +} diff --git a/src/Service/Cronjob.php b/src/Service/Cronjob.php index f022b73fd..08883927e 100644 --- a/src/Service/Cronjob.php +++ b/src/Service/Cronjob.php @@ -52,7 +52,7 @@ public function __construct(Table\Cronjob $cronjobTable, Cronjob\Validator $vali public function create(int $categoryId, CronjobCreate $cronjob, UserContext $context): int { - $this->validator->assert($cronjob); + $this->validator->assert($cronjob, $context->getTenantId()); // create cronjob try { @@ -94,7 +94,7 @@ public function update(string $cronjobId, CronjobUpdate $cronjob, UserContext $c throw new StatusCode\GoneException('Cronjob was deleted'); } - $this->validator->assert($cronjob, $existing); + $this->validator->assert($cronjob, $context->getTenantId(), $existing); $existing->setName($cronjob->getName() ?? $existing->getName()); $existing->setCron($cronjob->getCron() ?? $existing->getCron()); diff --git a/src/Service/Cronjob/Validator.php b/src/Service/Cronjob/Validator.php index 8daf48fdb..2aa2d9da4 100644 --- a/src/Service/Cronjob/Validator.php +++ b/src/Service/Cronjob/Validator.php @@ -21,6 +21,7 @@ namespace Fusio\Impl\Service\Cronjob; use Cron\CronExpression; +use Fusio\Impl\Service\Tenant\UsageLimiter; use Fusio\Impl\Table; use Fusio\Model\Backend\Cronjob; use PSX\Http\Exception as StatusCode; @@ -35,17 +36,21 @@ class Validator { private Table\Cronjob $cronjobTable; + private UsageLimiter $usageLimiter; - public function __construct(Table\Cronjob $cronjobTable) + public function __construct(Table\Cronjob $cronjobTable, UsageLimiter $usageLimiter) { $this->cronjobTable = $cronjobTable; + $this->usageLimiter = $usageLimiter; } - public function assert(Cronjob $cronjob, ?Table\Generated\CronjobRow $existing = null): void + public function assert(Cronjob $cronjob, ?string $tenantId, ?Table\Generated\CronjobRow $existing = null): void { + $this->usageLimiter->assertCronjobCount($tenantId); + $name = $cronjob->getName(); if ($name !== null) { - $this->assertName($name, $existing); + $this->assertName($name, $tenantId, $existing); } else { if ($existing === null) { throw new StatusCode\BadRequestException('Cronjob name must not be empty'); @@ -62,13 +67,13 @@ public function assert(Cronjob $cronjob, ?Table\Generated\CronjobRow $existing = } } - private function assertName(string $name, ?Table\Generated\CronjobRow $existing = null): void + private function assertName(string $name, ?string $tenantId, ?Table\Generated\CronjobRow $existing = null): void { if (empty($name) || !preg_match('/^[a-zA-Z0-9\\-\\_]{3,255}$/', $name)) { throw new StatusCode\BadRequestException('Invalid connection name'); } - if (($existing === null || $name !== $existing->getName()) && $this->cronjobTable->findOneByName($name)) { + if (($existing === null || $name !== $existing->getName()) && $this->cronjobTable->findOneByTenantAndName($tenantId, $name)) { throw new StatusCode\BadRequestException('Connection already exists'); } } diff --git a/src/Service/Event.php b/src/Service/Event.php index 1901c4416..0c7392386 100644 --- a/src/Service/Event.php +++ b/src/Service/Event.php @@ -54,7 +54,7 @@ public function __construct(Table\Event $eventTable, Event\Validator $validator, public function create(int $categoryId, EventCreate $event, UserContext $context): int { - $this->validator->assert($event); + $this->validator->assert($event, $context->getTenantId()); // create event try { @@ -96,7 +96,7 @@ public function update(string $eventId, EventUpdate $event, UserContext $context throw new StatusCode\GoneException('Event was deleted'); } - $this->validator->assert($event, $existing); + $this->validator->assert($event, $context->getTenantId(), $existing); // update event $existing->setName($event->getName() ?? $existing->getName()); diff --git a/src/Service/Event/Dispatcher.php b/src/Service/Event/Dispatcher.php index 8bd18d47b..9dc795309 100644 --- a/src/Service/Event/Dispatcher.php +++ b/src/Service/Event/Dispatcher.php @@ -22,6 +22,7 @@ use Fusio\Engine\DispatcherInterface; use Fusio\Impl\Messenger\TriggerEvent; +use Fusio\Impl\Service\System\FrameworkConfig; use Symfony\Component\Messenger\MessageBusInterface; /** @@ -34,14 +35,16 @@ class Dispatcher implements DispatcherInterface { private MessageBusInterface $messageBus; + private FrameworkConfig $frameworkConfig; - public function __construct(MessageBusInterface $messageBus) + public function __construct(MessageBusInterface $messageBus, FrameworkConfig $frameworkConfig) { $this->messageBus = $messageBus; + $this->frameworkConfig = $frameworkConfig; } public function dispatch(string $eventName, mixed $payload): void { - $this->messageBus->dispatch(new TriggerEvent($eventName, $payload)); + $this->messageBus->dispatch(new TriggerEvent($this->frameworkConfig->getTenantId(), $eventName, $payload)); } } diff --git a/src/Service/Event/Subscription.php b/src/Service/Event/Subscription.php deleted file mode 100644 index 3fa51f145..000000000 --- a/src/Service/Event/Subscription.php +++ /dev/null @@ -1,112 +0,0 @@ - - * - * Copyright 2015-2023 Christoph Kappestein - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace Fusio\Impl\Service\Event; - -use Fusio\Impl\Authorization\UserContext; -use Fusio\Impl\Event\Event\Subscription\CreatedEvent; -use Fusio\Impl\Event\Event\Subscription\DeletedEvent; -use Fusio\Impl\Event\Event\Subscription\UpdatedEvent; -use Fusio\Impl\Table; -use Fusio\Model\Backend\EventSubscriptionCreate; -use Fusio\Model\Backend\EventSubscriptionUpdate; -use Psr\EventDispatcher\EventDispatcherInterface; -use PSX\Http\Exception as StatusCode; - -/** - * Subscription - * - * @author Christoph Kappestein - * @license http://www.apache.org/licenses/LICENSE-2.0 - * @link https://www.fusio-project.org - */ -class Subscription -{ - private Table\Event\Subscription $subscriptionTable; - private EventDispatcherInterface $eventDispatcher; - - public function __construct(Table\Event\Subscription $subscriptionTable, EventDispatcherInterface $eventDispatcher) - { - $this->subscriptionTable = $subscriptionTable; - $this->eventDispatcher = $eventDispatcher; - } - - public function create(EventSubscriptionCreate $subscription, UserContext $context): int - { - // create subscription - try { - $this->subscriptionTable->beginTransaction(); - - $row = new Table\Generated\EventSubscriptionRow(); - $row->setEventId($subscription->getEventId()); - $row->setUserId($subscription->getUserId()); - $row->setStatus(Table\Event\Subscription::STATUS_ACTIVE); - $row->setEndpoint($subscription->getEndpoint()); - $this->subscriptionTable->create($row); - - $subscriptionId = $this->subscriptionTable->getLastInsertId(); - $subscription->setId($subscriptionId); - - $this->subscriptionTable->commit(); - } catch (\Throwable $e) { - $this->subscriptionTable->rollBack(); - - throw $e; - } - - $this->eventDispatcher->dispatch(new CreatedEvent($subscription, $context)); - - return $subscriptionId; - } - - public function update(int $subscriptionId, EventSubscriptionUpdate $subscription, UserContext $context): int - { - $existing = $this->subscriptionTable->find($subscriptionId); - if (empty($existing)) { - throw new StatusCode\NotFoundException('Could not find subscription'); - } - - // update subscription - $existing->setEndpoint($subscription->getEndpoint()); - $this->subscriptionTable->update($existing); - - $this->eventDispatcher->dispatch(new UpdatedEvent($subscription, $existing, $context)); - - return $subscriptionId; - } - - public function delete(int $subscriptionId, UserContext $context): int - { - $existing = $this->subscriptionTable->find($subscriptionId); - if (empty($existing)) { - throw new StatusCode\NotFoundException('Could not find subscription'); - } - - // delete all responses - $this->subscriptionTable->deleteAllResponses($subscriptionId); - - // remove subscription - $this->subscriptionTable->delete($existing); - - $this->eventDispatcher->dispatch(new DeletedEvent($existing, $context)); - - return $subscriptionId; - } -} diff --git a/src/Service/Event/Validator.php b/src/Service/Event/Validator.php index 443b445c3..ccdfffff3 100644 --- a/src/Service/Event/Validator.php +++ b/src/Service/Event/Validator.php @@ -22,6 +22,7 @@ use Cron\CronExpression; use Fusio\Impl\Framework\Schema\Scheme as SchemaScheme; +use Fusio\Impl\Service\Tenant\UsageLimiter; use Fusio\Impl\Table; use Fusio\Model\Backend\Cronjob; use Fusio\Model\Backend\Event; @@ -41,18 +42,22 @@ class Validator { private Table\Event $eventTable; private SchemaManagerInterface $schemaManager; + private UsageLimiter $usageLimiter; - public function __construct(Table\Event $eventTable, SchemaManagerInterface $schemaManager) + public function __construct(Table\Event $eventTable, SchemaManagerInterface $schemaManager, UsageLimiter $usageLimiter) { $this->eventTable = $eventTable; $this->schemaManager = $schemaManager; + $this->usageLimiter = $usageLimiter; } - public function assert(Event $event, ?Table\Generated\EventRow $existing = null): void + public function assert(Event $event, ?string $tenantId, ?Table\Generated\EventRow $existing = null): void { + $this->usageLimiter->assertEventCount($tenantId); + $name = $event->getName(); if ($name !== null) { - $this->assertName($name, $existing); + $this->assertName($name, $tenantId, $existing); } elseif ($existing === null) { throw new StatusCode\BadRequestException('Event name must not be empty'); } @@ -63,13 +68,13 @@ public function assert(Event $event, ?Table\Generated\EventRow $existing = null) } } - private function assertName(string $name, ?Table\Generated\EventRow $existing = null): void + private function assertName(string $name, ?string $tenantId, ?Table\Generated\EventRow $existing = null): void { if (empty($name) || !preg_match('/^[a-zA-Z0-9\\-\\_\\.]{3,64}$/', $name)) { throw new StatusCode\BadRequestException('Invalid event name'); } - if (($existing === null || $name !== $existing->getName()) && $this->eventTable->findOneByName($name)) { + if (($existing === null || $name !== $existing->getName()) && $this->eventTable->findOneByTenantAndName($tenantId, $name)) { throw new StatusCode\BadRequestException('Event already exists'); } } diff --git a/src/Service/Identity.php b/src/Service/Identity.php index d22b98682..5b8415fbf 100644 --- a/src/Service/Identity.php +++ b/src/Service/Identity.php @@ -75,7 +75,7 @@ public function __construct(Table\Identity $identityTable, Table\Generated\Ident public function create(Model\Backend\IdentityCreate $identity, UserContext $context): int { - $this->validator->assert($identity); + $this->validator->assert($identity, $context->getTenantId()); $provider = $this->identityProvider->getInstance($identity->getClass()); if (!$provider instanceof ProviderInterface) { @@ -127,7 +127,7 @@ public function update(string $identityId, Model\Backend\IdentityUpdate $identit throw new StatusCode\GoneException('Identity was deleted'); } - $this->validator->assert($identity, $existing); + $this->validator->assert($identity, $context->getTenantId(), $existing); $provider = $this->identityProvider->getInstance($existing->getClass()); if (!$provider instanceof ProviderInterface) { diff --git a/src/Service/Identity/Validator.php b/src/Service/Identity/Validator.php index dc8448476..77df82d64 100644 --- a/src/Service/Identity/Validator.php +++ b/src/Service/Identity/Validator.php @@ -20,6 +20,7 @@ namespace Fusio\Impl\Service\Identity; +use Fusio\Impl\Service\Tenant\UsageLimiter; use Fusio\Impl\Table; use Fusio\Model\Backend\Identity; use PSX\Http\Exception as StatusCode; @@ -35,29 +36,33 @@ class Validator { private Table\Identity $identityTable; + private UsageLimiter $usageLimiter; - public function __construct(Table\Identity $identityTable) + public function __construct(Table\Identity $identityTable, UsageLimiter $usageLimiter) { $this->identityTable = $identityTable; + $this->usageLimiter = $usageLimiter; } - public function assert(Identity $identity, ?Table\Generated\IdentityRow $existing = null): void + public function assert(Identity $identity, ?string $tenantId, ?Table\Generated\IdentityRow $existing = null): void { + $this->usageLimiter->assertIdentityCount($tenantId); + $name = $identity->getName(); if ($name !== null) { - $this->assertName($name, $existing); + $this->assertName($name, $tenantId, $existing); } elseif ($existing === null) { throw new StatusCode\BadRequestException('Identity name must not be empty'); } } - private function assertName(string $name, ?Table\Generated\IdentityRow $existing = null): void + private function assertName(string $name, ?string $tenantId, ?Table\Generated\IdentityRow $existing = null): void { if (empty($name) || !preg_match('/^[a-zA-Z0-9\\-\\_]{3,255}$/', $name)) { throw new StatusCode\BadRequestException('Invalid identity name'); } - if (($existing === null || $name !== $existing->getName()) && $this->identityTable->findOneByName($name)) { + if (($existing === null || $name !== $existing->getName()) && $this->identityTable->findOneByTenantAndName($tenantId, $name)) { throw new StatusCode\BadRequestException('Identity already exists'); } } diff --git a/src/Service/Operation.php b/src/Service/Operation.php index eb9d453cf..e29370a20 100644 --- a/src/Service/Operation.php +++ b/src/Service/Operation.php @@ -65,7 +65,7 @@ public function __construct(Table\Operation $operationTable, Service\Operation\V public function create(int $categoryId, OperationCreate $operation, UserContext $context): int { - $this->validator->assert($operation); + $this->validator->assert($operation, $context->getTenantId()); // create operation try { @@ -128,7 +128,7 @@ public function update(string $operationId, OperationUpdate $operation, UserCont throw new StatusCode\GoneException('Operation was deleted'); } - $this->validator->assert($operation, $existing); + $this->validator->assert($operation, $context->getTenantId(), $existing); $isStable = in_array($existing->getStability(), [OperationInterface::STABILITY_STABLE, OperationInterface::STABILITY_LEGACY], true); diff --git a/src/Service/Operation/Validator.php b/src/Service/Operation/Validator.php index b457808e0..144b2ae6a 100644 --- a/src/Service/Operation/Validator.php +++ b/src/Service/Operation/Validator.php @@ -26,6 +26,7 @@ use Fusio\Engine\ProcessorInterface; use Fusio\Impl\Action\Scheme as ActionScheme; use Fusio\Impl\Framework\Schema\Scheme as SchemaScheme; +use Fusio\Impl\Service\Tenant\UsageLimiter; use Fusio\Impl\Table; use Fusio\Model\Backend\Operation; use Fusio\Model\Backend\OperationParameters; @@ -50,19 +51,23 @@ class Validator private Table\Operation $operationTable; private SchemaManagerInterface $schemaManager; private ProcessorInterface $processor; + private UsageLimiter $usageLimiter; - public function __construct(Table\Operation $operationTable, SchemaManagerInterface $schemaManager, ProcessorInterface $processor) + public function __construct(Table\Operation $operationTable, SchemaManagerInterface $schemaManager, ProcessorInterface $processor, UsageLimiter $usageLimiter) { $this->operationTable = $operationTable; $this->schemaManager = $schemaManager; $this->processor = $processor; + $this->usageLimiter = $usageLimiter; } - public function assert(Operation $operation, ?Table\Generated\OperationRow $existing = null): void + public function assert(Operation $operation, ?string $tenantId, ?Table\Generated\OperationRow $existing = null): void { + $this->usageLimiter->assertOperationCount($tenantId); + $name = $operation->getName(); if ($name !== null) { - $this->assertName($name, $existing); + $this->assertName($name, $tenantId, $existing); } else { if ($existing === null) { throw new StatusCode\BadRequestException('Operation name must not be empty'); @@ -105,7 +110,7 @@ public function assert(Operation $operation, ?Table\Generated\OperationRow $exis } } - $this->assertHttpMethodAndPathExisting($operation, $existing); + $this->assertHttpMethodAndPathExisting($operation, $tenantId, $existing); $this->assertParameters($operation->getParameters()); $this->assertIncoming($operation->getIncoming()); @@ -130,13 +135,13 @@ public function assert(Operation $operation, ?Table\Generated\OperationRow $exis } } - private function assertName(string $name, ?Table\Generated\OperationRow $existing = null): void + private function assertName(string $name, ?string $tenantId, ?Table\Generated\OperationRow $existing = null): void { if (empty($name) || !preg_match('/^[a-zA-Z0-9\\_\\.]{3,64}$/', $name)) { throw new StatusCode\BadRequestException('Invalid operation name'); } - if (($existing === null || $name !== $existing->getName()) && $this->operationTable->findOneByName($name)) { + if (($existing === null || $name !== $existing->getName()) && $this->operationTable->findOneByTenantAndName($tenantId, $name)) { throw new StatusCode\BadRequestException('Operation already exists'); } } @@ -195,7 +200,7 @@ private function assertHttpCode(int $code, int $start, int $end, string $type): } } - private function assertHttpMethodAndPathExisting(Operation $operation, ?Table\Generated\OperationRow $existing): void + private function assertHttpMethodAndPathExisting(Operation $operation, ?string $tenantId, ?Table\Generated\OperationRow $existing): void { if ($existing instanceof Table\Generated\OperationRow && $existing->getHttpMethod() === $operation->getHttpMethod() && $existing->getHttpPath() === $operation->getHttpPath()) { // in case we update an existing operation and the method and path has not changed, we dont need to validate @@ -203,6 +208,7 @@ private function assertHttpMethodAndPathExisting(Operation $operation, ?Table\Ge } $condition = Condition::withAnd(); + $condition->equals(Table\Generated\OperationTable::COLUMN_TENANT_ID, $tenantId); $condition->equals(Table\Generated\OperationTable::COLUMN_HTTP_METHOD, $operation->getHttpMethod()); $condition->equals(Table\Generated\OperationTable::COLUMN_HTTP_PATH, $operation->getHttpPath()); if ($this->operationTable->getCount($condition) > 0) { diff --git a/src/Service/Page.php b/src/Service/Page.php index baffe126d..e486ad488 100644 --- a/src/Service/Page.php +++ b/src/Service/Page.php @@ -24,6 +24,7 @@ use Fusio\Impl\Event\Page\CreatedEvent; use Fusio\Impl\Event\Page\DeletedEvent; use Fusio\Impl\Event\Page\UpdatedEvent; +use Fusio\Impl\Service\Page\SlugBuilder; use Fusio\Impl\Table; use Fusio\Model\Backend\PageCreate; use Fusio\Model\Backend\PageUpdate; @@ -53,10 +54,10 @@ public function __construct(Table\Page $pageTable, Page\Validator $validator, Ev public function create(PageCreate $page, UserContext $context): int { - $this->validator->assert($page); + $this->validator->assert($page, $context->getTenantId()); $title = $page->getTitle(); - $slug = $this->createSlug($title); + $slug = SlugBuilder::build($title); // create page try { @@ -98,10 +99,10 @@ public function update(string $pageId, PageUpdate $page, UserContext $context): throw new StatusCode\GoneException('Page was deleted'); } - $this->validator->assert($page, $existing); + $this->validator->assert($page, $context->getTenantId(), $existing); $title = $page->getTitle(); - $slug = $title !== null ? $this->createSlug($title) : null; + $slug = $title !== null ? SlugBuilder::build($title) : null; // update action $existing->setStatus($page->getStatus() ?? $existing->getStatus()); @@ -134,20 +135,4 @@ public function delete(string $pageId, UserContext $context): int return $existing->getId(); } - - /** - * Generates a slug from the title - * - * @see https://haensel.pro/php/php-function-create-slugs-from-string - * @param string $title - * @return string - */ - private function createSlug(string $title): string - { - $slug = iconv('UTF-8', 'ASCII//TRANSLIT', $title); - $slug = preg_replace('/[^a-zA-Z0-9\/_|+ -]/', '', $slug); - $slug = strtolower(trim($slug, '-')); - $slug = preg_replace('/[\/_|+ -]+/', '-', $slug); - return $slug; - } } diff --git a/src/Service/Page/SlugBuilder.php b/src/Service/Page/SlugBuilder.php new file mode 100644 index 000000000..dbe1686ee --- /dev/null +++ b/src/Service/Page/SlugBuilder.php @@ -0,0 +1,45 @@ + + * + * Copyright 2015-2023 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Service\Page; + +/** + * SlugBuilder + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +class SlugBuilder +{ + /** + * Generates a slug from the title + * + * @see https://haensel.pro/php/php-function-create-slugs-from-string + */ + public static function build(string $title): string + { + $slug = iconv('UTF-8', 'ASCII//TRANSLIT', $title); + $slug = preg_replace('/[^a-zA-Z0-9\/_|+ -]/', '', $slug); + $slug = strtolower(trim($slug, '-')); + $slug = preg_replace('/[\/_|+ -]+/', '-', $slug); + return $slug; + } +} diff --git a/src/Service/Page/Validator.php b/src/Service/Page/Validator.php index 7ef8389f2..9a8197930 100644 --- a/src/Service/Page/Validator.php +++ b/src/Service/Page/Validator.php @@ -20,6 +20,7 @@ namespace Fusio\Impl\Service\Page; +use Fusio\Impl\Service\Tenant\UsageLimiter; use Fusio\Impl\Table; use Fusio\Model\Backend\Page; use PSX\Http\Exception as StatusCode; @@ -34,17 +35,21 @@ class Validator { private Table\Page $pageTable; + private UsageLimiter $usageLimiter; - public function __construct(Table\Page $pageTable) + public function __construct(Table\Page $pageTable, UsageLimiter $usageLimiter) { $this->pageTable = $pageTable; + $this->usageLimiter = $usageLimiter; } - public function assert(Page $page, ?Table\Generated\PageRow $existing = null): void + public function assert(Page $page, ?string $tenantId, ?Table\Generated\PageRow $existing = null): void { + $this->usageLimiter->assertPageCount($tenantId); + $title = $page->getTitle(); if ($title !== null) { - $this->assertTitle($title, $existing); + $this->assertTitle($title, $tenantId, $existing); } elseif ($existing === null) { throw new StatusCode\BadRequestException('Page title must not be empty'); } @@ -55,13 +60,13 @@ public function assert(Page $page, ?Table\Generated\PageRow $existing = null): v } } - private function assertTitle(string $title, ?Table\Generated\PageRow $existing = null): void + private function assertTitle(string $title, ?string $tenantId, ?Table\Generated\PageRow $existing = null): void { if (empty($title)) { throw new StatusCode\BadRequestException('Invalid page title'); } - if (($existing === null || $title !== $existing->getTitle()) && $this->pageTable->findOneByTitle($title)) { + if (($existing === null || $title !== $existing->getTitle()) && $this->pageTable->findOneByTenantAndSlug($tenantId, SlugBuilder::build($title))) { throw new StatusCode\BadRequestException('Page already exists'); } } diff --git a/src/Service/Plan.php b/src/Service/Plan.php index 0d6881ac2..cd7a2c8ff 100644 --- a/src/Service/Plan.php +++ b/src/Service/Plan.php @@ -56,7 +56,7 @@ public function __construct(Table\Plan $planTable, Table\Scope $scopeTable, Tabl public function create(PlanCreate $plan, UserContext $context): int { - $this->validator->assert($plan); + $this->validator->assert($plan, $context->getTenantId()); try { $this->planTable->beginTransaction(); @@ -110,7 +110,7 @@ public function update(string $planId, PlanUpdate $plan, UserContext $context): throw new StatusCode\GoneException('Plan was deleted'); } - $this->validator->assert($plan, $existing); + $this->validator->assert($plan, $context->getTenantId(), $existing); $price = $plan->getPrice(); if ($price !== null) { diff --git a/src/Service/Plan/Validator.php b/src/Service/Plan/Validator.php index c19cb2161..5aea65dc1 100644 --- a/src/Service/Plan/Validator.php +++ b/src/Service/Plan/Validator.php @@ -20,6 +20,7 @@ namespace Fusio\Impl\Service\Plan; +use Fusio\Impl\Service\Tenant\UsageLimiter; use Fusio\Impl\Table; use Fusio\Model\Backend\Plan; use PSX\Http\Exception as StatusCode; @@ -34,30 +35,34 @@ class Validator { private Table\Plan $planTable; + private UsageLimiter $usageLimiter; - public function __construct(Table\Plan $planTable) + public function __construct(Table\Plan $planTable, UsageLimiter $usageLimiter) { $this->planTable = $planTable; + $this->usageLimiter = $usageLimiter; } - public function assert(Plan $plan, ?Table\Generated\PlanRow $existing = null): void + public function assert(Plan $plan, ?string $tenantId, ?Table\Generated\PlanRow $existing = null): void { + $this->usageLimiter->assertPlanCount($tenantId); + $name = $plan->getName(); if ($name !== null) { - $this->assertName($name, $existing); + $this->assertName($name, $tenantId, $existing); } elseif ($existing === null) { throw new StatusCode\BadRequestException('Plan name must not be empty'); } } - private function assertName(string $name, ?Table\Generated\PlanRow $existing = null): void + private function assertName(string $name, ?string $tenantId, ?Table\Generated\PlanRow $existing = null): void { if (empty($name)) { throw new StatusCode\BadRequestException('Invalid plan name'); } - if (($existing === null || $name !== $existing->getName()) && $this->planTable->findOneByName($name)) { - throw new StatusCode\BadRequestException('Event already exists'); + if (($existing === null || $name !== $existing->getName()) && $this->planTable->findOneByTenantAndName($tenantId, $name)) { + throw new StatusCode\BadRequestException('Plan already exists'); } } } diff --git a/src/Service/Rate.php b/src/Service/Rate.php index 502637e3a..6846fb6a3 100644 --- a/src/Service/Rate.php +++ b/src/Service/Rate.php @@ -55,7 +55,7 @@ public function __construct(Table\Rate $rateTable, Table\Rate\Allocation $rateAl public function create(RateCreate $rate, UserContext $context): int { - $this->validator->assert($rate); + $this->validator->assert($rate, $context->getTenantId()); try { $this->rateTable->beginTransaction(); @@ -98,7 +98,7 @@ public function update(string $rateId, RateUpdate $rate, UserContext $context): throw new StatusCode\GoneException('Rate was deleted'); } - $this->validator->assert($rate, $existing); + $this->validator->assert($rate, $context->getTenantId(), $existing); try { $this->rateTable->beginTransaction(); diff --git a/src/Service/Rate/Validator.php b/src/Service/Rate/Validator.php index a3a3a0035..882d850b6 100644 --- a/src/Service/Rate/Validator.php +++ b/src/Service/Rate/Validator.php @@ -20,6 +20,7 @@ namespace Fusio\Impl\Service\Rate; +use Fusio\Impl\Service\Tenant\UsageLimiter; use Fusio\Impl\Table; use Fusio\Model\Backend\Rate; use PSX\Http\Exception as StatusCode; @@ -34,29 +35,33 @@ class Validator { private Table\Rate $rateTable; + private UsageLimiter $usageLimiter; - public function __construct(Table\Rate $rateTable) + public function __construct(Table\Rate $rateTable, UsageLimiter $usageLimiter) { $this->rateTable = $rateTable; + $this->usageLimiter = $usageLimiter; } - public function assert(Rate $rate, ?Table\Generated\RateRow $existing = null): void + public function assert(Rate $rate, ?string $tenantId, ?Table\Generated\RateRow $existing = null): void { + $this->usageLimiter->assertRateCount($tenantId); + $name = $rate->getName(); if ($name !== null) { - $this->assertName($name, $existing); + $this->assertName($name, $tenantId, $existing); } elseif ($existing === null) { throw new StatusCode\BadRequestException('Rate name must not be empty'); } } - private function assertName(string $name, ?Table\Generated\RateRow $existing = null): void + private function assertName(string $name, ?string $tenantId, ?Table\Generated\RateRow $existing = null): void { if (empty($name) || !preg_match('/^[a-zA-Z0-9\\-\\_]{3,64}$/', $name)) { throw new StatusCode\BadRequestException('Invalid rate name'); } - if (($existing === null || $name !== $existing->getName()) && $this->rateTable->findOneByName($name)) { + if (($existing === null || $name !== $existing->getName()) && $this->rateTable->findOneByTenantAndName($tenantId, $name)) { throw new StatusCode\BadRequestException('Rate already exists'); } } diff --git a/src/Service/Role.php b/src/Service/Role.php index 59a3b5291..65f1ebbbb 100644 --- a/src/Service/Role.php +++ b/src/Service/Role.php @@ -57,7 +57,7 @@ public function __construct(Table\Role $roleTable, Table\Role\Scope $roleScopeTa public function create(RoleCreate $role, UserContext $context): int { - $this->validator->assert($role); + $this->validator->assert($role, $context->getTenantId()); try { $this->roleTable->beginTransaction(); @@ -100,7 +100,7 @@ public function update(string $roleId, RoleUpdate $role, UserContext $context): throw new StatusCode\GoneException('Role was deleted'); } - $this->validator->assert($role, $existing); + $this->validator->assert($role, $context->getTenantId(), $existing); try { $this->roleTable->beginTransaction(); diff --git a/src/Service/Role/Validator.php b/src/Service/Role/Validator.php index c656a4291..eedbad0fe 100644 --- a/src/Service/Role/Validator.php +++ b/src/Service/Role/Validator.php @@ -20,6 +20,7 @@ namespace Fusio\Impl\Service\Role; +use Fusio\Impl\Service\Tenant\UsageLimiter; use Fusio\Impl\Table; use Fusio\Model\Backend\Role; use PSX\Http\Exception as StatusCode; @@ -34,29 +35,33 @@ class Validator { private Table\Role $roleTable; + private UsageLimiter $usageLimiter; - public function __construct(Table\Role $roleTable) + public function __construct(Table\Role $roleTable, UsageLimiter $usageLimiter) { $this->roleTable = $roleTable; + $this->usageLimiter = $usageLimiter; } - public function assert(Role $role, ?Table\Generated\RoleRow $existing = null): void + public function assert(Role $role, ?string $tenantId, ?Table\Generated\RoleRow $existing = null): void { + $this->usageLimiter->assertRoleCount($tenantId); + $name = $role->getName(); if ($name !== null) { - $this->assertName($name, $existing); + $this->assertName($name, $tenantId, $existing); } elseif ($existing === null) { throw new StatusCode\BadRequestException('Role name must not be empty'); } } - private function assertName(string $name, ?Table\Generated\RoleRow $existing = null): void + private function assertName(string $name, ?string $tenantId, ?Table\Generated\RoleRow $existing = null): void { if (empty($name) || !preg_match('/^[a-zA-Z0-9\\-\\_]{3,64}$/', $name)) { throw new StatusCode\BadRequestException('Invalid role name'); } - if (($existing === null || $name !== $existing->getName()) && $this->roleTable->findOneByName($name)) { + if (($existing === null || $name !== $existing->getName()) && $this->roleTable->findOneByTenantAndName($tenantId, $name)) { throw new StatusCode\BadRequestException('Role already exists'); } } diff --git a/src/Service/Schema.php b/src/Service/Schema.php index d71967d9b..2e5e9f952 100644 --- a/src/Service/Schema.php +++ b/src/Service/Schema.php @@ -59,7 +59,7 @@ public function __construct(Table\Schema $schemaTable, Schema\Validator $validat public function create(int $categoryId, SchemaCreate $schema, UserContext $context): int { - $this->validator->assert($schema); + $this->validator->assert($schema, $context->getTenantId()); try { $this->schemaTable->beginTransaction(); @@ -103,7 +103,7 @@ public function update(string $schemaId, SchemaUpdate $schema, UserContext $cont throw new StatusCode\GoneException('Schema was deleted'); } - $this->validator->assert($schema, $existing); + $this->validator->assert($schema, $context->getTenantId(), $existing); try { $this->schemaTable->beginTransaction(); diff --git a/src/Service/Schema/Validator.php b/src/Service/Schema/Validator.php index 18190425b..46ff81928 100644 --- a/src/Service/Schema/Validator.php +++ b/src/Service/Schema/Validator.php @@ -20,6 +20,7 @@ namespace Fusio\Impl\Service\Schema; +use Fusio\Impl\Service\Tenant\UsageLimiter; use Fusio\Impl\Table; use Fusio\Model\Backend\Schema; use PSX\Http\Exception as StatusCode; @@ -34,29 +35,33 @@ class Validator { private Table\Schema $schemaTable; + private UsageLimiter $usageLimiter; - public function __construct(Table\Schema $schemaTable) + public function __construct(Table\Schema $schemaTable, UsageLimiter $usageLimiter) { $this->schemaTable = $schemaTable; + $this->usageLimiter = $usageLimiter; } - public function assert(Schema $schema, ?Table\Generated\SchemaRow $existing = null): void + public function assert(Schema $schema, ?string $tenantId, ?Table\Generated\SchemaRow $existing = null): void { + $this->usageLimiter->assertSchemaCount($tenantId); + $name = $schema->getName(); if ($name !== null) { - $this->assertName($name, $existing); + $this->assertName($name, $tenantId, $existing); } elseif ($existing === null) { throw new StatusCode\BadRequestException('Schema name must not be empty'); } } - private function assertName(string $name, ?Table\Generated\SchemaRow $existing = null): void + private function assertName(string $name, ?string $tenantId, ?Table\Generated\SchemaRow $existing = null): void { if (empty($name) || !preg_match('/^[a-zA-Z0-9\\-\\_]{3,255}$/', $name)) { throw new StatusCode\BadRequestException('Invalid schema name'); } - if (($existing === null || $name !== $existing->getName()) && $this->schemaTable->findOneByName($name)) { + if (($existing === null || $name !== $existing->getName()) && $this->schemaTable->findOneByTenantAndName($tenantId, $name)) { throw new StatusCode\BadRequestException('Schema already exists'); } } diff --git a/src/Service/Scope.php b/src/Service/Scope.php index 8b6010ddd..5aad3c4e3 100644 --- a/src/Service/Scope.php +++ b/src/Service/Scope.php @@ -62,7 +62,7 @@ public function __construct(Table\Scope $scopeTable, Table\Scope\Operation $scop public function create(int $categoryId, ScopeCreate $scope, UserContext $context): int { - $this->validator->assert($scope); + $this->validator->assert($scope, $context->getTenantId()); try { $this->scopeTable->beginTransaction(); @@ -133,7 +133,7 @@ public function update(string $scopeId, ScopeUpdate $scope, UserContext $context throw new StatusCode\GoneException('Scope was deleted'); } - $this->validator->assert($scope, $existing); + $this->validator->assert($scope, $context->getTenantId(), $existing); try { $this->scopeTable->beginTransaction(); diff --git a/src/Service/Scope/Validator.php b/src/Service/Scope/Validator.php index ccc123059..a7cb3ba98 100644 --- a/src/Service/Scope/Validator.php +++ b/src/Service/Scope/Validator.php @@ -20,6 +20,7 @@ namespace Fusio\Impl\Service\Scope; +use Fusio\Impl\Service\Tenant\UsageLimiter; use Fusio\Impl\Table; use Fusio\Model\Backend\Scope; use PSX\Http\Exception as StatusCode; @@ -34,17 +35,21 @@ class Validator { private Table\Scope $scopeTable; + private UsageLimiter $usageLimiter; - public function __construct(Table\Scope $scopeTable) + public function __construct(Table\Scope $scopeTable, UsageLimiter $usageLimiter) { $this->scopeTable = $scopeTable; + $this->usageLimiter = $usageLimiter; } - public function assert(Scope $scope, ?Table\Generated\ScopeRow $existing = null): void + public function assert(Scope $scope, ?string $tenantId, ?Table\Generated\ScopeRow $existing = null): void { + $this->usageLimiter->assertScopeCount($tenantId); + $name = $scope->getName(); if ($name !== null) { - $this->assertName($name, $existing); + $this->assertName($name, $tenantId, $existing); } else { if ($existing === null) { throw new StatusCode\BadRequestException('Scope name must not be empty'); @@ -59,13 +64,13 @@ public function assert(Scope $scope, ?Table\Generated\ScopeRow $existing = null) } } - private function assertName(string $name, ?Table\Generated\ScopeRow $existing = null): void + private function assertName(string $name, ?string $tenantId, ?Table\Generated\ScopeRow $existing = null): void { if (empty($name) || !preg_match('/^[a-zA-Z0-9\\-\\_\\.]{3,64}$/', $name)) { throw new StatusCode\BadRequestException('Invalid scope name'); } - if (($existing === null || $name !== $existing->getName()) && $this->scopeTable->findOneByName($name)) { + if (($existing === null || $name !== $existing->getName()) && $this->scopeTable->findOneByTenantAndName($tenantId, $name)) { throw new StatusCode\BadRequestException('Scope already exists'); } } diff --git a/src/Service/System/Cleaner.php b/src/Service/System/Cleaner.php index 6d32552da..18a8bed9c 100644 --- a/src/Service/System/Cleaner.php +++ b/src/Service/System/Cleaner.php @@ -61,7 +61,7 @@ private function cleanUpIdentityRequests(): void private function cleanUpEventResponses(): void { - $this->connection->executeStatement('DELETE FROM fusio_event_response WHERE insert_date < :now', [ + $this->connection->executeStatement('DELETE FROM fusio_webhook_response WHERE insert_date < :now', [ 'now' => (new \DateTime('last month'))->format('Y-m-d H:i:s'), ]); } diff --git a/src/Service/System/Health.php b/src/Service/System/Health.php index 5c4d7b751..f1272939d 100644 --- a/src/Service/System/Health.php +++ b/src/Service/System/Health.php @@ -47,9 +47,9 @@ public function __construct(Table\Connection $connectionTable, Factory\Connectio $this->secretKey = $frameworkConfig->getProjectKey(); } - public function check(): Service\Health\CheckResult + public function check(): Health\CheckResult { - $checks = new Service\Health\CheckResult(); + $checks = new Health\CheckResult(); $condition = Condition::withAnd(); $condition->equals('status', Table\Connection::STATUS_ACTIVE); diff --git a/src/Service/Health/CheckResult.php b/src/Service/System/Health/CheckResult.php similarity index 97% rename from src/Service/Health/CheckResult.php rename to src/Service/System/Health/CheckResult.php index f66545c6c..6a28da210 100644 --- a/src/Service/Health/CheckResult.php +++ b/src/Service/System/Health/CheckResult.php @@ -18,7 +18,7 @@ * limitations under the License. */ -namespace Fusio\Impl\Service\Health; +namespace Fusio\Impl\Service\System\Health; /** * CheckResult diff --git a/src/Service/Tenant/LimiterInterface.php b/src/Service/Tenant/LimiterInterface.php new file mode 100644 index 000000000..d2e1028fd --- /dev/null +++ b/src/Service/Tenant/LimiterInterface.php @@ -0,0 +1,48 @@ + + * + * Copyright 2015-2023 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Service\Tenant; + +/** + * LimiterInterface + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +interface LimiterInterface +{ + public function getActionCount(): int; + public function getAppCount(): int; + public function getCategoryCount(): int; + public function getConnectionCount(): int; + public function getCronjobCount(): int; + public function getEventCount(): int; + public function getIdentityCount(): int; + public function getOperationCount(): int; + public function getPageCount(): int; + public function getPlanCount(): int; + public function getRateCount(): int; + public function getRoleCount(): int; + public function getSchemaCount(): int; + public function getScopeCount(): int; + public function getUserCount(): int; + public function getWebhookCount(): int; +} diff --git a/src/Service/Tenant/UsageLimiter.php b/src/Service/Tenant/UsageLimiter.php new file mode 100644 index 000000000..af49ab94b --- /dev/null +++ b/src/Service/Tenant/UsageLimiter.php @@ -0,0 +1,207 @@ + + * + * Copyright 2015-2023 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Service\Tenant; + +use Doctrine\DBAL\Connection; +use Fusio\Impl\Exception\UsageLimitExceededException; +use Fusio\Impl\Service\System\FrameworkConfig; +use Fusio\Impl\Table; + +/** + * UsageLimiter + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +class UsageLimiter +{ + private Connection $connection; + private LimiterInterface $limiter; + + public function __construct(Connection $connection, LimiterInterface $limiter) + { + $this->connection = $connection; + $this->limiter = $limiter; + } + + public function assertActionCount(?string $tenantId): void + { + $this->assert( + $this->getActualCount(Table\Generated\ActionTable::NAME, $tenantId), + $this->limiter->getActionCount(), + 'action' + ); + } + + public function assertAppCount(?string $tenantId): void + { + $this->assert( + $this->getActualCount(Table\Generated\AppTable::NAME, $tenantId), + $this->limiter->getAppCount(), + 'app' + ); + } + + public function assertCategoryCount(?string $tenantId): void + { + $this->assert( + $this->getActualCount(Table\Generated\CategoryTable::NAME, $tenantId), + $this->limiter->getCategoryCount(), + 'category' + ); + } + + public function assertConnectionCount(?string $tenantId): void + { + $this->assert( + $this->getActualCount(Table\Generated\ConnectionTable::NAME, $tenantId), + $this->limiter->getConnectionCount(), + 'connection' + ); + } + + public function assertCronjobCount(?string $tenantId): void + { + $this->assert( + $this->getActualCount(Table\Generated\CronjobTable::NAME, $tenantId), + $this->limiter->getCronjobCount(), + 'cronjob' + ); + } + + public function assertEventCount(?string $tenantId): void + { + $this->assert( + $this->getActualCount(Table\Generated\EventTable::NAME, $tenantId), + $this->limiter->getEventCount(), + 'event' + ); + } + + public function assertIdentityCount(?string $tenantId): void + { + $this->assert( + $this->getActualCount(Table\Generated\IdentityTable::NAME, $tenantId), + $this->limiter->getIdentityCount(), + 'identity' + ); + } + + public function assertOperationCount(?string $tenantId): void + { + $this->assert( + $this->getActualCount(Table\Generated\OperationTable::NAME, $tenantId), + $this->limiter->getOperationCount(), + 'operation' + ); + } + + public function assertPageCount(?string $tenantId): void + { + $this->assert( + $this->getActualCount(Table\Generated\PageTable::NAME, $tenantId), + $this->limiter->getPageCount(), + 'page' + ); + } + + public function assertPlanCount(?string $tenantId): void + { + $this->assert( + $this->getActualCount(Table\Generated\PlanTable::NAME, $tenantId), + $this->limiter->getPlanCount(), + 'plan' + ); + } + + public function assertRateCount(?string $tenantId): void + { + $this->assert( + $this->getActualCount(Table\Generated\RateTable::NAME, $tenantId), + $this->limiter->getRateCount(), + 'rate' + ); + } + + public function assertRoleCount(?string $tenantId): void + { + $this->assert( + $this->getActualCount(Table\Generated\RoleTable::NAME, $tenantId), + $this->limiter->getRoleCount(), + 'role' + ); + } + + public function assertSchemaCount(?string $tenantId): void + { + $this->assert( + $this->getActualCount(Table\Generated\SchemaTable::NAME, $tenantId), + $this->limiter->getSchemaCount(), + 'schema' + ); + } + + public function assertScopeCount(?string $tenantId): void + { + $this->assert( + $this->getActualCount(Table\Generated\ScopeTable::NAME, $tenantId), + $this->limiter->getScopeCount(), + 'scope' + ); + } + + public function assertUserCount(?string $tenantId): void + { + $this->assert( + $this->getActualCount(Table\Generated\UserTable::NAME, $tenantId), + $this->limiter->getUserCount(), + 'user' + ); + } + + public function assertWebhookCount(?string $tenantId): void + { + $this->assert( + $this->getActualCount(Table\Generated\WebhookTable::NAME, $tenantId), + $this->limiter->getWebhookCount(), + 'webhook' + ); + } + + private function assert(int $actual, int $expect, string $type): void + { + if ($actual >= $expect) { + throw new UsageLimitExceededException('Usage limit of ' . $expect . ' exceeded for resource ' . $type); + } + } + + private function getActualCount(string $tableName, ?string $tenantId): int + { + if ($tenantId === null) { + return 0; + } + + return (int) $this->connection->fetchOne('SELECT COUNT(id) AS cnt FROM ' . $tableName . ' WHERE tenant_id = :tenant_id', [ + 'tenant_id' => $tenantId + ]); + } +} diff --git a/src/Service/User.php b/src/Service/User.php index 16b7b2012..6f7548d2f 100644 --- a/src/Service/User.php +++ b/src/Service/User.php @@ -70,7 +70,7 @@ public function __construct(Table\User $userTable, Table\Scope $scopeTable, Tabl public function create(UserCreate $user, UserContext $context): int { - $this->validator->assert($user); + $this->validator->assert($user, $context->getTenantId()); $roleId = $user->getRoleId(); @@ -126,11 +126,11 @@ public function createRemote(Table\Generated\IdentityRow $identity, UserInfo $us $name = str_replace(' ', '_', $userInfo->getName()); // check values - $this->validator->assertName($name); + $this->validator->assertName($name, $context->getTenantId()); $email = $userInfo->getEmail(); if (!empty($email)) { - $this->validator->assertEmail($email); + $this->validator->assertEmail($email, $context->getTenantId()); } try { @@ -138,9 +138,9 @@ public function createRemote(Table\Generated\IdentityRow $identity, UserInfo $us $roleId = $identity->getRoleId(); if (!empty($roleId)) { - $role = $this->roleTable->find($roleId); + $role = $this->roleTable->findOneByTenantAndId($context->getTenantId(), $roleId); } else { - $role = $this->roleTable->findOneByName($this->configService->getValue('role_default')); + $role = $this->roleTable->findOneByTenantAndName($context->getTenantId(), $this->configService->getValue('role_default')); } if (empty($role)) { @@ -196,7 +196,7 @@ public function update(string $userId, UserUpdate $user, UserContext $context): throw new StatusCode\GoneException('User was deleted'); } - $this->validator->assert($user, $existing); + $this->validator->assert($user, $context->getTenantId(), $existing); try { $this->userTable->beginTransaction(); diff --git a/src/Service/User/Validator.php b/src/Service/User/Validator.php index 098d42eda..3ed7dbf49 100644 --- a/src/Service/User/Validator.php +++ b/src/Service/User/Validator.php @@ -21,13 +21,11 @@ namespace Fusio\Impl\Service\User; use Fusio\Impl\Service; +use Fusio\Impl\Service\Tenant\UsageLimiter; use Fusio\Impl\Table; -use Fusio\Impl\Table\Generated\UserRow; use Fusio\Model\Backend\User; use Fusio\Model\Backend\UserCreate; -use Fusio\Model\Backend\UserRemote; use PSX\Http\Exception as StatusCode; -use PSX\Sql\Condition; /** * Validator @@ -42,20 +40,24 @@ class Validator private Table\Role $roleTable; private Table\Plan $planTable; private Service\Config $configService; + private UsageLimiter $usageLimiter; - public function __construct(Table\User $userTable, Table\Role $roleTable, Table\Plan $planTable, Service\Config $configService) + public function __construct(Table\User $userTable, Table\Role $roleTable, Table\Plan $planTable, Service\Config $configService, UsageLimiter $usageLimiter) { $this->userTable = $userTable; $this->roleTable = $roleTable; $this->planTable = $planTable; $this->configService = $configService; + $this->usageLimiter = $usageLimiter; } - public function assert(User $user, ?Table\Generated\UserRow $existing = null): void + public function assert(User $user, ?string $tenantId, ?Table\Generated\UserRow $existing = null): void { + $this->usageLimiter->assertUserCount($tenantId); + $roleId = $user->getRoleId(); if ($roleId !== null) { - $this->assertRoleId($roleId); + $this->assertRoleId($roleId, $tenantId); } else { if ($existing === null) { throw new StatusCode\BadRequestException('User role id must not be empty'); @@ -64,12 +66,12 @@ public function assert(User $user, ?Table\Generated\UserRow $existing = null): v $planId = $user->getPlanId(); if ($planId !== null) { - $this->assertPlanId($planId); + $this->assertPlanId($planId, $tenantId); } $name = $user->getName(); if ($name !== null) { - $this->assertName($name, $existing); + $this->assertName($name, $tenantId, $existing); } else { if ($existing === null) { throw new StatusCode\BadRequestException('User name must not be empty'); @@ -78,7 +80,7 @@ public function assert(User $user, ?Table\Generated\UserRow $existing = null): v $email = $user->getEmail(); if ($email !== null) { - $this->assertEmail($email, $existing); + $this->assertEmail($email, $tenantId, $existing); } else { if ($existing === null) { throw new StatusCode\BadRequestException('User email must not be empty'); @@ -97,18 +99,18 @@ public function assert(User $user, ?Table\Generated\UserRow $existing = null): v } } - public function assertName(?string $name, ?Table\Generated\UserRow $existing = null): void + public function assertName(?string $name, ?string $tenantId, ?Table\Generated\UserRow $existing = null): void { if (empty($name) || !preg_match('/^[a-zA-Z0-9\\-\\_\\.]{3,255}$/', $name)) { throw new StatusCode\BadRequestException('Invalid user name'); } - if (($existing === null || $name !== $existing->getName()) && $this->userTable->findOneByName($name)) { + if (($existing === null || $name !== $existing->getName()) && $this->userTable->findOneByTenantAndName($tenantId, $name)) { throw new StatusCode\BadRequestException('User name already exists'); } } - public function assertEmail(?string $email, ?Table\Generated\UserRow $existing = null): void + public function assertEmail(?string $email, ?string $tenantId, ?Table\Generated\UserRow $existing = null): void { if (empty($email)) { throw new StatusCode\BadRequestException('Email must not be empty'); @@ -118,7 +120,7 @@ public function assertEmail(?string $email, ?Table\Generated\UserRow $existing = throw new StatusCode\BadRequestException('Invalid email format'); } - if (($existing === null || $email !== $existing->getEmail()) && $this->userTable->findOneByEmail($email)) { + if (($existing === null || $email !== $existing->getEmail()) && $this->userTable->findOneByTenantAndEmail($tenantId, $email)) { throw new StatusCode\BadRequestException('User email already exists'); } } @@ -178,17 +180,17 @@ public function assertPassword($password, $minLength = null, $minAlpha = null, $ } } - private function assertRoleId(int $roleId): void + private function assertRoleId(int $roleId, ?string $tenantId): void { - $role = $this->roleTable->find($roleId); + $role = $this->roleTable->findOneByTenantAndId($tenantId, $roleId); if (!$role instanceof Table\Generated\RoleRow) { throw new StatusCode\BadRequestException('Provided role id does not exist'); } } - private function assertPlanId(int $planId): void + private function assertPlanId(int $planId, ?string $tenantId): void { - $plan = $this->planTable->find($planId); + $plan = $this->planTable->findOneByTenantAndId($tenantId, $planId); if (!$plan instanceof Table\Generated\PlanRow) { throw new StatusCode\BadRequestException('Provided plan id does not exist'); } diff --git a/src/Service/Webhook.php b/src/Service/Webhook.php new file mode 100644 index 000000000..45557eb66 --- /dev/null +++ b/src/Service/Webhook.php @@ -0,0 +1,140 @@ + + * + * Copyright 2015-2023 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Service; + +use Fusio\Impl\Authorization\UserContext; +use Fusio\Impl\Event\Webhook\CreatedEvent; +use Fusio\Impl\Event\Webhook\DeletedEvent; +use Fusio\Impl\Event\Webhook\UpdatedEvent; +use Fusio\Impl\Table; +use Fusio\Model\Backend\WebhookCreate; +use Fusio\Model\Backend\WebhookUpdate; +use Psr\EventDispatcher\EventDispatcherInterface; +use PSX\Http\Exception as StatusCode; + +/** + * Webhook + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +class Webhook +{ + private Table\Webhook $webhookTable; + private Webhook\Validator $validator; + private EventDispatcherInterface $eventDispatcher; + + public function __construct(Table\Webhook $webhookTable, Webhook\Validator $validator, EventDispatcherInterface $eventDispatcher) + { + $this->webhookTable = $webhookTable; + $this->validator = $validator; + $this->eventDispatcher = $eventDispatcher; + } + + public function create(WebhookCreate $webhook, UserContext $context): int + { + $this->validator->assert($webhook, $context->getTenantId()); + + try { + $this->webhookTable->beginTransaction(); + + $row = new Table\Generated\WebhookRow(); + $row->setTenantId($context->getTenantId()); + $row->setEventId($webhook->getEventId()); + $row->setUserId($webhook->getUserId()); + $row->setStatus(Table\Webhook::STATUS_ACTIVE); + $row->setName($webhook->getName()); + $row->setEndpoint($webhook->getEndpoint()); + $this->webhookTable->create($row); + + $webhookId = $this->webhookTable->getLastInsertId(); + $webhook->setId($webhookId); + + $this->webhookTable->commit(); + } catch (\Throwable $e) { + $this->webhookTable->rollBack(); + + throw $e; + } + + $this->eventDispatcher->dispatch(new CreatedEvent($webhook, $context)); + + return $webhookId; + } + + public function update(string $webhookId, WebhookUpdate $webhook, UserContext $context): int + { + $existing = $this->webhookTable->findOneByIdentifier($context->getTenantId(), $webhookId); + if (empty($existing)) { + throw new StatusCode\NotFoundException('Could not find webhook'); + } + + $this->validator->assert($webhook, $context->getTenantId(), $existing); + + try { + $this->webhookTable->beginTransaction(); + + // update webhook + $existing->setName($webhook->getName() ?? $existing->getName()); + $existing->setEndpoint($webhook->getEndpoint() ?? $existing->getEndpoint()); + $this->webhookTable->update($existing); + + $this->webhookTable->commit(); + } catch (\Throwable $e) { + $this->webhookTable->rollBack(); + + throw $e; + } + + $this->eventDispatcher->dispatch(new UpdatedEvent($webhook, $existing, $context)); + + return $existing->getId(); + } + + public function delete(string $webhookId, UserContext $context): int + { + $existing = $this->webhookTable->findOneByIdentifier($context->getTenantId(), $webhookId); + if (empty($existing)) { + throw new StatusCode\NotFoundException('Could not find webhook'); + } + + try { + $this->webhookTable->beginTransaction(); + + // delete all responses + $this->webhookTable->deleteAllResponses($existing->getId()); + + // remove webhook + $this->webhookTable->delete($existing); + + $this->webhookTable->commit(); + } catch (\Throwable $e) { + $this->webhookTable->rollBack(); + + throw $e; + } + + $this->eventDispatcher->dispatch(new DeletedEvent($existing, $context)); + + return $existing->getId(); + } +} diff --git a/src/Service/Webhook/Validator.php b/src/Service/Webhook/Validator.php new file mode 100644 index 000000000..0d8528acc --- /dev/null +++ b/src/Service/Webhook/Validator.php @@ -0,0 +1,99 @@ + + * + * Copyright 2015-2023 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Service\Webhook; + +use Fusio\Impl\Service\Tenant\UsageLimiter; +use Fusio\Impl\Table; +use Fusio\Model\Backend\Webhook; +use PSX\Http\Exception as StatusCode; + +/** + * Validator + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +class Validator +{ + private Table\Event $eventTable; + private Table\User $userTable; + private UsageLimiter $usageLimiter; + + public function __construct(Table\Event $eventTable, Table\User $userTable, UsageLimiter $usageLimiter) + { + $this->eventTable = $eventTable; + $this->userTable = $userTable; + $this->usageLimiter = $usageLimiter; + } + + public function assert(Webhook $webhook, ?string $tenantId, ?Table\Generated\WebhookRow $existing = null): void + { + $this->usageLimiter->assertWebhookCount($tenantId); + + $eventId = $webhook->getEventId(); + if ($eventId !== null) { + $this->assertEvent($eventId, $tenantId); + } else { + if ($existing === null) { + throw new StatusCode\BadRequestException('Webhook event must not be empty'); + } + } + + $userId = $webhook->getUserId(); + if ($userId !== null) { + $this->assertUser($userId, $tenantId); + } else { + if ($existing === null) { + throw new StatusCode\BadRequestException('Webhook user must not be empty'); + } + } + + $this->assertUrl($webhook->getEndpoint()); + } + + private function assertEvent(int $eventId, ?string $tenantId): void + { + $event = $this->eventTable->findOneByTenantAndId($tenantId, $eventId); + if (empty($event)) { + throw new StatusCode\BadRequestException('Webhook event does not exist'); + } + } + + private function assertUser(int $userId, ?string $tenantId): void + { + $user = $this->userTable->findOneByTenantAndId($tenantId, $userId); + if (empty($user)) { + throw new StatusCode\BadRequestException('Webhook user does not exist'); + } + } + + private function assertUrl(?string $url): void + { + if (empty($url)) { + throw new StatusCode\BadRequestException('Webhook endpoint must contain a value'); + } + + if (!filter_var($url, FILTER_VALIDATE_URL)) { + throw new StatusCode\BadRequestException('Webhook endpoint must be a valid url'); + } + } +} diff --git a/src/Table/Action.php b/src/Table/Action.php index 066da8e36..37a8eb76f 100644 --- a/src/Table/Action.php +++ b/src/Table/Action.php @@ -21,6 +21,7 @@ namespace Fusio\Impl\Table; use Fusio\Impl\Table\Generated\ActionRow; +use Fusio\Impl\Table\Generated\UserRow; use PSX\Sql\Condition; /** @@ -37,14 +38,27 @@ class Action extends Generated\ActionTable public function findOneByIdentifier(?string $tenantId, string $id): ?ActionRow { - $condition = Condition::withAnd(); - $condition->equals(self::COLUMN_TENANT_ID, $tenantId); - if (str_starts_with($id, '~')) { - $condition->equals(self::COLUMN_NAME, urldecode(substr($id, 1))); + return $this->findOneByTenantAndName($tenantId, urldecode(substr($id, 1))); } else { - $condition->equals(self::COLUMN_ID, (int) $id); + return $this->findOneByTenantAndId($tenantId, (int) $id); } + } + + public function findOneByTenantAndId(?string $tenantId, int $id): ?ActionRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_ID, $id); + + return $this->findOneBy($condition); + } + + public function findOneByTenantAndName(?string $tenantId, string $name): ?ActionRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_NAME, $name); return $this->findOneBy($condition); } diff --git a/src/Table/App.php b/src/Table/App.php index 293c82ebf..43d00d49a 100644 --- a/src/Table/App.php +++ b/src/Table/App.php @@ -20,6 +20,7 @@ namespace Fusio\Impl\Table; +use Fusio\Impl\Table\Generated\ActionRow; use Fusio\Impl\Table\Generated\AppRow; use Fusio\Impl\Table\Generated\OperationRow; use PSX\Sql\Condition; @@ -40,16 +41,11 @@ class App extends Generated\AppTable public function findOneByIdentifier(?string $tenantId, string $id): ?AppRow { - $condition = Condition::withAnd(); - $condition->equals(self::COLUMN_TENANT_ID, $tenantId); - if (str_starts_with($id, '~')) { - $condition->equals(self::COLUMN_NAME, urldecode(substr($id, 1))); + return $this->findOneByTenantAndName($tenantId, urldecode(substr($id, 1))); } else { - $condition->equals(self::COLUMN_ID, (int) $id); + return $this->findOneByTenantAndId($tenantId, (int) $id); } - - return $this->findOneBy($condition); } public function findOneByTenantAndId(?string $tenantId, int $id): ?AppRow @@ -61,6 +57,15 @@ public function findOneByTenantAndId(?string $tenantId, int $id): ?AppRow return $this->findOneBy($condition); } + public function findOneByTenantAndName(?string $tenantId, string $name): ?AppRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_NAME, $name); + + return $this->findOneBy($condition); + } + public function findOneByAppKeyAndSecret(?string $tenantId, string $appKey, string $appSecret): ?AppRow { $condition = Condition::withAnd(); diff --git a/src/Table/Category.php b/src/Table/Category.php index 441c328a2..296649432 100644 --- a/src/Table/Category.php +++ b/src/Table/Category.php @@ -20,6 +20,7 @@ namespace Fusio\Impl\Table; +use Fusio\Impl\Table\Generated\ActionRow; use Fusio\Impl\Table\Generated\CategoryRow; use PSX\Sql\Condition; @@ -37,14 +38,27 @@ class Category extends Generated\CategoryTable public function findOneByIdentifier(?string $tenantId, string $id): ?CategoryRow { - $condition = Condition::withAnd(); - $condition->equals(self::COLUMN_TENANT_ID, $tenantId); - if (str_starts_with($id, '~')) { - $condition->equals(self::COLUMN_NAME, urldecode(substr($id, 1))); + return $this->findOneByTenantAndName($tenantId, urldecode(substr($id, 1))); } else { - $condition->equals(self::COLUMN_ID, (int) $id); + return $this->findOneByTenantAndId($tenantId, (int) $id); } + } + + public function findOneByTenantAndId(?string $tenantId, int $id): ?CategoryRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_ID, $id); + + return $this->findOneBy($condition); + } + + public function findOneByTenantAndName(?string $tenantId, string $name): ?CategoryRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_NAME, $name); return $this->findOneBy($condition); } diff --git a/src/Table/Config.php b/src/Table/Config.php index cd783b6fd..5c62fc98f 100644 --- a/src/Table/Config.php +++ b/src/Table/Config.php @@ -43,14 +43,27 @@ class Config extends Generated\ConfigTable public function findOneByIdentifier(?string $tenantId, string $id): ?ConfigRow { - $condition = Condition::withAnd(); - $condition->equals(self::COLUMN_TENANT_ID, $tenantId); - if (str_starts_with($id, '~')) { - $condition->equals(self::COLUMN_NAME, urldecode(substr($id, 1))); + return $this->findOneByTenantAndName($tenantId, urldecode(substr($id, 1))); } else { - $condition->equals(self::COLUMN_ID, (int) $id); + return $this->findOneByTenantAndId($tenantId, (int) $id); } + } + + public function findOneByTenantAndId(?string $tenantId, int $id): ?ConfigRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_ID, $id); + + return $this->findOneBy($condition); + } + + public function findOneByTenantAndName(?string $tenantId, string $name): ?ConfigRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_NAME, $name); return $this->findOneBy($condition); } diff --git a/src/Table/Connection.php b/src/Table/Connection.php index 8147094f3..8fe79a8a4 100644 --- a/src/Table/Connection.php +++ b/src/Table/Connection.php @@ -20,6 +20,7 @@ namespace Fusio\Impl\Table; +use Fusio\Impl\Table\Generated\CategoryRow; use Fusio\Impl\Table\Generated\ConnectionRow; use PSX\Sql\Condition; @@ -37,14 +38,27 @@ class Connection extends Generated\ConnectionTable public function findOneByIdentifier(?string $tenantId, string $id): ?ConnectionRow { - $condition = Condition::withAnd(); - $condition->equals(self::COLUMN_TENANT_ID, $tenantId); - if (str_starts_with($id, '~')) { - $condition->equals(self::COLUMN_NAME, urldecode(substr($id, 1))); + return $this->findOneByTenantAndName($tenantId, urldecode(substr($id, 1))); } else { - $condition->equals(self::COLUMN_ID, (int) $id); + return $this->findOneByTenantAndId($tenantId, (int) $id); } + } + + public function findOneByTenantAndId(?string $tenantId, int $id): ?ConnectionRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_ID, $id); + + return $this->findOneBy($condition); + } + + public function findOneByTenantAndName(?string $tenantId, string $name): ?ConnectionRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_NAME, $name); return $this->findOneBy($condition); } diff --git a/src/Table/Cronjob.php b/src/Table/Cronjob.php index 7c82735e9..bbc69c90a 100644 --- a/src/Table/Cronjob.php +++ b/src/Table/Cronjob.php @@ -41,14 +41,27 @@ class Cronjob extends Generated\CronjobTable public function findOneByIdentifier(?string $tenantId, string $id): ?CronjobRow { - $condition = Condition::withAnd(); - $condition->equals(self::COLUMN_TENANT_ID, $tenantId); - if (str_starts_with($id, '~')) { - $condition->equals(self::COLUMN_NAME, urldecode(substr($id, 1))); + return $this->findOneByTenantAndName($tenantId, urldecode(substr($id, 1))); } else { - $condition->equals(self::COLUMN_ID, (int) $id); + return $this->findOneByTenantAndId($tenantId, (int) $id); } + } + + public function findOneByTenantAndId(?string $tenantId, int $id): ?CronjobRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_ID, $id); + + return $this->findOneBy($condition); + } + + public function findOneByTenantAndName(?string $tenantId, string $name): ?CronjobRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_NAME, $name); return $this->findOneBy($condition); } diff --git a/src/Table/Event.php b/src/Table/Event.php index 7aee4deb3..b24794fc9 100644 --- a/src/Table/Event.php +++ b/src/Table/Event.php @@ -38,14 +38,27 @@ class Event extends Generated\EventTable public function findOneByIdentifier(?string $tenantId, string $id): ?EventRow { - $condition = Condition::withAnd(); - $condition->equals(self::COLUMN_TENANT_ID, $tenantId); - if (str_starts_with($id, '~')) { - $condition->equals(self::COLUMN_NAME, urldecode(substr($id, 1))); + return $this->findOneByTenantAndName($tenantId, urldecode(substr($id, 1))); } else { - $condition->equals(self::COLUMN_ID, (int) $id); + return $this->findOneByTenantAndId($tenantId, (int) $id); } + } + + public function findOneByTenantAndId(?string $tenantId, int $id): ?EventRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_ID, $id); + + return $this->findOneBy($condition); + } + + public function findOneByTenantAndName(?string $tenantId, string $name): ?EventRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_NAME, $name); return $this->findOneBy($condition); } diff --git a/src/Table/Event/Subscription.php b/src/Table/Event/Subscription.php deleted file mode 100644 index ff0a4d461..000000000 --- a/src/Table/Event/Subscription.php +++ /dev/null @@ -1,70 +0,0 @@ - - * - * Copyright 2015-2023 Christoph Kappestein - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace Fusio\Impl\Table\Event; - -use Fusio\Impl\Table\Generated; -use PSX\Sql\Condition; - -/** - * Subscription - * - * @author Christoph Kappestein - * @license http://www.apache.org/licenses/LICENSE-2.0 - * @link https://www.fusio-project.org - */ -class Subscription extends Generated\EventSubscriptionTable -{ - const STATUS_ACTIVE = 1; - const STATUS_INACTIVE = 2; - - public function getSubscriptionsForEvent(int $eventId): array - { - $condition = Condition::withAnd(); - $condition->equals(self::COLUMN_EVENT_ID, $eventId); - $condition->equals(self::COLUMN_STATUS, self::STATUS_ACTIVE); - - $queryBuilder = $this->connection->createQueryBuilder() - ->select([ - 'subscription.' . self::COLUMN_ID, - 'subscription.' . self::COLUMN_ENDPOINT, - ]) - ->from('fusio_event_subscription', 'subscription') - ->where($condition->getExpression($this->connection->getDatabasePlatform())) - ->orderBy('subscription.' . self::COLUMN_ID, 'ASC') - ->setParameters($condition->getValues()); - - return $this->connection->fetchAllAssociative($queryBuilder->getSQL(), $queryBuilder->getParameters()); - } - - public function getSubscriptionCount(int $userId): int - { - return (int) $this->connection->fetchOne('SELECT COUNT(*) AS cnt FROM fusio_event_subscription WHERE user_id = :user_id', [ - 'user_id' => $userId - ]); - } - - public function deleteAllResponses(int $subscriptionId): void - { - $this->connection->executeStatement('DELETE FROM fusio_event_response WHERE subscription_id = :subscription_id', [ - 'subscription_id' => $subscriptionId - ]); - } -} diff --git a/src/Table/Generated/EventResponseRow.php b/src/Table/Generated/WebhookResponseRow.php similarity index 85% rename from src/Table/Generated/EventResponseRow.php rename to src/Table/Generated/WebhookResponseRow.php index 8bd31b224..e6b529c6e 100644 --- a/src/Table/Generated/EventResponseRow.php +++ b/src/Table/Generated/WebhookResponseRow.php @@ -2,10 +2,10 @@ namespace Fusio\Impl\Table\Generated; -class EventResponseRow implements \JsonSerializable, \PSX\Record\RecordableInterface +class WebhookResponseRow implements \JsonSerializable, \PSX\Record\RecordableInterface { private ?int $id = null; - private ?int $subscriptionId = null; + private ?int $webhookId = null; private ?int $status = null; private ?int $attempts = null; private ?int $code = null; @@ -20,13 +20,13 @@ public function getId() : int { return $this->id ?? throw new \PSX\Sql\Exception\NoValueAvailable('No value for required column "id" was provided'); } - public function setSubscriptionId(int $subscriptionId) : void + public function setWebhookId(int $webhookId) : void { - $this->subscriptionId = $subscriptionId; + $this->webhookId = $webhookId; } - public function getSubscriptionId() : int + public function getWebhookId() : int { - return $this->subscriptionId ?? throw new \PSX\Sql\Exception\NoValueAvailable('No value for required column "subscription_id" was provided'); + return $this->webhookId ?? throw new \PSX\Sql\Exception\NoValueAvailable('No value for required column "webhook_id" was provided'); } public function setStatus(int $status) : void { @@ -81,7 +81,7 @@ public function toRecord() : \PSX\Record\RecordInterface /** @var \PSX\Record\Record $record */ $record = new \PSX\Record\Record(); $record->put('id', $this->id); - $record->put('subscription_id', $this->subscriptionId); + $record->put('webhook_id', $this->webhookId); $record->put('status', $this->status); $record->put('attempts', $this->attempts); $record->put('code', $this->code); @@ -98,7 +98,7 @@ public static function from(array|\ArrayAccess $data) : self { $row = new self(); $row->id = isset($data['id']) && is_int($data['id']) ? $data['id'] : null; - $row->subscriptionId = isset($data['subscription_id']) && is_int($data['subscription_id']) ? $data['subscription_id'] : null; + $row->webhookId = isset($data['webhook_id']) && is_int($data['webhook_id']) ? $data['webhook_id'] : null; $row->status = isset($data['status']) && is_int($data['status']) ? $data['status'] : null; $row->attempts = isset($data['attempts']) && is_int($data['attempts']) ? $data['attempts'] : null; $row->code = isset($data['code']) && is_int($data['code']) ? $data['code'] : null; diff --git a/src/Table/Generated/EventResponseTable.php b/src/Table/Generated/WebhookResponseTable.php similarity index 81% rename from src/Table/Generated/EventResponseTable.php rename to src/Table/Generated/WebhookResponseTable.php index d43ddb74a..b6104281c 100644 --- a/src/Table/Generated/EventResponseTable.php +++ b/src/Table/Generated/WebhookResponseTable.php @@ -3,13 +3,13 @@ namespace Fusio\Impl\Table\Generated; /** - * @extends \PSX\Sql\TableAbstract<\Fusio\Impl\Table\Generated\EventResponseRow> + * @extends \PSX\Sql\TableAbstract<\Fusio\Impl\Table\Generated\WebhookResponseRow> */ -class EventResponseTable extends \PSX\Sql\TableAbstract +class WebhookResponseTable extends \PSX\Sql\TableAbstract { - public const NAME = 'fusio_event_response'; + public const NAME = 'fusio_webhook_response'; public const COLUMN_ID = 'id'; - public const COLUMN_SUBSCRIPTION_ID = 'subscription_id'; + public const COLUMN_WEBHOOK_ID = 'webhook_id'; public const COLUMN_STATUS = 'status'; public const COLUMN_ATTEMPTS = 'attempts'; public const COLUMN_CODE = 'code'; @@ -22,10 +22,10 @@ public function getName() : string } public function getColumns() : array { - return array(self::COLUMN_ID => 0x3020000a, self::COLUMN_SUBSCRIPTION_ID => 0x20000a, self::COLUMN_STATUS => 0x20000a, self::COLUMN_ATTEMPTS => 0x20000a, self::COLUMN_CODE => 0x4020000a, self::COLUMN_BODY => 0x40b00000, self::COLUMN_EXECUTE_DATE => 0x40800000, self::COLUMN_INSERT_DATE => 0x800000); + return array(self::COLUMN_ID => 0x3020000a, self::COLUMN_WEBHOOK_ID => 0x20000a, self::COLUMN_STATUS => 0x20000a, self::COLUMN_ATTEMPTS => 0x20000a, self::COLUMN_CODE => 0x4020000a, self::COLUMN_BODY => 0x40b00000, self::COLUMN_EXECUTE_DATE => 0x40800000, self::COLUMN_INSERT_DATE => 0x800000); } /** - * @return array<\Fusio\Impl\Table\Generated\EventResponseRow> + * @return array<\Fusio\Impl\Table\Generated\WebhookResponseRow> * @throws \PSX\Sql\Exception\QueryException */ public function findAll(?\PSX\Sql\Condition $condition = null, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array @@ -33,7 +33,7 @@ public function findAll(?\PSX\Sql\Condition $condition = null, ?int $startIndex return $this->doFindAll($condition, $startIndex, $count, $sortBy, $sortOrder); } /** - * @return array<\Fusio\Impl\Table\Generated\EventResponseRow> + * @return array<\Fusio\Impl\Table\Generated\WebhookResponseRow> * @throws \PSX\Sql\Exception\QueryException */ public function findBy(\PSX\Sql\Condition $condition, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array @@ -43,21 +43,21 @@ public function findBy(\PSX\Sql\Condition $condition, ?int $startIndex = null, ? /** * @throws \PSX\Sql\Exception\QueryException */ - public function findOneBy(\PSX\Sql\Condition $condition) : ?\Fusio\Impl\Table\Generated\EventResponseRow + public function findOneBy(\PSX\Sql\Condition $condition) : ?\Fusio\Impl\Table\Generated\WebhookResponseRow { return $this->doFindOneBy($condition); } /** * @throws \PSX\Sql\Exception\QueryException */ - public function find(int $id) : ?\Fusio\Impl\Table\Generated\EventResponseRow + public function find(int $id) : ?\Fusio\Impl\Table\Generated\WebhookResponseRow { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('id', $id); return $this->doFindOneBy($condition); } /** - * @return array<\Fusio\Impl\Table\Generated\EventResponseRow> + * @return array<\Fusio\Impl\Table\Generated\WebhookResponseRow> * @throws \PSX\Sql\Exception\QueryException */ public function findById(int $value, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array @@ -69,7 +69,7 @@ public function findById(int $value, ?int $startIndex = null, ?int $count = null /** * @throws \PSX\Sql\Exception\QueryException */ - public function findOneById(int $value) : ?\Fusio\Impl\Table\Generated\EventResponseRow + public function findOneById(int $value) : ?\Fusio\Impl\Table\Generated\WebhookResponseRow { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('id', $value); @@ -78,7 +78,7 @@ public function findOneById(int $value) : ?\Fusio\Impl\Table\Generated\EventResp /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function updateById(int $value, \Fusio\Impl\Table\Generated\EventResponseRow $record) : int + public function updateById(int $value, \Fusio\Impl\Table\Generated\WebhookResponseRow $record) : int { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('id', $value); @@ -94,44 +94,44 @@ public function deleteById(int $value) : int return $this->doDeleteBy($condition); } /** - * @return array<\Fusio\Impl\Table\Generated\EventResponseRow> + * @return array<\Fusio\Impl\Table\Generated\WebhookResponseRow> * @throws \PSX\Sql\Exception\QueryException */ - public function findBySubscriptionId(int $value, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array + public function findByWebhookId(int $value, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array { $condition = \PSX\Sql\Condition::withAnd(); - $condition->equals('subscription_id', $value); + $condition->equals('webhook_id', $value); return $this->doFindBy($condition, $startIndex, $count, $sortBy, $sortOrder); } /** * @throws \PSX\Sql\Exception\QueryException */ - public function findOneBySubscriptionId(int $value) : ?\Fusio\Impl\Table\Generated\EventResponseRow + public function findOneByWebhookId(int $value) : ?\Fusio\Impl\Table\Generated\WebhookResponseRow { $condition = \PSX\Sql\Condition::withAnd(); - $condition->equals('subscription_id', $value); + $condition->equals('webhook_id', $value); return $this->doFindOneBy($condition); } /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function updateBySubscriptionId(int $value, \Fusio\Impl\Table\Generated\EventResponseRow $record) : int + public function updateByWebhookId(int $value, \Fusio\Impl\Table\Generated\WebhookResponseRow $record) : int { $condition = \PSX\Sql\Condition::withAnd(); - $condition->equals('subscription_id', $value); + $condition->equals('webhook_id', $value); return $this->doUpdateBy($condition, $record->toRecord()); } /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function deleteBySubscriptionId(int $value) : int + public function deleteByWebhookId(int $value) : int { $condition = \PSX\Sql\Condition::withAnd(); - $condition->equals('subscription_id', $value); + $condition->equals('webhook_id', $value); return $this->doDeleteBy($condition); } /** - * @return array<\Fusio\Impl\Table\Generated\EventResponseRow> + * @return array<\Fusio\Impl\Table\Generated\WebhookResponseRow> * @throws \PSX\Sql\Exception\QueryException */ public function findByStatus(int $value, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array @@ -143,7 +143,7 @@ public function findByStatus(int $value, ?int $startIndex = null, ?int $count = /** * @throws \PSX\Sql\Exception\QueryException */ - public function findOneByStatus(int $value) : ?\Fusio\Impl\Table\Generated\EventResponseRow + public function findOneByStatus(int $value) : ?\Fusio\Impl\Table\Generated\WebhookResponseRow { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('status', $value); @@ -152,7 +152,7 @@ public function findOneByStatus(int $value) : ?\Fusio\Impl\Table\Generated\Event /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function updateByStatus(int $value, \Fusio\Impl\Table\Generated\EventResponseRow $record) : int + public function updateByStatus(int $value, \Fusio\Impl\Table\Generated\WebhookResponseRow $record) : int { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('status', $value); @@ -168,7 +168,7 @@ public function deleteByStatus(int $value) : int return $this->doDeleteBy($condition); } /** - * @return array<\Fusio\Impl\Table\Generated\EventResponseRow> + * @return array<\Fusio\Impl\Table\Generated\WebhookResponseRow> * @throws \PSX\Sql\Exception\QueryException */ public function findByAttempts(int $value, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array @@ -180,7 +180,7 @@ public function findByAttempts(int $value, ?int $startIndex = null, ?int $count /** * @throws \PSX\Sql\Exception\QueryException */ - public function findOneByAttempts(int $value) : ?\Fusio\Impl\Table\Generated\EventResponseRow + public function findOneByAttempts(int $value) : ?\Fusio\Impl\Table\Generated\WebhookResponseRow { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('attempts', $value); @@ -189,7 +189,7 @@ public function findOneByAttempts(int $value) : ?\Fusio\Impl\Table\Generated\Eve /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function updateByAttempts(int $value, \Fusio\Impl\Table\Generated\EventResponseRow $record) : int + public function updateByAttempts(int $value, \Fusio\Impl\Table\Generated\WebhookResponseRow $record) : int { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('attempts', $value); @@ -205,7 +205,7 @@ public function deleteByAttempts(int $value) : int return $this->doDeleteBy($condition); } /** - * @return array<\Fusio\Impl\Table\Generated\EventResponseRow> + * @return array<\Fusio\Impl\Table\Generated\WebhookResponseRow> * @throws \PSX\Sql\Exception\QueryException */ public function findByCode(int $value, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array @@ -217,7 +217,7 @@ public function findByCode(int $value, ?int $startIndex = null, ?int $count = nu /** * @throws \PSX\Sql\Exception\QueryException */ - public function findOneByCode(int $value) : ?\Fusio\Impl\Table\Generated\EventResponseRow + public function findOneByCode(int $value) : ?\Fusio\Impl\Table\Generated\WebhookResponseRow { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('code', $value); @@ -226,7 +226,7 @@ public function findOneByCode(int $value) : ?\Fusio\Impl\Table\Generated\EventRe /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function updateByCode(int $value, \Fusio\Impl\Table\Generated\EventResponseRow $record) : int + public function updateByCode(int $value, \Fusio\Impl\Table\Generated\WebhookResponseRow $record) : int { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('code', $value); @@ -242,7 +242,7 @@ public function deleteByCode(int $value) : int return $this->doDeleteBy($condition); } /** - * @return array<\Fusio\Impl\Table\Generated\EventResponseRow> + * @return array<\Fusio\Impl\Table\Generated\WebhookResponseRow> * @throws \PSX\Sql\Exception\QueryException */ public function findByBody(string $value, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array @@ -254,7 +254,7 @@ public function findByBody(string $value, ?int $startIndex = null, ?int $count = /** * @throws \PSX\Sql\Exception\QueryException */ - public function findOneByBody(string $value) : ?\Fusio\Impl\Table\Generated\EventResponseRow + public function findOneByBody(string $value) : ?\Fusio\Impl\Table\Generated\WebhookResponseRow { $condition = \PSX\Sql\Condition::withAnd(); $condition->like('body', $value); @@ -263,7 +263,7 @@ public function findOneByBody(string $value) : ?\Fusio\Impl\Table\Generated\Even /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function updateByBody(string $value, \Fusio\Impl\Table\Generated\EventResponseRow $record) : int + public function updateByBody(string $value, \Fusio\Impl\Table\Generated\WebhookResponseRow $record) : int { $condition = \PSX\Sql\Condition::withAnd(); $condition->like('body', $value); @@ -279,7 +279,7 @@ public function deleteByBody(string $value) : int return $this->doDeleteBy($condition); } /** - * @return array<\Fusio\Impl\Table\Generated\EventResponseRow> + * @return array<\Fusio\Impl\Table\Generated\WebhookResponseRow> * @throws \PSX\Sql\Exception\QueryException */ public function findByExecuteDate(\PSX\DateTime\LocalDateTime $value, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array @@ -291,7 +291,7 @@ public function findByExecuteDate(\PSX\DateTime\LocalDateTime $value, ?int $star /** * @throws \PSX\Sql\Exception\QueryException */ - public function findOneByExecuteDate(\PSX\DateTime\LocalDateTime $value) : ?\Fusio\Impl\Table\Generated\EventResponseRow + public function findOneByExecuteDate(\PSX\DateTime\LocalDateTime $value) : ?\Fusio\Impl\Table\Generated\WebhookResponseRow { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('execute_date', $value); @@ -300,7 +300,7 @@ public function findOneByExecuteDate(\PSX\DateTime\LocalDateTime $value) : ?\Fus /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function updateByExecuteDate(\PSX\DateTime\LocalDateTime $value, \Fusio\Impl\Table\Generated\EventResponseRow $record) : int + public function updateByExecuteDate(\PSX\DateTime\LocalDateTime $value, \Fusio\Impl\Table\Generated\WebhookResponseRow $record) : int { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('execute_date', $value); @@ -316,7 +316,7 @@ public function deleteByExecuteDate(\PSX\DateTime\LocalDateTime $value) : int return $this->doDeleteBy($condition); } /** - * @return array<\Fusio\Impl\Table\Generated\EventResponseRow> + * @return array<\Fusio\Impl\Table\Generated\WebhookResponseRow> * @throws \PSX\Sql\Exception\QueryException */ public function findByInsertDate(\PSX\DateTime\LocalDateTime $value, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array @@ -328,7 +328,7 @@ public function findByInsertDate(\PSX\DateTime\LocalDateTime $value, ?int $start /** * @throws \PSX\Sql\Exception\QueryException */ - public function findOneByInsertDate(\PSX\DateTime\LocalDateTime $value) : ?\Fusio\Impl\Table\Generated\EventResponseRow + public function findOneByInsertDate(\PSX\DateTime\LocalDateTime $value) : ?\Fusio\Impl\Table\Generated\WebhookResponseRow { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('insert_date', $value); @@ -337,7 +337,7 @@ public function findOneByInsertDate(\PSX\DateTime\LocalDateTime $value) : ?\Fusi /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function updateByInsertDate(\PSX\DateTime\LocalDateTime $value, \Fusio\Impl\Table\Generated\EventResponseRow $record) : int + public function updateByInsertDate(\PSX\DateTime\LocalDateTime $value, \Fusio\Impl\Table\Generated\WebhookResponseRow $record) : int { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('insert_date', $value); @@ -355,28 +355,28 @@ public function deleteByInsertDate(\PSX\DateTime\LocalDateTime $value) : int /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function create(\Fusio\Impl\Table\Generated\EventResponseRow $record) : int + public function create(\Fusio\Impl\Table\Generated\WebhookResponseRow $record) : int { return $this->doCreate($record->toRecord()); } /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function update(\Fusio\Impl\Table\Generated\EventResponseRow $record) : int + public function update(\Fusio\Impl\Table\Generated\WebhookResponseRow $record) : int { return $this->doUpdate($record->toRecord()); } /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function updateBy(\PSX\Sql\Condition $condition, \Fusio\Impl\Table\Generated\EventResponseRow $record) : int + public function updateBy(\PSX\Sql\Condition $condition, \Fusio\Impl\Table\Generated\WebhookResponseRow $record) : int { return $this->doUpdateBy($condition, $record->toRecord()); } /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function delete(\Fusio\Impl\Table\Generated\EventResponseRow $record) : int + public function delete(\Fusio\Impl\Table\Generated\WebhookResponseRow $record) : int { return $this->doDelete($record->toRecord()); } @@ -390,8 +390,8 @@ public function deleteBy(\PSX\Sql\Condition $condition) : int /** * @param array $row */ - protected function newRecord(array $row) : \Fusio\Impl\Table\Generated\EventResponseRow + protected function newRecord(array $row) : \Fusio\Impl\Table\Generated\WebhookResponseRow { - return \Fusio\Impl\Table\Generated\EventResponseRow::from($row); + return \Fusio\Impl\Table\Generated\WebhookResponseRow::from($row); } } \ No newline at end of file diff --git a/src/Table/Generated/EventSubscriptionRow.php b/src/Table/Generated/WebhookRow.php similarity index 75% rename from src/Table/Generated/EventSubscriptionRow.php rename to src/Table/Generated/WebhookRow.php index 87451185d..aaf67674d 100644 --- a/src/Table/Generated/EventSubscriptionRow.php +++ b/src/Table/Generated/WebhookRow.php @@ -2,12 +2,14 @@ namespace Fusio\Impl\Table\Generated; -class EventSubscriptionRow implements \JsonSerializable, \PSX\Record\RecordableInterface +class WebhookRow implements \JsonSerializable, \PSX\Record\RecordableInterface { private ?int $id = null; private ?int $eventId = null; private ?int $userId = null; + private ?string $tenantId = null; private ?int $status = null; + private ?string $name = null; private ?string $endpoint = null; public function setId(int $id) : void { @@ -33,6 +35,14 @@ public function getUserId() : int { return $this->userId ?? throw new \PSX\Sql\Exception\NoValueAvailable('No value for required column "user_id" was provided'); } + public function setTenantId(?string $tenantId) : void + { + $this->tenantId = $tenantId; + } + public function getTenantId() : ?string + { + return $this->tenantId; + } public function setStatus(int $status) : void { $this->status = $status; @@ -41,6 +51,14 @@ public function getStatus() : int { return $this->status ?? throw new \PSX\Sql\Exception\NoValueAvailable('No value for required column "status" was provided'); } + public function setName(string $name) : void + { + $this->name = $name; + } + public function getName() : string + { + return $this->name ?? throw new \PSX\Sql\Exception\NoValueAvailable('No value for required column "name" was provided'); + } public function setEndpoint(string $endpoint) : void { $this->endpoint = $endpoint; @@ -56,7 +74,9 @@ public function toRecord() : \PSX\Record\RecordInterface $record->put('id', $this->id); $record->put('event_id', $this->eventId); $record->put('user_id', $this->userId); + $record->put('tenant_id', $this->tenantId); $record->put('status', $this->status); + $record->put('name', $this->name); $record->put('endpoint', $this->endpoint); return $record; } @@ -70,7 +90,9 @@ public static function from(array|\ArrayAccess $data) : self $row->id = isset($data['id']) && is_int($data['id']) ? $data['id'] : null; $row->eventId = isset($data['event_id']) && is_int($data['event_id']) ? $data['event_id'] : null; $row->userId = isset($data['user_id']) && is_int($data['user_id']) ? $data['user_id'] : null; + $row->tenantId = isset($data['tenant_id']) && is_string($data['tenant_id']) ? $data['tenant_id'] : null; $row->status = isset($data['status']) && is_int($data['status']) ? $data['status'] : null; + $row->name = isset($data['name']) && is_string($data['name']) ? $data['name'] : null; $row->endpoint = isset($data['endpoint']) && is_string($data['endpoint']) ? $data['endpoint'] : null; return $row; } diff --git a/src/Table/Generated/EventSubscriptionTable.php b/src/Table/Generated/WebhookTable.php similarity index 67% rename from src/Table/Generated/EventSubscriptionTable.php rename to src/Table/Generated/WebhookTable.php index 552e30422..d935d344f 100644 --- a/src/Table/Generated/EventSubscriptionTable.php +++ b/src/Table/Generated/WebhookTable.php @@ -3,15 +3,17 @@ namespace Fusio\Impl\Table\Generated; /** - * @extends \PSX\Sql\TableAbstract<\Fusio\Impl\Table\Generated\EventSubscriptionRow> + * @extends \PSX\Sql\TableAbstract<\Fusio\Impl\Table\Generated\WebhookRow> */ -class EventSubscriptionTable extends \PSX\Sql\TableAbstract +class WebhookTable extends \PSX\Sql\TableAbstract { - public const NAME = 'fusio_event_subscription'; + public const NAME = 'fusio_webhook'; public const COLUMN_ID = 'id'; public const COLUMN_EVENT_ID = 'event_id'; public const COLUMN_USER_ID = 'user_id'; + public const COLUMN_TENANT_ID = 'tenant_id'; public const COLUMN_STATUS = 'status'; + public const COLUMN_NAME = 'name'; public const COLUMN_ENDPOINT = 'endpoint'; public function getName() : string { @@ -19,10 +21,10 @@ public function getName() : string } public function getColumns() : array { - return array(self::COLUMN_ID => 0x3020000a, self::COLUMN_EVENT_ID => 0x20000a, self::COLUMN_USER_ID => 0x20000a, self::COLUMN_STATUS => 0x20000a, self::COLUMN_ENDPOINT => 0xa000ff); + return array(self::COLUMN_ID => 0x3020000a, self::COLUMN_EVENT_ID => 0x20000a, self::COLUMN_USER_ID => 0x20000a, self::COLUMN_TENANT_ID => 0x40a00040, self::COLUMN_STATUS => 0x20000a, self::COLUMN_NAME => 0xa00020, self::COLUMN_ENDPOINT => 0xa000ff); } /** - * @return array<\Fusio\Impl\Table\Generated\EventSubscriptionRow> + * @return array<\Fusio\Impl\Table\Generated\WebhookRow> * @throws \PSX\Sql\Exception\QueryException */ public function findAll(?\PSX\Sql\Condition $condition = null, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array @@ -30,7 +32,7 @@ public function findAll(?\PSX\Sql\Condition $condition = null, ?int $startIndex return $this->doFindAll($condition, $startIndex, $count, $sortBy, $sortOrder); } /** - * @return array<\Fusio\Impl\Table\Generated\EventSubscriptionRow> + * @return array<\Fusio\Impl\Table\Generated\WebhookRow> * @throws \PSX\Sql\Exception\QueryException */ public function findBy(\PSX\Sql\Condition $condition, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array @@ -40,21 +42,21 @@ public function findBy(\PSX\Sql\Condition $condition, ?int $startIndex = null, ? /** * @throws \PSX\Sql\Exception\QueryException */ - public function findOneBy(\PSX\Sql\Condition $condition) : ?\Fusio\Impl\Table\Generated\EventSubscriptionRow + public function findOneBy(\PSX\Sql\Condition $condition) : ?\Fusio\Impl\Table\Generated\WebhookRow { return $this->doFindOneBy($condition); } /** * @throws \PSX\Sql\Exception\QueryException */ - public function find(int $id) : ?\Fusio\Impl\Table\Generated\EventSubscriptionRow + public function find(int $id) : ?\Fusio\Impl\Table\Generated\WebhookRow { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('id', $id); return $this->doFindOneBy($condition); } /** - * @return array<\Fusio\Impl\Table\Generated\EventSubscriptionRow> + * @return array<\Fusio\Impl\Table\Generated\WebhookRow> * @throws \PSX\Sql\Exception\QueryException */ public function findById(int $value, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array @@ -66,7 +68,7 @@ public function findById(int $value, ?int $startIndex = null, ?int $count = null /** * @throws \PSX\Sql\Exception\QueryException */ - public function findOneById(int $value) : ?\Fusio\Impl\Table\Generated\EventSubscriptionRow + public function findOneById(int $value) : ?\Fusio\Impl\Table\Generated\WebhookRow { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('id', $value); @@ -75,7 +77,7 @@ public function findOneById(int $value) : ?\Fusio\Impl\Table\Generated\EventSubs /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function updateById(int $value, \Fusio\Impl\Table\Generated\EventSubscriptionRow $record) : int + public function updateById(int $value, \Fusio\Impl\Table\Generated\WebhookRow $record) : int { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('id', $value); @@ -91,7 +93,7 @@ public function deleteById(int $value) : int return $this->doDeleteBy($condition); } /** - * @return array<\Fusio\Impl\Table\Generated\EventSubscriptionRow> + * @return array<\Fusio\Impl\Table\Generated\WebhookRow> * @throws \PSX\Sql\Exception\QueryException */ public function findByEventId(int $value, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array @@ -103,7 +105,7 @@ public function findByEventId(int $value, ?int $startIndex = null, ?int $count = /** * @throws \PSX\Sql\Exception\QueryException */ - public function findOneByEventId(int $value) : ?\Fusio\Impl\Table\Generated\EventSubscriptionRow + public function findOneByEventId(int $value) : ?\Fusio\Impl\Table\Generated\WebhookRow { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('event_id', $value); @@ -112,7 +114,7 @@ public function findOneByEventId(int $value) : ?\Fusio\Impl\Table\Generated\Even /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function updateByEventId(int $value, \Fusio\Impl\Table\Generated\EventSubscriptionRow $record) : int + public function updateByEventId(int $value, \Fusio\Impl\Table\Generated\WebhookRow $record) : int { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('event_id', $value); @@ -128,7 +130,7 @@ public function deleteByEventId(int $value) : int return $this->doDeleteBy($condition); } /** - * @return array<\Fusio\Impl\Table\Generated\EventSubscriptionRow> + * @return array<\Fusio\Impl\Table\Generated\WebhookRow> * @throws \PSX\Sql\Exception\QueryException */ public function findByUserId(int $value, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array @@ -140,7 +142,7 @@ public function findByUserId(int $value, ?int $startIndex = null, ?int $count = /** * @throws \PSX\Sql\Exception\QueryException */ - public function findOneByUserId(int $value) : ?\Fusio\Impl\Table\Generated\EventSubscriptionRow + public function findOneByUserId(int $value) : ?\Fusio\Impl\Table\Generated\WebhookRow { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('user_id', $value); @@ -149,7 +151,7 @@ public function findOneByUserId(int $value) : ?\Fusio\Impl\Table\Generated\Event /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function updateByUserId(int $value, \Fusio\Impl\Table\Generated\EventSubscriptionRow $record) : int + public function updateByUserId(int $value, \Fusio\Impl\Table\Generated\WebhookRow $record) : int { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('user_id', $value); @@ -165,7 +167,44 @@ public function deleteByUserId(int $value) : int return $this->doDeleteBy($condition); } /** - * @return array<\Fusio\Impl\Table\Generated\EventSubscriptionRow> + * @return array<\Fusio\Impl\Table\Generated\WebhookRow> + * @throws \PSX\Sql\Exception\QueryException + */ + public function findByTenantId(string $value, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array + { + $condition = \PSX\Sql\Condition::withAnd(); + $condition->like('tenant_id', $value); + return $this->doFindBy($condition, $startIndex, $count, $sortBy, $sortOrder); + } + /** + * @throws \PSX\Sql\Exception\QueryException + */ + public function findOneByTenantId(string $value) : ?\Fusio\Impl\Table\Generated\WebhookRow + { + $condition = \PSX\Sql\Condition::withAnd(); + $condition->like('tenant_id', $value); + return $this->doFindOneBy($condition); + } + /** + * @throws \PSX\Sql\Exception\ManipulationException + */ + public function updateByTenantId(string $value, \Fusio\Impl\Table\Generated\WebhookRow $record) : int + { + $condition = \PSX\Sql\Condition::withAnd(); + $condition->like('tenant_id', $value); + return $this->doUpdateBy($condition, $record->toRecord()); + } + /** + * @throws \PSX\Sql\Exception\ManipulationException + */ + public function deleteByTenantId(string $value) : int + { + $condition = \PSX\Sql\Condition::withAnd(); + $condition->like('tenant_id', $value); + return $this->doDeleteBy($condition); + } + /** + * @return array<\Fusio\Impl\Table\Generated\WebhookRow> * @throws \PSX\Sql\Exception\QueryException */ public function findByStatus(int $value, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array @@ -177,7 +216,7 @@ public function findByStatus(int $value, ?int $startIndex = null, ?int $count = /** * @throws \PSX\Sql\Exception\QueryException */ - public function findOneByStatus(int $value) : ?\Fusio\Impl\Table\Generated\EventSubscriptionRow + public function findOneByStatus(int $value) : ?\Fusio\Impl\Table\Generated\WebhookRow { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('status', $value); @@ -186,7 +225,7 @@ public function findOneByStatus(int $value) : ?\Fusio\Impl\Table\Generated\Event /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function updateByStatus(int $value, \Fusio\Impl\Table\Generated\EventSubscriptionRow $record) : int + public function updateByStatus(int $value, \Fusio\Impl\Table\Generated\WebhookRow $record) : int { $condition = \PSX\Sql\Condition::withAnd(); $condition->equals('status', $value); @@ -202,7 +241,44 @@ public function deleteByStatus(int $value) : int return $this->doDeleteBy($condition); } /** - * @return array<\Fusio\Impl\Table\Generated\EventSubscriptionRow> + * @return array<\Fusio\Impl\Table\Generated\WebhookRow> + * @throws \PSX\Sql\Exception\QueryException + */ + public function findByName(string $value, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array + { + $condition = \PSX\Sql\Condition::withAnd(); + $condition->like('name', $value); + return $this->doFindBy($condition, $startIndex, $count, $sortBy, $sortOrder); + } + /** + * @throws \PSX\Sql\Exception\QueryException + */ + public function findOneByName(string $value) : ?\Fusio\Impl\Table\Generated\WebhookRow + { + $condition = \PSX\Sql\Condition::withAnd(); + $condition->like('name', $value); + return $this->doFindOneBy($condition); + } + /** + * @throws \PSX\Sql\Exception\ManipulationException + */ + public function updateByName(string $value, \Fusio\Impl\Table\Generated\WebhookRow $record) : int + { + $condition = \PSX\Sql\Condition::withAnd(); + $condition->like('name', $value); + return $this->doUpdateBy($condition, $record->toRecord()); + } + /** + * @throws \PSX\Sql\Exception\ManipulationException + */ + public function deleteByName(string $value) : int + { + $condition = \PSX\Sql\Condition::withAnd(); + $condition->like('name', $value); + return $this->doDeleteBy($condition); + } + /** + * @return array<\Fusio\Impl\Table\Generated\WebhookRow> * @throws \PSX\Sql\Exception\QueryException */ public function findByEndpoint(string $value, ?int $startIndex = null, ?int $count = null, ?string $sortBy = null, ?\PSX\Sql\OrderBy $sortOrder = null) : array @@ -214,7 +290,7 @@ public function findByEndpoint(string $value, ?int $startIndex = null, ?int $cou /** * @throws \PSX\Sql\Exception\QueryException */ - public function findOneByEndpoint(string $value) : ?\Fusio\Impl\Table\Generated\EventSubscriptionRow + public function findOneByEndpoint(string $value) : ?\Fusio\Impl\Table\Generated\WebhookRow { $condition = \PSX\Sql\Condition::withAnd(); $condition->like('endpoint', $value); @@ -223,7 +299,7 @@ public function findOneByEndpoint(string $value) : ?\Fusio\Impl\Table\Generated\ /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function updateByEndpoint(string $value, \Fusio\Impl\Table\Generated\EventSubscriptionRow $record) : int + public function updateByEndpoint(string $value, \Fusio\Impl\Table\Generated\WebhookRow $record) : int { $condition = \PSX\Sql\Condition::withAnd(); $condition->like('endpoint', $value); @@ -241,28 +317,28 @@ public function deleteByEndpoint(string $value) : int /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function create(\Fusio\Impl\Table\Generated\EventSubscriptionRow $record) : int + public function create(\Fusio\Impl\Table\Generated\WebhookRow $record) : int { return $this->doCreate($record->toRecord()); } /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function update(\Fusio\Impl\Table\Generated\EventSubscriptionRow $record) : int + public function update(\Fusio\Impl\Table\Generated\WebhookRow $record) : int { return $this->doUpdate($record->toRecord()); } /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function updateBy(\PSX\Sql\Condition $condition, \Fusio\Impl\Table\Generated\EventSubscriptionRow $record) : int + public function updateBy(\PSX\Sql\Condition $condition, \Fusio\Impl\Table\Generated\WebhookRow $record) : int { return $this->doUpdateBy($condition, $record->toRecord()); } /** * @throws \PSX\Sql\Exception\ManipulationException */ - public function delete(\Fusio\Impl\Table\Generated\EventSubscriptionRow $record) : int + public function delete(\Fusio\Impl\Table\Generated\WebhookRow $record) : int { return $this->doDelete($record->toRecord()); } @@ -276,8 +352,8 @@ public function deleteBy(\PSX\Sql\Condition $condition) : int /** * @param array $row */ - protected function newRecord(array $row) : \Fusio\Impl\Table\Generated\EventSubscriptionRow + protected function newRecord(array $row) : \Fusio\Impl\Table\Generated\WebhookRow { - return \Fusio\Impl\Table\Generated\EventSubscriptionRow::from($row); + return \Fusio\Impl\Table\Generated\WebhookRow::from($row); } } \ No newline at end of file diff --git a/src/Table/Identity.php b/src/Table/Identity.php index 0d9087fd1..7733fbbe9 100644 --- a/src/Table/Identity.php +++ b/src/Table/Identity.php @@ -20,6 +20,7 @@ namespace Fusio\Impl\Table; +use Fusio\Impl\Table\Generated\EventRow; use Fusio\Impl\Table\Generated\IdentityRow; use PSX\Sql\Condition; @@ -37,14 +38,27 @@ class Identity extends Generated\IdentityTable public function findOneByIdentifier(?string $tenantId, string $id): ?IdentityRow { - $condition = Condition::withAnd(); - $condition->equals(self::COLUMN_TENANT_ID, $tenantId); - if (str_starts_with($id, '~')) { - $condition->equals(self::COLUMN_NAME, urldecode(substr($id, 1))); + return $this->findOneByTenantAndName($tenantId, urldecode(substr($id, 1))); } else { - $condition->equals(self::COLUMN_ID, (int) $id); + return $this->findOneByTenantAndId($tenantId, (int) $id); } + } + + public function findOneByTenantAndId(?string $tenantId, int $id): ?IdentityRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_ID, $id); + + return $this->findOneBy($condition); + } + + public function findOneByTenantAndName(?string $tenantId, string $name): ?IdentityRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_NAME, $name); return $this->findOneBy($condition); } diff --git a/src/Table/Log.php b/src/Table/Log.php index 17d47ae18..6a32e36fc 100644 --- a/src/Table/Log.php +++ b/src/Table/Log.php @@ -20,6 +20,7 @@ namespace Fusio\Impl\Table; +use Fusio\Impl\Table\Generated\IdentityRow; use Fusio\Impl\Table\Generated\LogRow; use PSX\Sql\Condition; @@ -32,7 +33,12 @@ */ class Log extends Generated\LogTable { - public function findOneByIdentifier(?string $tenantId, int $id): ?LogRow + public function findOneByIdentifier(?string $tenantId, string $id): ?LogRow + { + return $this->findOneByTenantAndId($tenantId, (int) $id); + } + + public function findOneByTenantAndId(?string $tenantId, int $id): ?LogRow { $condition = Condition::withAnd(); $condition->equals(self::COLUMN_TENANT_ID, $tenantId); diff --git a/src/Table/Operation.php b/src/Table/Operation.php index b0a1727cd..99c72bf91 100644 --- a/src/Table/Operation.php +++ b/src/Table/Operation.php @@ -21,6 +21,7 @@ namespace Fusio\Impl\Table; use Fusio\Impl\Table\Generated\CronjobRow; +use Fusio\Impl\Table\Generated\IdentityRow; use Fusio\Impl\Table\Generated\OperationRow; use PSX\Sql\Condition; @@ -38,16 +39,11 @@ class Operation extends Generated\OperationTable public function findOneByIdentifier(?string $tenantId, string $id): ?OperationRow { - $condition = Condition::withAnd(); - $condition->equals(self::COLUMN_TENANT_ID, $tenantId); - if (str_starts_with($id, '~')) { - $condition->equals(self::COLUMN_NAME, urldecode(substr($id, 1))); + return $this->findOneByTenantAndName($tenantId, urldecode(substr($id, 1))); } else { - $condition->equals(self::COLUMN_ID, (int) $id); + return $this->findOneByTenantAndId($tenantId, (int) $id); } - - return $this->findOneBy($condition); } public function findOneByTenantAndId(?string $tenantId, int $id): ?OperationRow @@ -55,6 +51,16 @@ public function findOneByTenantAndId(?string $tenantId, int $id): ?OperationRow $condition = Condition::withAnd(); $condition->equals(self::COLUMN_TENANT_ID, $tenantId); $condition->equals(self::COLUMN_ID, $id); + + return $this->findOneBy($condition); + } + + public function findOneByTenantAndName(?string $tenantId, string $name): ?OperationRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_NAME, $name); + return $this->findOneBy($condition); } diff --git a/src/Table/Page.php b/src/Table/Page.php index fd8495350..842b6eed0 100644 --- a/src/Table/Page.php +++ b/src/Table/Page.php @@ -21,6 +21,7 @@ namespace Fusio\Impl\Table; use Fusio\Impl\Table\Generated\ConfigRow; +use Fusio\Impl\Table\Generated\OperationRow; use Fusio\Impl\Table\Generated\PageRow; use PSX\Sql\Condition; @@ -39,14 +40,27 @@ class Page extends Generated\PageTable public function findOneByIdentifier(?string $tenantId, string $id): ?PageRow { - $condition = Condition::withAnd(); - $condition->equals(self::COLUMN_TENANT_ID, $tenantId); - if (str_starts_with($id, '~')) { - $condition->equals(self::COLUMN_SLUG, urldecode(substr($id, 1))); + return $this->findOneByTenantAndSlug($tenantId, urldecode(substr($id, 1))); } else { - $condition->equals(self::COLUMN_ID, (int) $id); + return $this->findOneByTenantAndId($tenantId, (int) $id); } + } + + public function findOneByTenantAndId(?string $tenantId, int $id): ?PageRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_ID, $id); + + return $this->findOneBy($condition); + } + + public function findOneByTenantAndSlug(?string $tenantId, string $slug): ?PageRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_SLUG, $slug); return $this->findOneBy($condition); } diff --git a/src/Table/Plan.php b/src/Table/Plan.php index 347580d69..92e2d79ed 100644 --- a/src/Table/Plan.php +++ b/src/Table/Plan.php @@ -20,6 +20,7 @@ namespace Fusio\Impl\Table; +use Fusio\Impl\Table\Generated\OperationRow; use Fusio\Impl\Table\Generated\PageRow; use Fusio\Impl\Table\Generated\PlanRow; use PSX\Sql\Condition; @@ -38,16 +39,11 @@ class Plan extends Generated\PlanTable public function findOneByIdentifier(?string $tenantId, string $id): ?PlanRow { - $condition = Condition::withAnd(); - $condition->equals(self::COLUMN_TENANT_ID, $tenantId); - if (str_starts_with($id, '~')) { - $condition->equals(self::COLUMN_NAME, urldecode(substr($id, 1))); + return $this->findOneByTenantAndName($tenantId, urldecode(substr($id, 1))); } else { - $condition->equals(self::COLUMN_ID, (int) $id); + return $this->findOneByTenantAndId($tenantId, (int) $id); } - - return $this->findOneBy($condition); } public function findOneByTenantAndId(?string $tenantId, int $id): ?PlanRow @@ -55,6 +51,16 @@ public function findOneByTenantAndId(?string $tenantId, int $id): ?PlanRow $condition = Condition::withAnd(); $condition->equals(self::COLUMN_TENANT_ID, $tenantId); $condition->equals(self::COLUMN_ID, $id); + + return $this->findOneBy($condition); + } + + public function findOneByTenantAndName(?string $tenantId, string $name): ?PlanRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_NAME, $name); + return $this->findOneBy($condition); } diff --git a/src/Table/Rate.php b/src/Table/Rate.php index 87dfbf8d7..14b238c04 100644 --- a/src/Table/Rate.php +++ b/src/Table/Rate.php @@ -38,14 +38,27 @@ class Rate extends Generated\RateTable public function findOneByIdentifier(?string $tenantId, string $id): ?RateRow { - $condition = Condition::withAnd(); - $condition->equals(self::COLUMN_TENANT_ID, $tenantId); - if (str_starts_with($id, '~')) { - $condition->equals(self::COLUMN_NAME, urldecode(substr($id, 1))); + return $this->findOneByTenantAndName($tenantId, urldecode(substr($id, 1))); } else { - $condition->equals(self::COLUMN_ID, (int) $id); + return $this->findOneByTenantAndId($tenantId, (int) $id); } + } + + public function findOneByTenantAndId(?string $tenantId, int $id): ?RateRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_ID, $id); + + return $this->findOneBy($condition); + } + + public function findOneByTenantAndName(?string $tenantId, string $name): ?RateRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_NAME, $name); return $this->findOneBy($condition); } diff --git a/src/Table/Role.php b/src/Table/Role.php index 715d2ca41..4be4b9785 100644 --- a/src/Table/Role.php +++ b/src/Table/Role.php @@ -38,14 +38,27 @@ class Role extends Generated\RoleTable public function findOneByIdentifier(?string $tenantId, string $id): ?RoleRow { - $condition = Condition::withAnd(); - $condition->equals(self::COLUMN_TENANT_ID, $tenantId); - if (str_starts_with($id, '~')) { - $condition->equals(self::COLUMN_NAME, urldecode(substr($id, 1))); + return $this->findOneByTenantAndName($tenantId, urldecode(substr($id, 1))); } else { - $condition->equals(self::COLUMN_ID, (int) $id); + return $this->findOneByTenantAndId($tenantId, (int) $id); } + } + + public function findOneByTenantAndId(?string $tenantId, int $id): ?RoleRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_ID, $id); + + return $this->findOneBy($condition); + } + + public function findOneByTenantAndName(?string $tenantId, string $name): ?RoleRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_NAME, $name); return $this->findOneBy($condition); } diff --git a/src/Table/Schema.php b/src/Table/Schema.php index d6344d99d..078df6e95 100644 --- a/src/Table/Schema.php +++ b/src/Table/Schema.php @@ -38,14 +38,27 @@ class Schema extends Generated\SchemaTable public function findOneByIdentifier(?string $tenantId, string $id): ?SchemaRow { - $condition = Condition::withAnd(); - $condition->equals(self::COLUMN_TENANT_ID, $tenantId); - if (str_starts_with($id, '~')) { - $condition->equals(self::COLUMN_NAME, urldecode(substr($id, 1))); + return $this->findOneByTenantAndName($tenantId, urldecode(substr($id, 1))); } else { - $condition->equals(self::COLUMN_ID, (int) $id); + return $this->findOneByTenantAndId($tenantId, (int) $id); } + } + + public function findOneByTenantAndId(?string $tenantId, int $id): ?SchemaRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_ID, $id); + + return $this->findOneBy($condition); + } + + public function findOneByTenantAndName(?string $tenantId, string $name): ?SchemaRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_NAME, $name); return $this->findOneBy($condition); } diff --git a/src/Table/Scope.php b/src/Table/Scope.php index e69ac6dbf..36c85d989 100644 --- a/src/Table/Scope.php +++ b/src/Table/Scope.php @@ -20,6 +20,7 @@ namespace Fusio\Impl\Table; +use Fusio\Impl\Table\Generated\SchemaRow; use Fusio\Impl\Table\Generated\ScopeRow; use PSX\Sql\Condition; use PSX\Sql\OrderBy; @@ -38,14 +39,18 @@ class Scope extends Generated\ScopeTable public function findOneByIdentifier(?string $tenantId, string $id): ?ScopeRow { - $condition = Condition::withAnd(); - $condition->equals(self::COLUMN_TENANT_ID, $tenantId); - if (str_starts_with($id, '~')) { - $condition->equals(self::COLUMN_NAME, urldecode(substr($id, 1))); + return $this->findOneByTenantAndName($tenantId, urldecode(substr($id, 1))); } else { - $condition->equals(self::COLUMN_ID, (int) $id); + return $this->findOneByTenantAndId($tenantId, (int) $id); } + } + + public function findOneByTenantAndId(?string $tenantId, int $id): ?ScopeRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_ID, $id); return $this->findOneBy($condition); } @@ -55,6 +60,7 @@ public function findOneByTenantAndName(?string $tenantId, string $name): ?ScopeR $condition = Condition::withAnd(); $condition->equals(self::COLUMN_TENANT_ID, $tenantId); $condition->equals(self::COLUMN_NAME, $name); + return $this->findOneBy($condition); } diff --git a/src/Table/Transaction.php b/src/Table/Transaction.php index d58f0024b..1ade0619f 100644 --- a/src/Table/Transaction.php +++ b/src/Table/Transaction.php @@ -20,6 +20,7 @@ namespace Fusio\Impl\Table; +use Fusio\Impl\Table\Generated\ScopeRow; use Fusio\Impl\Table\Generated\TransactionRow; use PSX\Sql\Condition; @@ -32,7 +33,16 @@ */ class Transaction extends Generated\TransactionTable { - public function findOneByIdentifier(?string $tenantId, int $id): ?TransactionRow + public function findOneByIdentifier(?string $tenantId, string $id): ?TransactionRow + { + if (str_starts_with($id, '~')) { + return $this->findOneByTenantAndTransactionId($tenantId, urldecode(substr($id, 1))); + } else { + return $this->findOneByTenantAndId($tenantId, (int) $id); + } + } + + public function findOneByTenantAndId(?string $tenantId, int $id): ?TransactionRow { $condition = Condition::withAnd(); $condition->equals(self::COLUMN_TENANT_ID, $tenantId); @@ -40,4 +50,13 @@ public function findOneByIdentifier(?string $tenantId, int $id): ?TransactionRow return $this->findOneBy($condition); } + + public function findOneByTenantAndTransactionId(?string $tenantId, string $transactionId): ?TransactionRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_TRANSACTION_ID, $transactionId); + + return $this->findOneBy($condition); + } } diff --git a/src/Table/User.php b/src/Table/User.php index 8cf7ce8ab..a86abd5a1 100644 --- a/src/Table/User.php +++ b/src/Table/User.php @@ -60,6 +60,15 @@ public function findOneByTenantAndId(?string $tenantId, int $id): ?UserRow return $this->findOneBy($condition); } + public function findOneByTenantAndName(?string $tenantId, string $name): ?UserRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_NAME, $name); + + return $this->findOneBy($condition); + } + public function findOneByTenantAndEmail(?string $tenantId, string $email): ?UserRow { $condition = Condition::withAnd(); diff --git a/src/Table/Webhook.php b/src/Table/Webhook.php new file mode 100644 index 000000000..49eec10c5 --- /dev/null +++ b/src/Table/Webhook.php @@ -0,0 +1,108 @@ + + * + * Copyright 2015-2023 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Table; + +use Fusio\Impl\Table\Generated\ScopeRow; +use Fusio\Impl\Table\Generated\WebhookRow; +use PSX\Sql\Condition; + +/** + * Subscription + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +class Webhook extends Generated\WebhookTable +{ + const STATUS_ACTIVE = 1; + const STATUS_INACTIVE = 2; + + public function findOneByIdentifier(?string $tenantId, string $id): ?WebhookRow + { + if (str_starts_with($id, '~')) { + return $this->findOneByTenantAndName($tenantId, urldecode(substr($id, 1))); + } else { + return $this->findOneByTenantAndId($tenantId, (int) $id); + } + } + + public function findOneByTenantAndId(?string $tenantId, int $id): ?WebhookRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_ID, $id); + + return $this->findOneBy($condition); + } + + public function findOneByTenantAndName(?string $tenantId, string $name): ?WebhookRow + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_NAME, $name); + + return $this->findOneBy($condition); + } + + public function getWebhooksForEvent(int $eventId): array + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_EVENT_ID, $eventId); + $condition->equals(self::COLUMN_STATUS, self::STATUS_ACTIVE); + + $queryBuilder = $this->connection->createQueryBuilder() + ->select([ + 'webhook.' . self::COLUMN_ID, + 'webhook.' . self::COLUMN_ENDPOINT, + ]) + ->from('fusio_webhook', 'webhook') + ->where($condition->getExpression($this->connection->getDatabasePlatform())) + ->orderBy('webhook.' . self::COLUMN_ID, 'ASC') + ->setParameters($condition->getValues()); + + return $this->connection->fetchAllAssociative($queryBuilder->getSQL(), $queryBuilder->getParameters()); + } + + public function getWebhookCount(?string $tenantId, int $userId): int + { + $condition = Condition::withAnd(); + $condition->equals(self::COLUMN_TENANT_ID, $tenantId); + $condition->equals(self::COLUMN_USER_ID, $userId); + + $queryBuilder = $this->connection->createQueryBuilder() + ->select([ + 'COUNT(webhook.id) AS cnt', + ]) + ->from('fusio_webhook', 'webhook') + ->where($condition->getExpression($this->connection->getDatabasePlatform())) + ->setParameters($condition->getValues()); + + return (int) $this->connection->fetchOne($queryBuilder->getSQL(), $queryBuilder->getParameters()); + } + + public function deleteAllResponses(int $webhookId): void + { + $this->connection->executeStatement('DELETE FROM fusio_webhook_response WHERE webhook_id = :webhook_id', [ + 'webhook_id' => $webhookId + ]); + } +} diff --git a/src/Table/Event/Response.php b/src/Table/Webhook/Response.php similarity index 88% rename from src/Table/Event/Response.php rename to src/Table/Webhook/Response.php index 666e58b9c..54593254a 100644 --- a/src/Table/Event/Response.php +++ b/src/Table/Webhook/Response.php @@ -18,7 +18,7 @@ * limitations under the License. */ -namespace Fusio\Impl\Table\Event; +namespace Fusio\Impl\Table\Webhook; use Fusio\Impl\Table\Generated; use PSX\Sql\Condition; @@ -30,16 +30,16 @@ * @license http://www.apache.org/licenses/LICENSE-2.0 * @link https://www.fusio-project.org */ -class Response extends Generated\EventResponseTable +class Response extends Generated\WebhookResponseTable { public const STATUS_PENDING = 1; public const STATUS_DONE = 2; public const STATUS_EXCEEDED = 3; - public function getAllBySubscription(int $subscriptionId): array + public function getAllByWebhook(int $webhookId): array { $condition = Condition::withAnd(); - $condition->equals(self::COLUMN_SUBSCRIPTION_ID, $subscriptionId); + $condition->equals(self::COLUMN_WEBHOOK_ID, $webhookId); $queryBuilder = $this->connection->createQueryBuilder() ->select([ @@ -50,7 +50,7 @@ public function getAllBySubscription(int $subscriptionId): array 'response.' . self::COLUMN_BODY, 'response.' . self::COLUMN_EXECUTE_DATE, ]) - ->from('fusio_event_response', 'response') + ->from('fusio_webhook_response', 'response') ->where($condition->getExpression($this->connection->getDatabasePlatform())) ->orderBy('response.' . self::COLUMN_EXECUTE_DATE, 'DESC') ->orderBy('response.' . self::COLUMN_ID, 'ASC') diff --git a/src/Tenant/UnlimitedLimiter.php b/src/Tenant/UnlimitedLimiter.php new file mode 100644 index 000000000..91e9dcf1a --- /dev/null +++ b/src/Tenant/UnlimitedLimiter.php @@ -0,0 +1,113 @@ + + * + * Copyright 2015-2023 Christoph Kappestein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +namespace Fusio\Impl\Tenant; + +use Fusio\Impl\Service\Tenant\LimiterInterface; + +/** + * UnlimitedLimiter + * + * @author Christoph Kappestein + * @license http://www.apache.org/licenses/LICENSE-2.0 + * @link https://www.fusio-project.org + */ +class UnlimitedLimiter implements LimiterInterface +{ + public function getActionCount(): int + { + return PHP_INT_MAX; + } + + public function getAppCount(): int + { + return PHP_INT_MAX; + } + + public function getCategoryCount(): int + { + return PHP_INT_MAX; + } + + public function getConnectionCount(): int + { + return PHP_INT_MAX; + } + + public function getCronjobCount(): int + { + return PHP_INT_MAX; + } + + public function getEventCount(): int + { + return PHP_INT_MAX; + } + + public function getIdentityCount(): int + { + return PHP_INT_MAX; + } + + public function getOperationCount(): int + { + return PHP_INT_MAX; + } + + public function getPageCount(): int + { + return PHP_INT_MAX; + } + + public function getPlanCount(): int + { + return PHP_INT_MAX; + } + + public function getRateCount(): int + { + return PHP_INT_MAX; + } + + public function getRoleCount(): int + { + return PHP_INT_MAX; + } + + public function getSchemaCount(): int + { + return PHP_INT_MAX; + } + + public function getScopeCount(): int + { + return PHP_INT_MAX; + } + + public function getUserCount(): int + { + return PHP_INT_MAX; + } + + public function getWebhookCount(): int + { + return PHP_INT_MAX; + } +} diff --git a/tests/Authorization/ClientCredentialsTest.php b/tests/Authorization/ClientCredentialsTest.php index 6f5a39d32..41c1d52b8 100644 --- a/tests/Authorization/ClientCredentialsTest.php +++ b/tests/Authorization/ClientCredentialsTest.php @@ -50,7 +50,7 @@ public function testPost() ], $body); // if we provide no explicit scopes we get all scopes assigned to the user - $this->assertAccessToken($response, 'backend,backend.account,backend.action,backend.app,backend.audit,backend.category,backend.config,backend.connection,backend.cronjob,backend.dashboard,backend.event,backend.generator,backend.identity,backend.log,backend.marketplace,backend.operation,backend.page,backend.plan,backend.rate,backend.role,backend.schema,backend.scope,backend.sdk,backend.statistic,backend.tenant,backend.transaction,backend.trash,backend.user,consumer,consumer.account,consumer.app,consumer.event,consumer.grant,consumer.identity,consumer.log,consumer.page,consumer.payment,consumer.plan,consumer.scope,consumer.subscription,consumer.transaction,authorization,foo,bar', 4); + $this->assertAccessToken($response, 'backend,backend.account,backend.action,backend.app,backend.audit,backend.category,backend.config,backend.connection,backend.cronjob,backend.dashboard,backend.event,backend.generator,backend.identity,backend.log,backend.marketplace,backend.operation,backend.page,backend.plan,backend.rate,backend.role,backend.schema,backend.scope,backend.sdk,backend.statistic,backend.tenant,backend.transaction,backend.trash,backend.user,backend.webhook,consumer,consumer.account,consumer.app,consumer.event,consumer.grant,consumer.identity,consumer.log,consumer.page,consumer.payment,consumer.plan,consumer.scope,consumer.transaction,consumer.webhook,authorization,foo,bar', 4); } public function testPostSpecificScope() @@ -74,7 +74,7 @@ public function testPostEmail() 'Content-Type' => 'application/x-www-form-urlencoded', ], $body); - $this->assertAccessToken($response, 'backend,backend.account,backend.action,backend.app,backend.audit,backend.category,backend.config,backend.connection,backend.cronjob,backend.dashboard,backend.event,backend.generator,backend.identity,backend.log,backend.marketplace,backend.operation,backend.page,backend.plan,backend.rate,backend.role,backend.schema,backend.scope,backend.sdk,backend.statistic,backend.tenant,backend.transaction,backend.trash,backend.user,consumer,consumer.account,consumer.app,consumer.event,consumer.grant,consumer.identity,consumer.log,consumer.page,consumer.payment,consumer.plan,consumer.scope,consumer.subscription,consumer.transaction,authorization,foo,bar', 4); + $this->assertAccessToken($response, 'backend,backend.account,backend.action,backend.app,backend.audit,backend.category,backend.config,backend.connection,backend.cronjob,backend.dashboard,backend.event,backend.generator,backend.identity,backend.log,backend.marketplace,backend.operation,backend.page,backend.plan,backend.rate,backend.role,backend.schema,backend.scope,backend.sdk,backend.statistic,backend.tenant,backend.transaction,backend.trash,backend.user,backend.webhook,consumer,consumer.account,consumer.app,consumer.event,consumer.grant,consumer.identity,consumer.log,consumer.page,consumer.payment,consumer.plan,consumer.scope,consumer.transaction,consumer.webhook,authorization,foo,bar', 4); } /** @@ -90,7 +90,7 @@ public function testPostConsumer() ], $body); // we receive only the authorization scope since out user has not the backend scope - $this->assertAccessToken($response, 'consumer,consumer.account,consumer.app,consumer.event,consumer.grant,consumer.identity,consumer.log,consumer.page,consumer.payment,consumer.plan,consumer.scope,consumer.subscription,consumer.transaction,authorization,foo,bar', 2); + $this->assertAccessToken($response, 'consumer,consumer.account,consumer.app,consumer.event,consumer.grant,consumer.identity,consumer.log,consumer.page,consumer.payment,consumer.plan,consumer.scope,consumer.transaction,consumer.webhook,authorization,foo,bar', 2); } /** diff --git a/tests/Authorization/WhoamiTest.php b/tests/Authorization/WhoamiTest.php index 84ce3827a..8422c1a0f 100644 --- a/tests/Authorization/WhoamiTest.php +++ b/tests/Authorization/WhoamiTest.php @@ -84,6 +84,7 @@ public function testGet() "backend.transaction", "backend.trash", "backend.user", + "backend.webhook", "consumer", "consumer.account", "consumer.app", @@ -95,8 +96,8 @@ public function testGet() "consumer.payment", "consumer.plan", "consumer.scope", - "consumer.subscription", "consumer.transaction", + "consumer.webhook", "authorization", "default", "foo", diff --git a/tests/Backend/Api/Account/AccountTest.php b/tests/Backend/Api/Account/AccountTest.php index 9c57ebe07..888d5e9a6 100644 --- a/tests/Backend/Api/Account/AccountTest.php +++ b/tests/Backend/Api/Account/AccountTest.php @@ -85,6 +85,7 @@ public function testGet() "backend.transaction", "backend.trash", "backend.user", + "backend.webhook", "consumer", "consumer.account", "consumer.app", @@ -96,8 +97,8 @@ public function testGet() "consumer.payment", "consumer.plan", "consumer.scope", - "consumer.subscription", "consumer.transaction", + "consumer.webhook", "authorization", "foo", "bar" diff --git a/tests/Backend/Api/Connection/Introspection/GetEntitiesTest.php b/tests/Backend/Api/Connection/Introspection/GetEntitiesTest.php index 8a319e8d1..7e81c356b 100644 --- a/tests/Backend/Api/Connection/Introspection/GetEntitiesTest.php +++ b/tests/Backend/Api/Connection/Introspection/GetEntitiesTest.php @@ -61,8 +61,6 @@ public function testGet() "fusio_cronjob", "fusio_cronjob_error", "fusio_event", - "fusio_event_response", - "fusio_event_subscription", "fusio_identity", "fusio_identity_request", "fusio_log", @@ -84,6 +82,8 @@ public function testGet() "fusio_user", "fusio_user_grant", "fusio_user_scope", + "fusio_webhook", + "fusio_webhook_response", "messenger_messages" ] } diff --git a/tests/Backend/Api/Event/CollectionTest.php b/tests/Backend/Api/Event/CollectionTest.php index e2f1e4387..cba39c857 100644 --- a/tests/Backend/Api/Event/CollectionTest.php +++ b/tests/Backend/Api/Event/CollectionTest.php @@ -52,7 +52,7 @@ public function testGet() "itemsPerPage": 16, "entry": [ { - "id": 56, + "id": 53, "status": 1, "name": "foo-event", "description": "Foo event description", @@ -106,7 +106,7 @@ public function testPost() $row = $this->connection->fetchAssociative($sql); - $this->assertEquals(57, $row['id']); + $this->assertEquals(54, $row['id']); $this->assertEquals(1, $row['status']); $this->assertEquals('bar-event', $row['name']); $this->assertEquals('Test description', $row['description']); diff --git a/tests/Backend/Api/Plan/CollectionTest.php b/tests/Backend/Api/Plan/CollectionTest.php index e91409985..19c62868c 100644 --- a/tests/Backend/Api/Plan/CollectionTest.php +++ b/tests/Backend/Api/Plan/CollectionTest.php @@ -143,7 +143,7 @@ public function testPost() $result = $this->connection->fetchAllAssociative($sql, ['plan_id' => $row['id']]); $this->assertEquals(1, count($result)); - $this->assertEquals(45, $result[0]['scope_id']); + $this->assertEquals(46, $result[0]['scope_id']); } public function testPut() diff --git a/tests/Backend/Api/Plan/EntityTest.php b/tests/Backend/Api/Plan/EntityTest.php index 88372bd09..32e9f6627 100644 --- a/tests/Backend/Api/Plan/EntityTest.php +++ b/tests/Backend/Api/Plan/EntityTest.php @@ -185,7 +185,7 @@ public function testPut() $result = $this->connection->fetchAllAssociative($sql, ['plan_id' => 1]); $this->assertEquals(1, count($result)); - $this->assertEquals(46, $result[0]['scope_id']); + $this->assertEquals(47, $result[0]['scope_id']); } public function testDelete() diff --git a/tests/Backend/Api/Role/EntityTest.php b/tests/Backend/Api/Role/EntityTest.php index 15d048a66..8e2e167e0 100644 --- a/tests/Backend/Api/Role/EntityTest.php +++ b/tests/Backend/Api/Role/EntityTest.php @@ -72,8 +72,8 @@ public function testGet() "consumer.payment", "consumer.plan", "consumer.scope", - "consumer.subscription", "consumer.transaction", + "consumer.webhook", "authorization", "default" ] @@ -110,8 +110,8 @@ public function testGetByName() "consumer.payment", "consumer.plan", "consumer.scope", - "consumer.subscription", "consumer.transaction", + "consumer.webhook", "authorization", "default" ] diff --git a/tests/Backend/Api/Scope/CategoriesTest.php b/tests/Backend/Api/Scope/CategoriesTest.php index 4ccc643fd..0a8870681 100644 --- a/tests/Backend/Api/Scope/CategoriesTest.php +++ b/tests/Backend/Api/Scope/CategoriesTest.php @@ -202,6 +202,11 @@ public function testGet() "id": 31, "name": "backend.user", "description": "" + }, + { + "id": 32, + "name": "backend.webhook", + "description": "" } ] }, @@ -215,63 +220,63 @@ public function testGet() "description": "" }, { - "id": 42, + "id": 43, "name": "consumer.account", "description": "" }, { - "id": 32, + "id": 33, "name": "consumer.app", "description": "" }, { - "id": 33, + "id": 34, "name": "consumer.event", "description": "" }, { - "id": 34, + "id": 35, "name": "consumer.grant", "description": "" }, { - "id": 43, + "id": 44, "name": "consumer.identity", "description": "" }, { - "id": 35, + "id": 36, "name": "consumer.log", "description": "" }, { - "id": 36, + "id": 37, "name": "consumer.page", "description": "" }, { - "id": 37, + "id": 38, "name": "consumer.payment", "description": "" }, { - "id": 38, + "id": 39, "name": "consumer.plan", "description": "" }, { - "id": 39, + "id": 40, "name": "consumer.scope", "description": "" }, { - "id": 40, - "name": "consumer.subscription", + "id": 42, + "name": "consumer.transaction", "description": "" }, { "id": 41, - "name": "consumer.transaction", + "name": "consumer.webhook", "description": "" } ] @@ -281,7 +286,7 @@ public function testGet() "name": "default", "scopes": [ { - "id": 46, + "id": 47, "name": "bar", "description": "Bar access" }, @@ -291,12 +296,12 @@ public function testGet() "description": "" }, { - "id": 45, + "id": 46, "name": "foo", "description": "Foo access" }, { - "id": 47, + "id": 48, "name": "plan_scope", "description": "Plan scope access" } @@ -307,7 +312,7 @@ public function testGet() "name": "system", "scopes": [ { - "id": 44, + "id": 45, "name": "system", "description": "" } diff --git a/tests/Backend/Api/Scope/CollectionTest.php b/tests/Backend/Api/Scope/CollectionTest.php index b00168142..bcd07d231 100644 --- a/tests/Backend/Api/Scope/CollectionTest.php +++ b/tests/Backend/Api/Scope/CollectionTest.php @@ -52,17 +52,17 @@ public function testGet() "itemsPerPage": 16, "entry": [ { - "id": 47, + "id": 48, "name": "plan_scope", "description": "Plan scope access" }, { - "id": 46, + "id": 47, "name": "bar", "description": "Bar access" }, { - "id": 45, + "id": 46, "name": "foo", "description": "Foo access", "metadata": { @@ -97,7 +97,7 @@ public function testGetSearch() "itemsPerPage": 16, "entry": [ { - "id": 45, + "id": 46, "name": "foo", "description": "Foo access", "metadata": { @@ -127,17 +127,17 @@ public function testGetCount() "itemsPerPage": 80, "entry": [ { - "id": 47, + "id": 48, "name": "plan_scope", "description": "Plan scope access" }, { - "id": 46, + "id": 47, "name": "bar", "description": "Bar access" }, { - "id": 45, + "id": 46, "name": "foo", "description": "Foo access", "metadata": { @@ -201,7 +201,7 @@ public function testPost() $row = $this->connection->fetchAssociative($sql); - $this->assertEquals(48, $row['id']); + $this->assertEquals(49, $row['id']); $this->assertEquals('test', $row['name']); $this->assertEquals('Test description', $row['description']); $this->assertJsonStringEqualsJsonString(json_encode($metadata), $row['metadata']); @@ -213,7 +213,7 @@ public function testPost() ->orderBy('id', 'DESC') ->getSQL(); - $scopeId = 48; + $scopeId = 49; $operations = $this->connection->fetchAllAssociative($sql, ['scope_id' => $scopeId]); $this->assertEquals([[ diff --git a/tests/Backend/Api/Scope/EntityTest.php b/tests/Backend/Api/Scope/EntityTest.php index 926388917..aeba9a9cb 100644 --- a/tests/Backend/Api/Scope/EntityTest.php +++ b/tests/Backend/Api/Scope/EntityTest.php @@ -57,49 +57,49 @@ public function testGet() $body = (string) $response->getBody(); $expect = <<getBody(); $expect = <<connection->fetchAllAssociative($sql, ['user_id' => 6]); - $this->assertEquals(43, count($userScopes)); + $this->assertEquals(44, count($userScopes)); } public function testPostNameExists() diff --git a/tests/Backend/Api/User/EntityTest.php b/tests/Backend/Api/User/EntityTest.php index ab4bcd31d..bbe86f475 100644 --- a/tests/Backend/Api/User/EntityTest.php +++ b/tests/Backend/Api/User/EntityTest.php @@ -82,8 +82,8 @@ public function testGet() "consumer.payment", "consumer.plan", "consumer.scope", - "consumer.subscription", "consumer.transaction", + "consumer.webhook", "authorization", "foo", "bar" @@ -208,7 +208,7 @@ public function testPut() $this->assertEquals(1, count($scopes)); $this->assertEquals(2, $scopes[0]['user_id']); - $this->assertEquals(46, $scopes[0]['scope_id']); + $this->assertEquals(47, $scopes[0]['scope_id']); } public function testDelete() diff --git a/tests/Backend/Api/Event/Subscription/CollectionTest.php b/tests/Backend/Api/Webhook/CollectionTest.php similarity index 84% rename from tests/Backend/Api/Event/Subscription/CollectionTest.php rename to tests/Backend/Api/Webhook/CollectionTest.php index 1fe9d4703..cc28a79df 100644 --- a/tests/Backend/Api/Event/Subscription/CollectionTest.php +++ b/tests/Backend/Api/Webhook/CollectionTest.php @@ -18,7 +18,7 @@ * limitations under the License. */ -namespace Fusio\Impl\Tests\Backend\Api\Event\Subscription; +namespace Fusio\Impl\Tests\Backend\Api\Webhook; use Fusio\Impl\Tests\Fixture; use PSX\Framework\Test\ControllerDbTestCase; @@ -48,7 +48,7 @@ public function getDataSet(): array public function testGet() { - $response = $this->sendRequest('/backend/event/subscription', 'GET', array( + $response = $this->sendRequest('/backend/webhook', 'GET', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer da250526d583edabca8ac2f99e37ee39aa02a3c076c0edc6929095e20ca18dcf' )); @@ -62,14 +62,16 @@ public function testGet() "entry": [ { "id": 2, - "eventId": {$this->eventId}, + "eventId": 53, "userId": 2, + "name": "pong", "endpoint": "http:\/\/www.fusio-project.org\/ping" }, { "id": 1, - "eventId": {$this->eventId}, + "eventId": 53, "userId": 1, + "name": "ping", "endpoint": "http:\/\/www.fusio-project.org\/ping" } ] @@ -82,12 +84,13 @@ public function testGet() public function testPost() { - $response = $this->sendRequest('/backend/event/subscription', 'POST', array( + $response = $this->sendRequest('/backend/webhook', 'POST', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer da250526d583edabca8ac2f99e37ee39aa02a3c076c0edc6929095e20ca18dcf' ), json_encode([ 'eventId' => $this->eventId, 'userId' => 1, + 'name' => 'foo', 'endpoint' => 'http://localhost', ])); @@ -95,7 +98,7 @@ public function testPost() $expect = <<<'JSON' { "success": true, - "message": "Subscription successfully created" + "message": "Webhook successfully created" } JSON; @@ -104,8 +107,8 @@ public function testPost() // check database $sql = $this->connection->createQueryBuilder() - ->select('id', 'event_id', 'user_id', 'endpoint') - ->from('fusio_event_subscription') + ->select('id', 'event_id', 'user_id', 'name', 'endpoint') + ->from('fusio_webhook') ->orderBy('id', 'DESC') ->setFirstResult(0) ->setMaxResults(1) @@ -114,14 +117,15 @@ public function testPost() $row = $this->connection->fetchAssociative($sql); $this->assertEquals(3, $row['id']); - $this->assertEquals(56, $row['event_id']); + $this->assertEquals(53, $row['event_id']); $this->assertEquals(1, $row['user_id']); + $this->assertEquals('foo', $row['name']); $this->assertEquals('http://localhost', $row['endpoint']); } public function testPut() { - $response = $this->sendRequest('/backend/event/subscription', 'PUT', array( + $response = $this->sendRequest('/backend/webhook', 'PUT', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer da250526d583edabca8ac2f99e37ee39aa02a3c076c0edc6929095e20ca18dcf' ), json_encode([ @@ -135,7 +139,7 @@ public function testPut() public function testDelete() { - $response = $this->sendRequest('/backend/event/subscription', 'DELETE', array( + $response = $this->sendRequest('/backend/webhook', 'DELETE', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer da250526d583edabca8ac2f99e37ee39aa02a3c076c0edc6929095e20ca18dcf' ), json_encode([ diff --git a/tests/Backend/Api/Event/Subscription/EntityTest.php b/tests/Backend/Api/Webhook/EntityTest.php similarity index 84% rename from tests/Backend/Api/Event/Subscription/EntityTest.php rename to tests/Backend/Api/Webhook/EntityTest.php index 45b10c648..f4ccbc137 100644 --- a/tests/Backend/Api/Event/Subscription/EntityTest.php +++ b/tests/Backend/Api/Webhook/EntityTest.php @@ -18,7 +18,7 @@ * limitations under the License. */ -namespace Fusio\Impl\Tests\Backend\Api\Event\Subscription; +namespace Fusio\Impl\Tests\Backend\Api\Webhook; use Fusio\Impl\Tests\Fixture; use Fusio\Impl\Tests\Normalizer; @@ -49,7 +49,7 @@ public function getDataSet(): array public function testGet() { - $response = $this->sendRequest('/backend/event/subscription/1', 'GET', array( + $response = $this->sendRequest('/backend/webhook/1', 'GET', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer da250526d583edabca8ac2f99e37ee39aa02a3c076c0edc6929095e20ca18dcf' )); @@ -60,15 +60,16 @@ public function testGet() $expect = <<eventId}, + "eventId": 53, "userId": 1, + "name": "ping", "endpoint": "http:\/\/www.fusio-project.org\/ping", "responses": [ { "id": 1, "status": 2, - "code": 200, "attempts": 1, + "code": 200, "executeDate": "[datetime]" } ] @@ -81,7 +82,7 @@ public function testGet() public function testGetNotFound() { - $response = $this->sendRequest('/backend/event/subscription/10', 'GET', array( + $response = $this->sendRequest('/backend/webhook/10', 'GET', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer da250526d583edabca8ac2f99e37ee39aa02a3c076c0edc6929095e20ca18dcf' )); @@ -91,12 +92,12 @@ public function testGetNotFound() $this->assertEquals(404, $response->getStatusCode(), $body); $this->assertFalse($data->success); - $this->assertStringStartsWith('Could not find subscription', $data->message); + $this->assertStringStartsWith('Could not find webhook', $data->message); } public function testPost() { - $response = $this->sendRequest('/backend/event/subscription/1', 'POST', array( + $response = $this->sendRequest('/backend/webhook/1', 'POST', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer da250526d583edabca8ac2f99e37ee39aa02a3c076c0edc6929095e20ca18dcf' ), json_encode([ @@ -110,10 +111,11 @@ public function testPost() public function testPut() { - $response = $this->sendRequest('/backend/event/subscription/1', 'PUT', array( + $response = $this->sendRequest('/backend/webhook/1', 'PUT', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer da250526d583edabca8ac2f99e37ee39aa02a3c076c0edc6929095e20ca18dcf' ), json_encode([ + 'name' => 'baz', 'endpoint' => 'http://localhost', ])); @@ -121,7 +123,7 @@ public function testPut() $expect = <<<'JSON' { "success": true, - "message": "Subscription successfully updated" + "message": "Webhook successfully updated" } JSON; @@ -130,8 +132,8 @@ public function testPut() // check database $sql = $this->connection->createQueryBuilder() - ->select('id', 'event_id', 'user_id', 'endpoint') - ->from('fusio_event_subscription') + ->select('id', 'event_id', 'user_id', 'name', 'endpoint') + ->from('fusio_webhook') ->where('id = :id') ->getSQL(); @@ -140,12 +142,13 @@ public function testPut() $this->assertEquals(1, $row['id']); $this->assertEquals($this->eventId, $row['event_id']); $this->assertEquals(1, $row['user_id']); + $this->assertEquals('baz', $row['name']); $this->assertEquals('http://localhost', $row['endpoint']); } public function testDelete() { - $response = $this->sendRequest('/backend/event/subscription/1', 'DELETE', array( + $response = $this->sendRequest('/backend/webhook/1', 'DELETE', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer da250526d583edabca8ac2f99e37ee39aa02a3c076c0edc6929095e20ca18dcf' )); @@ -154,7 +157,7 @@ public function testDelete() $expect = <<<'JSON' { "success": true, - "message": "Subscription successfully deleted" + "message": "Webhook successfully deleted" } JSON; @@ -164,7 +167,7 @@ public function testDelete() // check database $sql = $this->connection->createQueryBuilder() ->select('id') - ->from('fusio_event_subscription') + ->from('fusio_webhook') ->orderBy('id', 'DESC') ->setFirstResult(0) ->setMaxResults(1) diff --git a/tests/Consumer/Api/App/EntityTest.php b/tests/Consumer/Api/App/EntityTest.php index 949830ecb..3acd9097a 100644 --- a/tests/Consumer/Api/App/EntityTest.php +++ b/tests/Consumer/Api/App/EntityTest.php @@ -76,8 +76,8 @@ public function testGet() "consumer.payment", "consumer.plan", "consumer.scope", - "consumer.subscription", "consumer.transaction", + "consumer.webhook", "authorization", "default" ], diff --git a/tests/Consumer/Api/Authorize/AuthorizeTest.php b/tests/Consumer/Api/Authorize/AuthorizeTest.php index cd3cb6c58..d59b3f164 100644 --- a/tests/Consumer/Api/Authorize/AuthorizeTest.php +++ b/tests/Consumer/Api/Authorize/AuthorizeTest.php @@ -53,12 +53,12 @@ public function testGet() "url": "http:\/\/google.com", "scopes": [ { - "id": 45, + "id": 46, "name": "foo", "description": "Foo access" }, { - "id": 46, + "id": 47, "name": "bar", "description": "Bar access" } diff --git a/tests/Consumer/Api/Event/CollectionTest.php b/tests/Consumer/Api/Event/CollectionTest.php index 565d34ba0..e63e33bc6 100644 --- a/tests/Consumer/Api/Event/CollectionTest.php +++ b/tests/Consumer/Api/Event/CollectionTest.php @@ -53,7 +53,7 @@ public function testGet() "itemsPerPage": 16, "entry": [ { - "id": 56, + "id": 53, "name": "foo-event", "description": "Foo event description" } diff --git a/tests/Consumer/Api/Identity/ExchangeTest.php b/tests/Consumer/Api/Identity/ExchangeTest.php index 09be845be..50f1b4f29 100644 --- a/tests/Consumer/Api/Identity/ExchangeTest.php +++ b/tests/Consumer/Api/Identity/ExchangeTest.php @@ -304,7 +304,7 @@ protected function assertToken(string $jwt, int $identityId): void $this->assertEquals(1, $row['status']); $this->assertNotEmpty($row['token']); $this->assertEquals('eb9a3e30-9c88-5525-b229-903113421324', $token->sub); - $this->assertEquals('consumer,consumer.account,consumer.app,consumer.event,consumer.grant,consumer.identity,consumer.log,consumer.page,consumer.payment,consumer.plan,consumer.scope,consumer.subscription,consumer.transaction,authorization,default', $row['scope']); + $this->assertEquals('consumer,consumer.account,consumer.app,consumer.event,consumer.grant,consumer.identity,consumer.log,consumer.page,consumer.payment,consumer.plan,consumer.scope,consumer.transaction,consumer.webhook,authorization,default', $row['scope']); $this->assertEquals('127.0.0.1', $row['ip']); $this->assertNotEmpty($row['expire']); diff --git a/tests/Consumer/Api/Scope/CollectionTest.php b/tests/Consumer/Api/Scope/CollectionTest.php index 5dfa83ae6..1e7c6f1b8 100644 --- a/tests/Consumer/Api/Scope/CollectionTest.php +++ b/tests/Consumer/Api/Scope/CollectionTest.php @@ -58,7 +58,7 @@ public function testGet() "description": "" }, { - "id": 45, + "id": 46, "name": "foo", "description": "Foo access", "metadata": { @@ -66,7 +66,7 @@ public function testGet() } }, { - "id": 46, + "id": 47, "name": "bar", "description": "Bar access" } diff --git a/tests/Consumer/Api/User/AccountTest.php b/tests/Consumer/Api/User/AccountTest.php index 8e4e02d3c..4b04215c0 100644 --- a/tests/Consumer/Api/User/AccountTest.php +++ b/tests/Consumer/Api/User/AccountTest.php @@ -84,6 +84,7 @@ public function testGet() "backend.transaction", "backend.trash", "backend.user", + "backend.webhook", "consumer", "consumer.account", "consumer.app", @@ -95,8 +96,8 @@ public function testGet() "consumer.payment", "consumer.plan", "consumer.scope", - "consumer.subscription", "consumer.transaction", + "consumer.webhook", "authorization", "default", "foo", diff --git a/tests/Consumer/Api/User/LoginTest.php b/tests/Consumer/Api/User/LoginTest.php index 2cf29cb03..49e882599 100644 --- a/tests/Consumer/Api/User/LoginTest.php +++ b/tests/Consumer/Api/User/LoginTest.php @@ -86,7 +86,7 @@ public function testPost() $this->assertEquals(1, $row['status']); $this->assertNotEmpty($row['token']); $this->assertEquals('2a11f995-1306-5494-aaa5-51c74d882e07', $token->sub); - $this->assertEquals('consumer,consumer.account,consumer.app,consumer.event,consumer.grant,consumer.identity,consumer.log,consumer.page,consumer.payment,consumer.plan,consumer.scope,consumer.subscription,consumer.transaction,authorization,foo,bar', $row['scope']); + $this->assertEquals('consumer,consumer.account,consumer.app,consumer.event,consumer.grant,consumer.identity,consumer.log,consumer.page,consumer.payment,consumer.plan,consumer.scope,consumer.transaction,consumer.webhook,authorization,foo,bar', $row['scope']); $this->assertEquals('127.0.0.1', $row['ip']); $this->assertNotEmpty($row['expire']); } diff --git a/tests/Consumer/Api/Subscription/CollectionTest.php b/tests/Consumer/Api/Webhook/CollectionTest.php similarity index 82% rename from tests/Consumer/Api/Subscription/CollectionTest.php rename to tests/Consumer/Api/Webhook/CollectionTest.php index 9afbf99d5..8af6bc45c 100644 --- a/tests/Consumer/Api/Subscription/CollectionTest.php +++ b/tests/Consumer/Api/Webhook/CollectionTest.php @@ -18,7 +18,7 @@ * limitations under the License. */ -namespace Fusio\Impl\Tests\Consumer\Api\Subscription; +namespace Fusio\Impl\Tests\Consumer\Api\Webhook; use Fusio\Impl\Tests\Fixture; use PSX\Framework\Test\ControllerDbTestCase; @@ -39,7 +39,7 @@ public function getDataSet(): array public function testGet() { - $response = $this->sendRequest('/consumer/subscription', 'GET', array( + $response = $this->sendRequest('/consumer/webhook', 'GET', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer b8f6f61bd22b440a3e4be2b7491066682bfcde611dbefa1b15d2e7f6522d77e2' )); @@ -68,11 +68,12 @@ public function testGet() public function testPost() { - $response = $this->sendRequest('/consumer/subscription', 'POST', array( + $response = $this->sendRequest('/consumer/webhook', 'POST', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer b8f6f61bd22b440a3e4be2b7491066682bfcde611dbefa1b15d2e7f6522d77e2' ), json_encode([ 'event' => 'foo-event', + 'name' => 'test', 'endpoint' => 'http://127.0.0.1/new-callback.php', ])); @@ -81,7 +82,7 @@ public function testPost() $expect = <<<'JSON' { "success": true, - "message": "Subscription successfully created" + "message": "Webhook successfully created" } JSON; @@ -90,8 +91,8 @@ public function testPost() // check database $sql = $this->connection->createQueryBuilder() - ->select('id', 'event_id', 'user_id', 'status', 'endpoint') - ->from('fusio_event_subscription') + ->select('id', 'event_id', 'user_id', 'status', 'name', 'endpoint') + ->from('fusio_webhook') ->orderBy('id', 'DESC') ->setFirstResult(0) ->setMaxResults(1) @@ -100,15 +101,16 @@ public function testPost() $row = $this->connection->fetchAssociative($sql); $this->assertEquals(3, $row['id']); - $this->assertEquals(56, $row['event_id']); + $this->assertEquals(53, $row['event_id']); $this->assertEquals(1, $row['user_id']); $this->assertEquals(1, $row['status']); + $this->assertEquals('test', $row['name']); $this->assertEquals('http://127.0.0.1/new-callback.php', $row['endpoint']); } public function testPostEmptyEndpoint() { - $response = $this->sendRequest('/consumer/subscription', 'POST', array( + $response = $this->sendRequest('/consumer/webhook', 'POST', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer b8f6f61bd22b440a3e4be2b7491066682bfcde611dbefa1b15d2e7f6522d77e2' ), json_encode([ @@ -119,12 +121,12 @@ public function testPostEmptyEndpoint() $body = (string) $response->getBody(); $this->assertEquals(400, $response->getStatusCode(), $body); - $this->assertStringContainsString('The endpoint contains no value', $body, $body); + $this->assertStringContainsString('/ the following properties are required: name', $body, $body); } public function testPostInvalidEndpoint() { - $response = $this->sendRequest('/consumer/subscription', 'POST', array( + $response = $this->sendRequest('/consumer/webhook', 'POST', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer b8f6f61bd22b440a3e4be2b7491066682bfcde611dbefa1b15d2e7f6522d77e2' ), json_encode([ @@ -135,12 +137,12 @@ public function testPostInvalidEndpoint() $body = (string) $response->getBody(); $this->assertEquals(400, $response->getStatusCode(), $body); - $this->assertStringContainsString('The endpoint has an invalid url format', $body, $body); + $this->assertStringContainsString('/ the following properties are required: name', $body, $body); } public function testPut() { - $response = $this->sendRequest('/consumer/subscription', 'PUT', array( + $response = $this->sendRequest('/consumer/webhook', 'PUT', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer b8f6f61bd22b440a3e4be2b7491066682bfcde611dbefa1b15d2e7f6522d77e2' ), json_encode([ @@ -154,7 +156,7 @@ public function testPut() public function testDelete() { - $response = $this->sendRequest('/consumer/subscription', 'DELETE', array( + $response = $this->sendRequest('/consumer/webhook', 'DELETE', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer b8f6f61bd22b440a3e4be2b7491066682bfcde611dbefa1b15d2e7f6522d77e2' ), json_encode([ diff --git a/tests/Consumer/Api/Subscription/EntityTest.php b/tests/Consumer/Api/Webhook/EntityTest.php similarity index 85% rename from tests/Consumer/Api/Subscription/EntityTest.php rename to tests/Consumer/Api/Webhook/EntityTest.php index 71324bf00..795a0fc99 100644 --- a/tests/Consumer/Api/Subscription/EntityTest.php +++ b/tests/Consumer/Api/Webhook/EntityTest.php @@ -18,7 +18,7 @@ * limitations under the License. */ -namespace Fusio\Impl\Tests\Consumer\Api\Subscription; +namespace Fusio\Impl\Tests\Consumer\Api\Webhook; use Fusio\Impl\Tests\Fixture; use Fusio\Impl\Tests\Normalizer; @@ -40,7 +40,7 @@ public function getDataSet(): array public function testGet() { - $response = $this->sendRequest('/consumer/subscription/1', 'GET', array( + $response = $this->sendRequest('/consumer/webhook/1', 'GET', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer b8f6f61bd22b440a3e4be2b7491066682bfcde611dbefa1b15d2e7f6522d77e2' )); @@ -71,7 +71,7 @@ public function testGet() public function testPost() { - $response = $this->sendRequest('/consumer/subscription/1', 'POST', array( + $response = $this->sendRequest('/consumer/webhook/1', 'POST', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer b8f6f61bd22b440a3e4be2b7491066682bfcde611dbefa1b15d2e7f6522d77e2' ), json_encode([ @@ -85,11 +85,12 @@ public function testPost() public function testPut() { - $response = $this->sendRequest('/consumer/subscription/1', 'PUT', array( + $response = $this->sendRequest('/consumer/webhook/1', 'PUT', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer b8f6f61bd22b440a3e4be2b7491066682bfcde611dbefa1b15d2e7f6522d77e2' ), json_encode([ 'event' => 'foo-event', + 'name' => 'foofoo', 'endpoint' => 'http://127.0.0.1/changed-callback.php', ])); @@ -98,7 +99,7 @@ public function testPut() $expect = <<<'JSON' { "success": true, - "message": "Subscription successfully updated" + "message": "Webhook successfully updated" } JSON; @@ -107,23 +108,24 @@ public function testPut() // check database $sql = $this->connection->createQueryBuilder() - ->select('id', 'event_id', 'user_id', 'status', 'endpoint') - ->from('fusio_event_subscription') + ->select('id', 'event_id', 'user_id', 'status', 'name', 'endpoint') + ->from('fusio_webhook') ->where('id = :id') ->getSQL(); $row = $this->connection->fetchAssociative($sql, ['id' => 1]); $this->assertEquals(1, $row['id']); - $this->assertEquals(56, $row['event_id']); + $this->assertEquals(53, $row['event_id']); $this->assertEquals(1, $row['user_id']); $this->assertEquals(1, $row['status']); + $this->assertEquals('foofoo', $row['name']); $this->assertEquals('http://127.0.0.1/changed-callback.php', $row['endpoint']); } public function testDelete() { - $response = $this->sendRequest('/consumer/subscription/1', 'DELETE', array( + $response = $this->sendRequest('/consumer/webhook/1', 'DELETE', array( 'User-Agent' => 'Fusio TestCase', 'Authorization' => 'Bearer b8f6f61bd22b440a3e4be2b7491066682bfcde611dbefa1b15d2e7f6522d77e2' ), json_encode([ @@ -135,7 +137,7 @@ public function testDelete() $expect = <<<'JSON' { "success": true, - "message": "Subscription successfully deleted" + "message": "Webhook successfully deleted" } JSON; @@ -145,7 +147,7 @@ public function testDelete() // check database $sql = $this->connection->createQueryBuilder() ->select('id', 'event_id', 'user_id', 'status', 'endpoint') - ->from('fusio_event_subscription') + ->from('fusio_webhook') ->where('id = :id') ->getSQL(); diff --git a/tests/Fixture.php b/tests/Fixture.php index bc30ca46d..35e8b185b 100644 --- a/tests/Fixture.php +++ b/tests/Fixture.php @@ -101,9 +101,9 @@ private static function appendTestInserts(DataBag $data): void $data->addCronjob('default', 'Test-Cron', '* * * * *', 'Sql-Select-All', ['foo' => 'bar']); $data->addCronjobError('Test-Cron', 'Syntax error, malformed JSON'); $data->addEvent('default', 'foo-event', 'Foo event description', ['foo' => 'bar']); - $data->addEventSubscription('foo-event', 'Administrator', 'http://www.fusio-project.org/ping'); - $data->addEventSubscription('foo-event', 'Consumer', 'http://www.fusio-project.org/ping'); - $data->addEventResponse(1); + $data->addWebhook('foo-event', 'Administrator', 'ping', 'http://www.fusio-project.org/ping'); + $data->addWebhook('foo-event', 'Consumer', 'pong', 'http://www.fusio-project.org/ping'); + $data->addWebhookResponse(1); $data->addIdentity('Developer', 'Facebook', 'bi-facebook', Facebook::class, 'facebook-key', 'facebook-secret', 'https://www.facebook.com/v17.0/dialog/oauth', 'https://graph.facebook.com/v12.0/oauth/access_token', 'https://graph.facebook.com/v2.5/me', 'id', 'name', 'email', '2023-07-22 13:56:00'); $data->addIdentity('Developer', 'GitHub', 'bi-github', Github::class, 'github-key', 'github-secret', 'https://github.com/login/oauth/authorize', 'https://github.com/login/oauth/access_token', 'https://api.github.com/user', 'id', 'login', 'email', '2023-07-22 13:56:00'); $data->addIdentity('Developer', 'Google', 'bi-google', Google::class, 'google-key', 'google-secret', 'https://accounts.google.com/o/oauth2/v2/auth', 'https://oauth2.googleapis.com/token', 'https://openidconnect.googleapis.com/v1/userinfo', 'id', 'name', 'email', '2023-07-22 13:56:00'); diff --git a/tests/Service/Cronjob/ValidatorTest.php b/tests/Service/Cronjob/ValidatorTest.php index 71f4f7ae5..ce1608261 100644 --- a/tests/Service/Cronjob/ValidatorTest.php +++ b/tests/Service/Cronjob/ValidatorTest.php @@ -45,7 +45,7 @@ public function testAssertCron(string $cron, bool $expect, ?string $errorMessage $cronjob = new CronjobCreate(); $cronjob->setName('test'); $cronjob->setCron($cron); - Environment::getService(Validator::class)->assert($cronjob); + Environment::getService(Validator::class)->assert($cronjob, null); $this->assertTrue($expect); } catch (BadRequestException $e) { diff --git a/tests/System/resources/typeapi_fusio.json b/tests/System/resources/typeapi_fusio.json index 1aa806508..3920e91da 100644 --- a/tests/System/resources/typeapi_fusio.json +++ b/tests/System/resources/typeapi_fusio.json @@ -31,7 +31,8 @@ "backend.tenant": "", "backend.transaction": "", "backend.trash": "", - "backend.user": "" + "backend.user": "", + "backend.webhook": "" } }, "operations": { @@ -980,8 +981,8 @@ "transaction" ] }, - "consumer.subscription.delete": { - "path": "/consumer/subscription/$subscription_id<[0-9]+>", + "consumer.webhook.delete": { + "path": "/consumer/webhook/$webhook_id<[0-9]+>", "method": "DELETE", "return": { "code": 200, @@ -990,7 +991,7 @@ } }, "arguments": { - "subscription_id": { + "webhook_id": { "in": "path", "schema": { "type": "string" @@ -1026,15 +1027,15 @@ "description": "", "stability": 1, "security": [ - "consumer.subscription" + "consumer.webhook" ], "authorization": true, "tags": [ - "subscription" + "webhook" ] }, - "consumer.subscription.update": { - "path": "/consumer/subscription/$subscription_id<[0-9]+>", + "consumer.webhook.update": { + "path": "/consumer/webhook/$webhook_id<[0-9]+>", "method": "PUT", "return": { "code": 200, @@ -1043,7 +1044,7 @@ } }, "arguments": { - "subscription_id": { + "webhook_id": { "in": "path", "schema": { "type": "string" @@ -1052,7 +1053,7 @@ "payload": { "in": "body", "schema": { - "$ref": "Consumer_EventSubscriptionUpdate" + "$ref": "Consumer_WebhookUpdate" } } }, @@ -1091,24 +1092,24 @@ "description": "", "stability": 1, "security": [ - "consumer.subscription" + "consumer.webhook" ], "authorization": true, "tags": [ - "subscription" + "webhook" ] }, - "consumer.subscription.get": { - "path": "/consumer/subscription/$subscription_id<[0-9]+>", + "consumer.webhook.get": { + "path": "/consumer/webhook/$webhook_id<[0-9]+>", "method": "GET", "return": { "code": 200, "schema": { - "$ref": "Consumer_EventSubscription" + "$ref": "Consumer_Webhook" } }, "arguments": { - "subscription_id": { + "webhook_id": { "in": "path", "schema": { "type": "string" @@ -1144,15 +1145,15 @@ "description": "", "stability": 1, "security": [ - "consumer.subscription" + "consumer.webhook" ], "authorization": true, "tags": [ - "subscription" + "webhook" ] }, - "consumer.subscription.create": { - "path": "/consumer/subscription", + "consumer.webhook.create": { + "path": "/consumer/webhook", "method": "POST", "return": { "code": 201, @@ -1164,7 +1165,7 @@ "payload": { "in": "body", "schema": { - "$ref": "Consumer_EventSubscriptionCreate" + "$ref": "Consumer_WebhookCreate" } } }, @@ -1191,20 +1192,20 @@ "description": "", "stability": 1, "security": [ - "consumer.subscription" + "consumer.webhook" ], "authorization": true, "tags": [ - "subscription" + "webhook" ] }, - "consumer.subscription.getAll": { - "path": "/consumer/subscription", + "consumer.webhook.getAll": { + "path": "/consumer/webhook", "method": "GET", "return": { "code": 200, "schema": { - "$ref": "Consumer_EventSubscriptionCollection" + "$ref": "Consumer_WebhookCollection" } }, "arguments": { @@ -1244,11 +1245,11 @@ "description": "", "stability": 1, "security": [ - "consumer.subscription" + "consumer.webhook" ], "authorization": true, "tags": [ - "subscription" + "webhook" ] }, "consumer.scope.getAll": { @@ -2142,6 +2143,271 @@ "app" ] }, + "backend.webhook.delete": { + "path": "/backend/webhook/$webhook_id<[0-9]+>", + "method": "DELETE", + "return": { + "code": 200, + "schema": { + "$ref": "Common_Message" + } + }, + "arguments": { + "webhook_id": { + "in": "path", + "schema": { + "type": "string" + } + } + }, + "throws": [ + { + "code": 401, + "schema": { + "$ref": "Common_Message" + } + }, + { + "code": 404, + "schema": { + "$ref": "Common_Message" + } + }, + { + "code": 410, + "schema": { + "$ref": "Common_Message" + } + }, + { + "code": 500, + "schema": { + "$ref": "Common_Message" + } + } + ], + "description": "", + "stability": 1, + "security": [ + "backend.webhook" + ], + "authorization": true, + "tags": [ + "webhook" + ] + }, + "backend.webhook.update": { + "path": "/backend/webhook/$webhook_id<[0-9]+>", + "method": "PUT", + "return": { + "code": 200, + "schema": { + "$ref": "Common_Message" + } + }, + "arguments": { + "webhook_id": { + "in": "path", + "schema": { + "type": "string" + } + }, + "payload": { + "in": "body", + "schema": { + "$ref": "Backend_WebhookUpdate" + } + } + }, + "throws": [ + { + "code": 400, + "schema": { + "$ref": "Common_Message" + } + }, + { + "code": 401, + "schema": { + "$ref": "Common_Message" + } + }, + { + "code": 404, + "schema": { + "$ref": "Common_Message" + } + }, + { + "code": 410, + "schema": { + "$ref": "Common_Message" + } + }, + { + "code": 500, + "schema": { + "$ref": "Common_Message" + } + } + ], + "description": "", + "stability": 1, + "security": [ + "backend.webhook" + ], + "authorization": true, + "tags": [ + "webhook" + ] + }, + "backend.webhook.get": { + "path": "/backend/webhook/$webhook_id<[0-9]+>", + "method": "GET", + "return": { + "code": 200, + "schema": { + "$ref": "Backend_Webhook" + } + }, + "arguments": { + "webhook_id": { + "in": "path", + "schema": { + "type": "string" + } + } + }, + "throws": [ + { + "code": 401, + "schema": { + "$ref": "Common_Message" + } + }, + { + "code": 404, + "schema": { + "$ref": "Common_Message" + } + }, + { + "code": 500, + "schema": { + "$ref": "Common_Message" + } + } + ], + "description": "", + "stability": 1, + "security": [ + "backend.webhook" + ], + "authorization": true, + "tags": [ + "webhook" + ] + }, + "backend.webhook.create": { + "path": "/backend/webhook", + "method": "POST", + "return": { + "code": 201, + "schema": { + "$ref": "Common_Message" + } + }, + "arguments": { + "payload": { + "in": "body", + "schema": { + "$ref": "Backend_WebhookCreate" + } + } + }, + "throws": [ + { + "code": 400, + "schema": { + "$ref": "Common_Message" + } + }, + { + "code": 401, + "schema": { + "$ref": "Common_Message" + } + }, + { + "code": 500, + "schema": { + "$ref": "Common_Message" + } + } + ], + "description": "", + "stability": 1, + "security": [ + "backend.webhook" + ], + "authorization": true, + "tags": [ + "webhook" + ] + }, + "backend.webhook.getAll": { + "path": "/backend/webhook", + "method": "GET", + "return": { + "code": 200, + "schema": { + "$ref": "Backend_WebhookCollection" + } + }, + "arguments": { + "startIndex": { + "in": "query", + "schema": { + "type": "integer" + } + }, + "count": { + "in": "query", + "schema": { + "type": "integer" + } + }, + "search": { + "in": "query", + "schema": { + "type": "string" + } + } + }, + "throws": [ + { + "code": 401, + "schema": { + "$ref": "Common_Message" + } + }, + { + "code": 500, + "schema": { + "$ref": "Common_Message" + } + } + ], + "description": "", + "stability": 1, + "security": [ + "backend.webhook" + ], + "authorization": true, + "tags": [ + "webhook" + ] + }, "backend.user.delete": { "path": "/backend/user/$user_id<[0-9]+>", "method": "DELETE", @@ -7437,23 +7703,16 @@ "event" ] }, - "backend.event.deleteSubscription": { - "path": "/backend/event/subscription/$subscription_id<[0-9]+>", - "method": "DELETE", + "backend.dashboard.getAll": { + "path": "/backend/dashboard", + "method": "GET", "return": { "code": 200, "schema": { - "$ref": "Common_Message" - } - }, - "arguments": { - "subscription_id": { - "in": "path", - "schema": { - "type": "string" - } + "$ref": "Backend_Dashboard" } }, + "arguments": [], "throws": [ { "code": 401, @@ -7462,265 +7721,7 @@ } }, { - "code": 404, - "schema": { - "$ref": "Common_Message" - } - }, - { - "code": 410, - "schema": { - "$ref": "Common_Message" - } - }, - { - "code": 500, - "schema": { - "$ref": "Common_Message" - } - } - ], - "description": "", - "stability": 1, - "security": [ - "backend.event" - ], - "authorization": true, - "tags": [ - "event" - ] - }, - "backend.event.updateSubscription": { - "path": "/backend/event/subscription/$subscription_id<[0-9]+>", - "method": "PUT", - "return": { - "code": 200, - "schema": { - "$ref": "Common_Message" - } - }, - "arguments": { - "subscription_id": { - "in": "path", - "schema": { - "type": "string" - } - }, - "payload": { - "in": "body", - "schema": { - "$ref": "Backend_EventSubscriptionUpdate" - } - } - }, - "throws": [ - { - "code": 400, - "schema": { - "$ref": "Common_Message" - } - }, - { - "code": 401, - "schema": { - "$ref": "Common_Message" - } - }, - { - "code": 404, - "schema": { - "$ref": "Common_Message" - } - }, - { - "code": 410, - "schema": { - "$ref": "Common_Message" - } - }, - { - "code": 500, - "schema": { - "$ref": "Common_Message" - } - } - ], - "description": "", - "stability": 1, - "security": [ - "backend.event" - ], - "authorization": true, - "tags": [ - "event" - ] - }, - "backend.event.getSubscription": { - "path": "/backend/event/subscription/$subscription_id<[0-9]+>", - "method": "GET", - "return": { - "code": 200, - "schema": { - "$ref": "Backend_EventSubscription" - } - }, - "arguments": { - "subscription_id": { - "in": "path", - "schema": { - "type": "string" - } - } - }, - "throws": [ - { - "code": 401, - "schema": { - "$ref": "Common_Message" - } - }, - { - "code": 404, - "schema": { - "$ref": "Common_Message" - } - }, - { - "code": 500, - "schema": { - "$ref": "Common_Message" - } - } - ], - "description": "", - "stability": 1, - "security": [ - "backend.event" - ], - "authorization": true, - "tags": [ - "event" - ] - }, - "backend.event.createSubscription": { - "path": "/backend/event/subscription", - "method": "POST", - "return": { - "code": 201, - "schema": { - "$ref": "Common_Message" - } - }, - "arguments": { - "payload": { - "in": "body", - "schema": { - "$ref": "Backend_EventSubscriptionCreate" - } - } - }, - "throws": [ - { - "code": 400, - "schema": { - "$ref": "Common_Message" - } - }, - { - "code": 401, - "schema": { - "$ref": "Common_Message" - } - }, - { - "code": 500, - "schema": { - "$ref": "Common_Message" - } - } - ], - "description": "", - "stability": 1, - "security": [ - "backend.event" - ], - "authorization": true, - "tags": [ - "event" - ] - }, - "backend.event.getAllSubscriptions": { - "path": "/backend/event/subscription", - "method": "GET", - "return": { - "code": 200, - "schema": { - "$ref": "Backend_EventSubscriptionCollection" - } - }, - "arguments": { - "startIndex": { - "in": "query", - "schema": { - "type": "integer" - } - }, - "count": { - "in": "query", - "schema": { - "type": "integer" - } - }, - "search": { - "in": "query", - "schema": { - "type": "string" - } - } - }, - "throws": [ - { - "code": 401, - "schema": { - "$ref": "Common_Message" - } - }, - { - "code": 500, - "schema": { - "$ref": "Common_Message" - } - } - ], - "description": "", - "stability": 1, - "security": [ - "backend.event" - ], - "authorization": true, - "tags": [ - "event" - ] - }, - "backend.dashboard.getAll": { - "path": "/backend/dashboard", - "method": "GET", - "return": { - "code": 200, - "schema": { - "$ref": "Backend_Dashboard" - } - }, - "arguments": [], - "throws": [ - { - "code": 401, - "schema": { - "$ref": "Common_Message" - } - }, - { - "code": 500, + "code": 500, "schema": { "$ref": "Common_Message" } @@ -10780,72 +10781,6 @@ "name" ] }, - "Backend_EventSubscription": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "eventId": { - "type": "integer" - }, - "userId": { - "type": "integer" - }, - "endpoint": { - "type": "string" - }, - "responses": { - "type": "array", - "items": { - "$ref": "Backend_EventSubscriptionResponse" - } - } - } - }, - "Backend_EventSubscriptionCollection": { - "$ref": "Common_Collection", - "$template": { - "T": "Backend_EventSubscription" - } - }, - "Backend_EventSubscriptionCreate": { - "$extends": "Backend_EventSubscription", - "type": "object", - "required": [ - "eventId", - "userId", - "endpoint" - ] - }, - "Backend_EventSubscriptionResponse": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "status": { - "type": "integer" - }, - "code": { - "type": "integer" - }, - "attempts": { - "type": "integer" - }, - "error": { - "type": "string" - }, - "executeDate": { - "format": "date-time", - "type": "string" - } - } - }, - "Backend_EventSubscriptionUpdate": { - "$extends": "Backend_EventSubscription", - "type": "object" - }, "Backend_EventUpdate": { "$extends": "Backend_Event", "type": "object" @@ -11833,6 +11768,76 @@ "$extends": "Backend_User", "type": "object" }, + "Backend_Webhook": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "eventId": { + "type": "integer" + }, + "userId": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "responses": { + "type": "array", + "items": { + "$ref": "Backend_WebhookResponse" + } + } + } + }, + "Backend_WebhookCollection": { + "$ref": "Common_Collection", + "$template": { + "T": "Backend_Webhook" + } + }, + "Backend_WebhookCreate": { + "$extends": "Backend_Webhook", + "type": "object", + "required": [ + "eventId", + "userId", + "name", + "endpoint" + ] + }, + "Backend_WebhookResponse": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "status": { + "type": "integer" + }, + "code": { + "type": "integer" + }, + "attempts": { + "type": "integer" + }, + "error": { + "type": "string" + }, + "executeDate": { + "format": "date-time", + "type": "string" + } + } + }, + "Backend_WebhookUpdate": { + "$extends": "Backend_Webhook", + "type": "object" + }, "Common_Collection": { "type": "object", "properties": { @@ -12159,82 +12164,6 @@ "T": "Consumer_Event" } }, - "Consumer_EventSubscription": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "status": { - "type": "integer" - }, - "event": { - "type": "string" - }, - "endpoint": { - "type": "string" - }, - "responses": { - "type": "array", - "items": { - "$ref": "Consumer_EventSubscriptionResponse" - } - } - } - }, - "Consumer_EventSubscriptionCollection": { - "$ref": "Common_Collection", - "$template": { - "T": "Consumer_EventSubscription" - } - }, - "Consumer_EventSubscriptionCreate": { - "type": "object", - "properties": { - "event": { - "type": "string" - }, - "endpoint": { - "type": "string" - } - }, - "required": [ - "event", - "endpoint" - ] - }, - "Consumer_EventSubscriptionResponse": { - "type": "object", - "properties": { - "status": { - "type": "integer" - }, - "code": { - "type": "integer" - }, - "attempts": { - "type": "string" - }, - "executeDate": { - "type": "string" - } - } - }, - "Consumer_EventSubscriptionUpdate": { - "type": "object", - "properties": { - "event": { - "type": "string" - }, - "endpoint": { - "type": "string" - } - }, - "required": [ - "event", - "endpoint" - ] - }, "Consumer_Grant": { "type": "object", "properties": { @@ -12642,6 +12571,93 @@ "password" ] }, + "Consumer_Webhook": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "status": { + "type": "integer" + }, + "event": { + "type": "string" + }, + "name": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "responses": { + "type": "array", + "items": { + "$ref": "Consumer_WebhookResponse" + } + } + } + }, + "Consumer_WebhookCollection": { + "$ref": "Common_Collection", + "$template": { + "T": "Consumer_Webhook" + } + }, + "Consumer_WebhookCreate": { + "type": "object", + "properties": { + "event": { + "type": "string" + }, + "name": { + "type": "string" + }, + "endpoint": { + "type": "string" + } + }, + "required": [ + "event", + "name", + "endpoint" + ] + }, + "Consumer_WebhookResponse": { + "type": "object", + "properties": { + "status": { + "type": "integer" + }, + "code": { + "type": "integer" + }, + "attempts": { + "type": "string" + }, + "executeDate": { + "type": "string" + } + } + }, + "Consumer_WebhookUpdate": { + "type": "object", + "properties": { + "event": { + "type": "string" + }, + "name": { + "type": "string" + }, + "endpoint": { + "type": "string" + } + }, + "required": [ + "event", + "name", + "endpoint" + ] + }, "Passthru": { "description": "No schema information available", "type": "object",