Merge branch 'main' into feat/v3-project-migration
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
"nelmio/cors-bundle": "^2.3",
|
||||
"phpdocumentor/reflection-docblock": "^5.3",
|
||||
"phpstan/phpdoc-parser": "^1.24",
|
||||
"stof/doctrine-extensions-bundle": "^1.11",
|
||||
"subiabre/doctrine-snowflakes": "^2.0",
|
||||
"symfony/asset": "6.4.*",
|
||||
"symfony/console": "6.4.*",
|
||||
|
||||
Generated
+258
-1
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "b558cc67d1647e5cfb49487057a3af3d",
|
||||
"content-hash": "e65a6d86be5982961b544a7bfa7704c9",
|
||||
"packages": [
|
||||
{
|
||||
"name": "api-platform/core",
|
||||
@@ -172,6 +172,55 @@
|
||||
},
|
||||
"time": "2024-02-20T09:52:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "behat/transliterator",
|
||||
"version": "v1.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Behat/Transliterator.git",
|
||||
"reference": "baac5873bac3749887d28ab68e2f74db3a4408af"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Behat/Transliterator/zipball/baac5873bac3749887d28ab68e2f74db3a4408af",
|
||||
"reference": "baac5873bac3749887d28ab68e2f74db3a4408af",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"chuyskywalker/rolling-curl": "^3.1",
|
||||
"php-yaoi/php-yaoi": "^1.0",
|
||||
"phpunit/phpunit": "^8.5.25 || ^9.5.19"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Behat\\Transliterator\\": "src/Behat/Transliterator"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"Artistic-1.0"
|
||||
],
|
||||
"description": "String transliterator",
|
||||
"keywords": [
|
||||
"i18n",
|
||||
"slug",
|
||||
"transliterator"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Behat/Transliterator/issues",
|
||||
"source": "https://github.com/Behat/Transliterator/tree/v1.5.0"
|
||||
},
|
||||
"time": "2022-03-30T09:27:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brick/math",
|
||||
"version": "0.11.0",
|
||||
@@ -1607,6 +1656,134 @@
|
||||
},
|
||||
"time": "2022-05-23T21:33:49+00:00"
|
||||
},
|
||||
{
|
||||
"name": "gedmo/doctrine-extensions",
|
||||
"version": "v3.15.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine-extensions/DoctrineExtensions.git",
|
||||
"reference": "2a89103f4984d8970f3855284c8c04e6e6a63c0f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine-extensions/DoctrineExtensions/zipball/2a89103f4984d8970f3855284c8c04e6e6a63c0f",
|
||||
"reference": "2a89103f4984d8970f3855284c8c04e6e6a63c0f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"behat/transliterator": "^1.2",
|
||||
"doctrine/collections": "^1.2 || ^2.0",
|
||||
"doctrine/common": "^2.13 || ^3.0",
|
||||
"doctrine/deprecations": "^1.0",
|
||||
"doctrine/event-manager": "^1.2 || ^2.0",
|
||||
"doctrine/persistence": "^2.2 || ^3.0",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"psr/cache": "^1 || ^2 || ^3",
|
||||
"psr/clock": "^1",
|
||||
"symfony/cache": "^5.4 || ^6.0 || ^7.0"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/annotations": "<1.13 || >=3.0",
|
||||
"doctrine/dbal": "<3.2 || >=4.0",
|
||||
"doctrine/mongodb-odm": "<2.3 || >=3.0",
|
||||
"doctrine/orm": "<2.14.0 || 2.16.0 || 2.16.1 || >=3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/annotations": "^1.13 || ^2.0",
|
||||
"doctrine/cache": "^1.11 || ^2.0",
|
||||
"doctrine/dbal": "^3.2",
|
||||
"doctrine/doctrine-bundle": "^2.3",
|
||||
"doctrine/mongodb-odm": "^2.3",
|
||||
"doctrine/orm": "^2.14.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.14.0",
|
||||
"nesbot/carbon": "^2.71 || ^3.0",
|
||||
"phpstan/phpstan": "^1.10.2",
|
||||
"phpstan/phpstan-doctrine": "^1.0",
|
||||
"phpstan/phpstan-phpunit": "^1.0",
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"rector/rector": "^0.19",
|
||||
"symfony/console": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/phpunit-bridge": "^6.0 || ^7.0",
|
||||
"symfony/yaml": "^5.4 || ^6.0 || ^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"doctrine/mongodb-odm": "to use the extensions with the MongoDB ODM",
|
||||
"doctrine/orm": "to use the extensions with the ORM"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "3.13-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Gedmo\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Gediminas Morkevicius",
|
||||
"email": "gediminas.morkevicius@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Gustavo Falco",
|
||||
"email": "comfortablynumb84@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "David Buchmann",
|
||||
"email": "david@liip.ch"
|
||||
}
|
||||
],
|
||||
"description": "Doctrine behavioral extensions",
|
||||
"homepage": "http://gediminasm.org/",
|
||||
"keywords": [
|
||||
"Blameable",
|
||||
"behaviors",
|
||||
"doctrine",
|
||||
"extensions",
|
||||
"gedmo",
|
||||
"loggable",
|
||||
"nestedset",
|
||||
"odm",
|
||||
"orm",
|
||||
"sluggable",
|
||||
"sortable",
|
||||
"timestampable",
|
||||
"translatable",
|
||||
"tree",
|
||||
"uploadable"
|
||||
],
|
||||
"support": {
|
||||
"email": "gediminas.morkevicius@gmail.com",
|
||||
"issues": "https://github.com/doctrine-extensions/DoctrineExtensions/issues",
|
||||
"source": "https://github.com/doctrine-extensions/DoctrineExtensions/tree/v3.15.0",
|
||||
"wiki": "https://github.com/Atlantic18/DoctrineExtensions/tree/main/doc"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/l3pp4rd",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/mbabker",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/phansys",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/stof",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-02-12T15:17:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "godruoyi/php-snowflake",
|
||||
"version": "3.0.0",
|
||||
@@ -2354,6 +2531,86 @@
|
||||
},
|
||||
"time": "2021-07-14T16:46:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "stof/doctrine-extensions-bundle",
|
||||
"version": "v1.11.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/stof/StofDoctrineExtensionsBundle.git",
|
||||
"reference": "9f7023e4c8a1c00a5627d41c1027a3f89e477630"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/stof/StofDoctrineExtensionsBundle/zipball/9f7023e4c8a1c00a5627d41c1027a3f89e477630",
|
||||
"reference": "9f7023e4c8a1c00a5627d41c1027a3f89e477630",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"gedmo/doctrine-extensions": "^3.15.0",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"symfony/cache": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/config": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/http-kernel": "^5.4 || ^6.0 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.1",
|
||||
"phpstan/phpstan-phpunit": "^1.3",
|
||||
"phpstan/phpstan-strict-rules": "^1.5",
|
||||
"phpstan/phpstan-symfony": "^1.3",
|
||||
"symfony/mime": "^5.4 || ^6.0 || ^7.0",
|
||||
"symfony/phpunit-bridge": "^v6.4.1 || ^7.0.1",
|
||||
"symfony/security-core": "^5.4 || ^6.0 || ^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"doctrine/doctrine-bundle": "to use the ORM extensions",
|
||||
"doctrine/mongodb-odm-bundle": "to use the MongoDB ODM extensions",
|
||||
"symfony/mime": "To use the Mime component integration for Uploadable"
|
||||
},
|
||||
"type": "symfony-bundle",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Stof\\DoctrineExtensionsBundle\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christophe Coevoet",
|
||||
"email": "stof@notk.org"
|
||||
}
|
||||
],
|
||||
"description": "Integration of the gedmo/doctrine-extensions with Symfony",
|
||||
"homepage": "https://github.com/stof/StofDoctrineExtensionsBundle",
|
||||
"keywords": [
|
||||
"behaviors",
|
||||
"doctrine2",
|
||||
"extensions",
|
||||
"gedmo",
|
||||
"loggable",
|
||||
"nestedset",
|
||||
"sluggable",
|
||||
"sortable",
|
||||
"timestampable",
|
||||
"translatable",
|
||||
"tree"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/stof/StofDoctrineExtensionsBundle/issues",
|
||||
"source": "https://github.com/stof/StofDoctrineExtensionsBundle/tree/v1.11.0"
|
||||
},
|
||||
"time": "2024-02-13T14:43:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "subiabre/doctrine-snowflakes",
|
||||
"version": "2.0",
|
||||
|
||||
@@ -11,4 +11,5 @@ return [
|
||||
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||
Zenstruck\Foundry\ZenstruckFoundryBundle::class => ['dev' => true, 'test' => true],
|
||||
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
||||
Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true],
|
||||
];
|
||||
|
||||
@@ -21,6 +21,12 @@ doctrine:
|
||||
dir: '%kernel.project_dir%/src/Entity'
|
||||
prefix: 'App\Entity'
|
||||
alias: App
|
||||
|
||||
gedmo_loggable:
|
||||
type: attribute
|
||||
prefix: Gedmo\Loggable\Entity
|
||||
dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/src/Loggable/Entity"
|
||||
is_bundle: false
|
||||
|
||||
when@test:
|
||||
doctrine:
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Read the documentation: https://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/index.html
|
||||
# See the official DoctrineExtensions documentation for more details: https://github.com/doctrine-extensions/DoctrineExtensions/tree/main/doc
|
||||
stof_doctrine_extensions:
|
||||
default_locale: en_US
|
||||
|
||||
orm:
|
||||
default:
|
||||
loggable: true
|
||||
@@ -32,6 +32,12 @@ RUN apt install -y \
|
||||
&& docker-php-ext-install \
|
||||
zip
|
||||
|
||||
# Intl
|
||||
RUN apt install -y \
|
||||
libicu-dev \
|
||||
&& docker-php-ext-install \
|
||||
intl
|
||||
|
||||
# Composer
|
||||
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\ApiResource;
|
||||
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\Filter\VersionResourceFilter;
|
||||
use App\Filter\VersionResourceIdFilter;
|
||||
use App\State\VersionStateProvider;
|
||||
use Gedmo\Loggable\Entity\LogEntry;
|
||||
|
||||
/**
|
||||
* Some resources are versioned. This means v4 keeps track of the changes performed in subsets of specific properties within these resources.\
|
||||
* \
|
||||
* This allows us to keep track of the flow and the evolution of records in the platform.
|
||||
* Looking at the changes done between one version and the next one we can reconstruct how a resource was at a certain point in time.
|
||||
*/
|
||||
#[API\ApiFilter(VersionResourceFilter::class, properties: ['resource'])]
|
||||
#[API\ApiFilter(VersionResourceIdFilter::class, properties: ['resourceId'])]
|
||||
#[API\GetCollection(provider: VersionStateProvider::class)]
|
||||
#[API\Get(provider: VersionStateProvider::class)]
|
||||
class Version
|
||||
{
|
||||
public function __construct(
|
||||
private readonly LogEntry $log,
|
||||
private readonly object $entity
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The ID of the version record.
|
||||
*/
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->log->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* The ID of the version for this specific resource.
|
||||
*/
|
||||
public function getVersion(): ?int
|
||||
{
|
||||
return $this->log->getVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of action that performed the recorded changes.
|
||||
*/
|
||||
public function getAction(): ?string
|
||||
{
|
||||
return $this->log->getAction();
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the recorded resource.
|
||||
*/
|
||||
public function getResource(): string
|
||||
{
|
||||
$classPieces = explode('\\', $this->log->getObjectClass());
|
||||
|
||||
return end($classPieces);
|
||||
}
|
||||
|
||||
/**
|
||||
* The ID of the recorded resource.
|
||||
*/
|
||||
public function getResouceId(): int
|
||||
{
|
||||
return $this->log->getObjectId();
|
||||
}
|
||||
|
||||
/**
|
||||
* The full resource data, reconstructed from the current resource data merged with versioned data.
|
||||
*/
|
||||
public function getResourceData()
|
||||
{
|
||||
$entity = $this->entity;
|
||||
|
||||
foreach ($this->log->getData() as $changedProperty => $changedValue) {
|
||||
$setter = sprintf("set%s", ucfirst($changedProperty));
|
||||
$entity->$setter($changedValue);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* The changed resource data, i.e the new values of the changed properties.
|
||||
*/
|
||||
public function getResourceChanges()
|
||||
{
|
||||
return $this->log->getData();
|
||||
}
|
||||
|
||||
/**
|
||||
* The date at which this version was created.
|
||||
*/
|
||||
public function getDateCreated(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->log->getLoggedAt();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\Repository\GatewayChargeRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Entity(repositoryClass: GatewayChargeRepository::class)]
|
||||
class GatewayCharge
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* The money to be charged by the Gateway at checkout.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[ORM\Embedded(class: Money::class)]
|
||||
private ?Money $money = null;
|
||||
|
||||
/**
|
||||
* The Accounting that will receive the Transaction of this GatewayCharge after a successful checkout.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[ORM\ManyToOne]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?Accounting $target = null;
|
||||
|
||||
/**
|
||||
* Custom data to be passed to the Gateway.\
|
||||
* E.g: billing period, product name, custom metadata, etc.
|
||||
*/
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?array $extradata = null;
|
||||
|
||||
#[API\ApiProperty(writable: false)]
|
||||
#[ORM\ManyToOne(inversedBy: 'charges')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?GatewayCheckout $checkout = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getMoney(): ?Money
|
||||
{
|
||||
return $this->money;
|
||||
}
|
||||
|
||||
public function setMoney(Money $money): static
|
||||
{
|
||||
$this->money = $money;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTarget(): ?Accounting
|
||||
{
|
||||
return $this->target;
|
||||
}
|
||||
|
||||
public function setTarget(?Accounting $target): static
|
||||
{
|
||||
$this->target = $target;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExtradata(): ?array
|
||||
{
|
||||
return $this->extradata;
|
||||
}
|
||||
|
||||
public function setExtradata(?array $extradata): static
|
||||
{
|
||||
$this->extradata = $extradata;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCheckout(): ?GatewayCheckout
|
||||
{
|
||||
return $this->checkout;
|
||||
}
|
||||
|
||||
public function setCheckout(?GatewayCheckout $checkout): static
|
||||
{
|
||||
$this->checkout = $checkout;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\Repository\GatewayCheckoutRepository;
|
||||
use App\State\GatewayCheckoutProcessor;
|
||||
use App\Validator\GatewayName;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* A GatewayCheckout bundles the data to perform a charge operation at a Gateway.
|
||||
* Use it in order to create Transactions and have the transferred money be backed by a Gateway's payment processing.
|
||||
*/
|
||||
#[API\GetCollection()]
|
||||
#[API\Post(processor: GatewayCheckoutProcessor::class)]
|
||||
#[API\Get()]
|
||||
#[ORM\Entity(repositoryClass: GatewayCheckoutRepository::class)]
|
||||
class GatewayCheckout
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* The name of the Gateway implementation to checkout with.
|
||||
*/
|
||||
#[GatewayName]
|
||||
#[Assert\NotBlank()]
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $gateway = null;
|
||||
|
||||
/**
|
||||
* The Accounting that will issue the Transactions of the GatewayCharges after a successful checkout.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[ORM\ManyToOne]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?Accounting $origin = null;
|
||||
|
||||
/**
|
||||
* The charges to be made by the Gateway at checkout.\
|
||||
* Each GatewayCharge will generate a separate Transaction after a successful checkout.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[Assert\Count(min: 1)]
|
||||
#[ORM\OneToMany(
|
||||
mappedBy: 'checkout',
|
||||
targetEntity: GatewayCharge::class,
|
||||
cascade: ['persist']
|
||||
)]
|
||||
private Collection $charges;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->charges = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getGateway(): ?string
|
||||
{
|
||||
return $this->gateway;
|
||||
}
|
||||
|
||||
public function setGateway(string $gateway): static
|
||||
{
|
||||
$this->gateway = $gateway;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOrigin(): ?Accounting
|
||||
{
|
||||
return $this->origin;
|
||||
}
|
||||
|
||||
public function setOrigin(?Accounting $origin): static
|
||||
{
|
||||
$this->origin = $origin;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, GatewayCharge>
|
||||
*/
|
||||
public function getCharges(): Collection
|
||||
{
|
||||
return $this->charges;
|
||||
}
|
||||
|
||||
public function addCharge(GatewayCharge $charge): static
|
||||
{
|
||||
if (!$this->charges->contains($charge)) {
|
||||
$this->charges->add($charge);
|
||||
$charge->setCheckout($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeCharge(GatewayCharge $charge): static
|
||||
{
|
||||
if ($this->charges->removeElement($charge)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($charge->getCheckout() === $this) {
|
||||
$charge->setCheckout(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity\Interface;
|
||||
|
||||
use App\Entity\User;
|
||||
|
||||
interface UserOwnedInterface
|
||||
{
|
||||
public function getOwner(): ?User;
|
||||
|
||||
public function isOwnedBy(User $user): bool;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\Repository\ProjectRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
@@ -12,13 +13,14 @@ use Doctrine\ORM\Mapping as ORM;
|
||||
* Since they can be recipients of funding, they are assigned an Accounting when created.
|
||||
* A Project's Accounting represents how much money the Project has raised from the community.
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: ProjectRepository::class)]
|
||||
#[API\GetCollection()]
|
||||
#[API\Post(security: 'is_granted("ROLE_USER")')]
|
||||
#[API\Get()]
|
||||
#[API\Put(security: 'is_granted("AUTH_PROJECT_EDIT")')]
|
||||
#[API\Delete(security: 'is_granted("AUTH_PROJECT_EDIT")')]
|
||||
#[API\Patch(security: 'is_granted("AUTH_PROJECT_EDIT")')]
|
||||
#[API\ApiFilter(filterClass: SearchFilter::class, properties: ['title' => 'partial'])]
|
||||
#[ORM\Entity(repositoryClass: ProjectRepository::class)]
|
||||
class Project
|
||||
{
|
||||
#[ORM\Id]
|
||||
|
||||
+25
-10
@@ -4,8 +4,6 @@ namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\Repository\TransactionRepository;
|
||||
use App\State\TransactionStateProcessor;
|
||||
use App\Validator\GatewayName;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
@@ -17,7 +15,6 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
*/
|
||||
#[ORM\Entity(repositoryClass: TransactionRepository::class)]
|
||||
#[API\GetCollection()]
|
||||
#[API\Post(processor: TransactionStateProcessor::class)]
|
||||
#[API\Get()]
|
||||
class Transaction
|
||||
{
|
||||
@@ -27,7 +24,7 @@ class Transaction
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* The monetary value to be received at target and issued at origin.
|
||||
* The monetary value received at target and issued at origin.
|
||||
*/
|
||||
#[Assert\Valid()]
|
||||
#[Assert\NotBlank()]
|
||||
@@ -51,12 +48,18 @@ class Transaction
|
||||
private ?Accounting $target = null;
|
||||
|
||||
/**
|
||||
* The Gateway processing this Transaction.
|
||||
* The name of the Gateway implementation that secured the money in this Transaction.
|
||||
*/
|
||||
#[Assert\Valid()]
|
||||
#[Assert\NotBlank()]
|
||||
#[ORM\Embedded(class: TransactionGateway::class)]
|
||||
private ?TransactionGateway $gateway = null;
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $gateway = null;
|
||||
|
||||
/**
|
||||
* An external identifier provided by the Gateway for this Transaction.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $gatewayReference = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
@@ -99,15 +102,27 @@ class Transaction
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getGateway(): ?TransactionGateway
|
||||
public function getGateway(): ?string
|
||||
{
|
||||
return $this->gateway;
|
||||
}
|
||||
|
||||
public function setGateway(TransactionGateway $gateway): static
|
||||
public function setGateway(string $gateway): static
|
||||
{
|
||||
$this->gateway = $gateway;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getGatewayReference(): ?string
|
||||
{
|
||||
return $this->gatewayReference;
|
||||
}
|
||||
|
||||
public function setGatewayReference(string $gatewayReference): static
|
||||
{
|
||||
$this->gatewayReference = $gatewayReference;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
+28
-13
@@ -2,11 +2,14 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\Entity\Interface\UserOwnedInterface;
|
||||
use App\Repository\UserRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Gedmo\Mapping\Annotation as Gedmo;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
@@ -19,16 +22,21 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
* Users are the usual issuers of funding, however an User's Accounting can still be a Transaction recipient.
|
||||
* This allows to keep an User's "wallet", witholding their non-raised fundings into their Accounting.
|
||||
*/
|
||||
#[Gedmo\Loggable()]
|
||||
#[API\GetCollection()]
|
||||
#[API\Post()]
|
||||
#[API\Post(validationContext: ['groups' => ['default', 'postValidation']])]
|
||||
#[API\Get()]
|
||||
#[API\Put(security: 'is_granted("AUTH_USER_EDIT")')]
|
||||
#[API\Delete(security: 'is_granted("AUTH_USER_EDIT")')]
|
||||
#[API\Patch(security: 'is_granted("AUTH_USER_EDIT")')]
|
||||
#[API\Put(security: 'is_granted("USER_EDIT", object)')]
|
||||
#[API\Delete(security: 'is_granted("USER_EDIT", object)')]
|
||||
#[API\Patch(security: 'is_granted("USER_EDIT", object)')]
|
||||
#[API\ApiFilter(filterClass: SearchFilter::class, properties: [
|
||||
'username' => 'partial',
|
||||
'name' => 'partial'
|
||||
])]
|
||||
#[UniqueEntity(fields: ['username'], message: 'This usernames already exists.')]
|
||||
#[UniqueEntity(fields: ['email'], message: 'This email address is already registered.')]
|
||||
#[ORM\Entity(repositoryClass: UserRepository::class)]
|
||||
class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
class User implements UserInterface, UserOwnedInterface, PasswordAuthenticatedUserInterface
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
@@ -38,6 +46,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
/**
|
||||
* Human readable, non white space, unique string.
|
||||
*/
|
||||
#[Gedmo\Versioned]
|
||||
#[Assert\NotBlank()]
|
||||
#[Assert\Length(min: 4, max: 30)]
|
||||
#[Assert\Regex('/^[a-z0-9_]+$/')]
|
||||
@@ -61,12 +70,13 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
/**
|
||||
* Plain-text, will be hashed by the platform.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[Assert\NotBlank(['groups' => ['postValidation']])]
|
||||
#[Assert\Length(min: 12)]
|
||||
#[API\ApiProperty(writable: true, readable: false)]
|
||||
#[API\ApiProperty(writable: true, readable: false, required: true)]
|
||||
#[SerializedName('password')]
|
||||
private ?string $plainPassword = null;
|
||||
|
||||
#[Gedmo\Versioned]
|
||||
#[Assert\NotBlank()]
|
||||
#[Assert\Email()]
|
||||
#[ORM\Column(length: 255)]
|
||||
@@ -80,8 +90,8 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
/**
|
||||
* The UserTokens owned by this user. Owner only property.
|
||||
*/
|
||||
#[API\ApiProperty(writable: false, readableLink: true, security: 'is_granted("AUTH_OWNER", object)')]
|
||||
#[ORM\OneToMany(mappedBy: 'ownedBy', targetEntity: UserToken::class, orphanRemoval: true)]
|
||||
#[API\ApiProperty(writable: false, security: 'is_granted("USER_OWNED", object)')]
|
||||
#[ORM\OneToMany(mappedBy: 'owner', targetEntity: UserToken::class, orphanRemoval: true)]
|
||||
private Collection $tokens;
|
||||
|
||||
#[ORM\Column]
|
||||
@@ -152,6 +162,11 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
return (string) $this->username;
|
||||
}
|
||||
|
||||
public function getOwner(): ?User
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isOwnedBy(User $user): bool
|
||||
{
|
||||
return $this->getUserIdentifier() === $user->getUserIdentifier();
|
||||
@@ -201,7 +216,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPlainPassword(): string
|
||||
public function getPlainPassword(): ?string
|
||||
{
|
||||
return $this->plainPassword;
|
||||
}
|
||||
@@ -258,7 +273,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
{
|
||||
if (!$this->tokens->contains($token)) {
|
||||
$this->tokens->add($token);
|
||||
$token->setOwnedBy($this);
|
||||
$token->setOwner($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -268,8 +283,8 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
{
|
||||
if ($this->tokens->removeElement($token)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($token->getOwnedBy() === $this) {
|
||||
$token->setOwnedBy(null);
|
||||
if ($token->getOwner() === $this) {
|
||||
$token->setOwner(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\Dto\UserTokenLoginDto;
|
||||
use App\Entity\Interface\UserOwnedInterface;
|
||||
use App\Repository\UserTokenRepository;
|
||||
use App\State\UserTokenLoginProcessor;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
@@ -18,9 +19,10 @@ use Doctrine\ORM\Mapping as ORM;
|
||||
* `pat_` means the token was created via a login flow.
|
||||
*/
|
||||
#[API\Post(input: UserTokenLoginDto::class, processor: UserTokenLoginProcessor::class)]
|
||||
#[API\Delete(security: 'is_granted("AUTH_OWNER")')]
|
||||
#[API\Get(security: 'is_granted("USER_OWNED", object)')]
|
||||
#[API\Delete(security: 'is_granted("USER_OWNED", object)')]
|
||||
#[ORM\Entity(repositoryClass: UserTokenRepository::class)]
|
||||
class UserToken
|
||||
class UserToken implements UserOwnedInterface
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
@@ -38,7 +40,7 @@ class UserToken
|
||||
*/
|
||||
#[ORM\ManyToOne(inversedBy: 'tokens')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?User $ownedBy = null;
|
||||
private ?User $owner = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
@@ -57,20 +59,20 @@ class UserToken
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOwnedBy(): ?User
|
||||
public function getOwner(): ?User
|
||||
{
|
||||
return $this->ownedBy;
|
||||
return $this->owner;
|
||||
}
|
||||
|
||||
public function setOwnedBy(?User $ownedBy): static
|
||||
public function setOwner(?User $owner): static
|
||||
{
|
||||
$this->ownedBy = $ownedBy;
|
||||
$this->owner = $owner;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isOwnedBy(User $user): bool
|
||||
{
|
||||
return $this->ownedBy->getUserIdentifier() === $user->getUserIdentifier();
|
||||
return $this->owner->getUserIdentifier() === $user->getUserIdentifier();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filter;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
|
||||
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\PropertyInfo\Type;
|
||||
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
|
||||
|
||||
final class VersionResourceFilter extends AbstractFilter
|
||||
{
|
||||
public function __construct(
|
||||
protected ManagerRegistry $managerRegistry,
|
||||
?LoggerInterface $logger = null,
|
||||
protected ?array $properties = null,
|
||||
protected ?NameConverterInterface $nameConverter = null
|
||||
) {
|
||||
parent::__construct($managerRegistry, $logger, $properties, $nameConverter);
|
||||
}
|
||||
|
||||
protected function filterProperty(
|
||||
string $property,
|
||||
$value,
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
string $resourceClass,
|
||||
Operation $operation = null,
|
||||
array $context = []
|
||||
): void {
|
||||
return;
|
||||
}
|
||||
|
||||
public function getDescription(string $resourceClass): array
|
||||
{
|
||||
$description = [];
|
||||
foreach ($this->properties as $property => $strategy) {
|
||||
$description["$property"] = [
|
||||
'property' => $property,
|
||||
'type' => Type::BUILTIN_TYPE_STRING,
|
||||
'required' => true,
|
||||
'description' => 'The name of the resource.',
|
||||
'schema' => [
|
||||
'type' => Type::BUILTIN_TYPE_STRING,
|
||||
'enum' => ['User']
|
||||
],
|
||||
'openapi' => [
|
||||
'allowEmptyValue' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $description;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filter;
|
||||
|
||||
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
|
||||
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Symfony\Component\PropertyInfo\Type;
|
||||
|
||||
final class VersionResourceIdFilter extends AbstractFilter
|
||||
{
|
||||
protected function filterProperty(
|
||||
string $property,
|
||||
$value,
|
||||
QueryBuilder $queryBuilder,
|
||||
QueryNameGeneratorInterface $queryNameGenerator,
|
||||
string $resourceClass,
|
||||
Operation $operation = null,
|
||||
array $context = []
|
||||
): void {
|
||||
return;
|
||||
}
|
||||
|
||||
public function getDescription(string $resourceClass): array
|
||||
{
|
||||
$description = [];
|
||||
foreach ($this->properties as $property => $strategy) {
|
||||
$description["$property"] = [
|
||||
'property' => $property,
|
||||
'type' => Type::BUILTIN_TYPE_INT,
|
||||
'required' => true,
|
||||
'description' => 'The ID of the named resource.',
|
||||
'openapi' => [
|
||||
'allowEmptyValue' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $description;
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ class UsersPump implements PumpInterface
|
||||
foreach ($data as $key => $userData) {
|
||||
$username = $this->normalizeUsername($userData['id']);
|
||||
if (!$username) {
|
||||
continue;
|
||||
$username = $this->normalizeUsername($userData['email']);
|
||||
}
|
||||
|
||||
$user = new User;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Library\Economy\Payment;
|
||||
|
||||
use App\Entity\Transaction;
|
||||
use App\Entity\GatewayCheckout;
|
||||
|
||||
interface GatewayInterface
|
||||
{
|
||||
@@ -12,11 +12,11 @@ interface GatewayInterface
|
||||
public static function getName(): string;
|
||||
|
||||
/**
|
||||
* When processing a Transaction a Gateway must validate it using it's own means.
|
||||
* When processing a GatewayCheckout a Gateway must validate it using it's own means.
|
||||
*
|
||||
* Gateways are trusted to have secured the funds in the Transaction.
|
||||
* @param Transaction
|
||||
* @return Transaction
|
||||
* Gateways are trusted to have secured the funds in the GatewayCheckout.
|
||||
* @param GatewayCheckout $checkout
|
||||
* @return GatewayCheckout
|
||||
*/
|
||||
public function process(Transaction $transaction): Transaction;
|
||||
public function process(GatewayCheckout $checkout): GatewayCheckout;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Library\Economy\Payment;
|
||||
|
||||
use App\Entity\Transaction;
|
||||
use App\Entity\GatewayCheckout;
|
||||
|
||||
class GatewayLocator
|
||||
{
|
||||
@@ -135,12 +135,17 @@ class GatewayLocator
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Transaction $transaction
|
||||
* @param GatewayCheckout $checkout
|
||||
* @return GatewayInterface
|
||||
* @throws \Exception When the $transaction::gateway::name does not match to that of an implemented Gateway
|
||||
* @throws \Exception When the $checkout::gateway does not match to that of an implemented Gateway
|
||||
*/
|
||||
public function getGatewayOf(Transaction $transaction): GatewayInterface
|
||||
public function getGatewayOf(GatewayCheckout $checkout): GatewayInterface
|
||||
{
|
||||
return $this->getGateway($transaction->getGateway()->getName());
|
||||
$gateway = $checkout->getGateway();
|
||||
if (!$gateway) {
|
||||
throw new \Exception("The given GatewayCheckout does not specify a Gateway");
|
||||
}
|
||||
|
||||
return $this->getGateway($gateway);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\GatewayCharge;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<GatewayCharge>
|
||||
*
|
||||
* @method GatewayCharge|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method GatewayCharge|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method GatewayCharge[] findAll()
|
||||
* @method GatewayCharge[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class GatewayChargeRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, GatewayCharge::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return GatewayCharge[] Returns an array of GatewayCharge objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('g')
|
||||
// ->andWhere('g.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('g.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?GatewayCharge
|
||||
// {
|
||||
// return $this->createQueryBuilder('g')
|
||||
// ->andWhere('g.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\GatewayCheckout;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<GatewayCheckout>
|
||||
*
|
||||
* @method GatewayCheckout|null find($id, $lockMode = null, $lockVersion = null)
|
||||
* @method GatewayCheckout|null findOneBy(array $criteria, array $orderBy = null)
|
||||
* @method GatewayCheckout[] findAll()
|
||||
* @method GatewayCheckout[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
|
||||
*/
|
||||
class GatewayCheckoutRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, GatewayCheckout::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return GatewayCheckout[] Returns an array of GatewayCheckout objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('g')
|
||||
// ->andWhere('g.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('g.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?GatewayCheckout
|
||||
// {
|
||||
// return $this->createQueryBuilder('g')
|
||||
// ->andWhere('g.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
@@ -21,6 +21,6 @@ class AccessTokenHandler implements AccessTokenHandlerInterface
|
||||
throw new BadCredentialsException();
|
||||
}
|
||||
|
||||
return new UserBadge($token->getOwnedBy()->getUserIdentifier());
|
||||
return new UserBadge($token->getOwner()->getUserIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,28 @@
|
||||
|
||||
namespace App\Security\Voter;
|
||||
|
||||
use App\Entity\Interface\UserOwnedInterface;
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
class AuthOwnerVoter extends Voter
|
||||
class UserOwnedVoter extends Voter
|
||||
{
|
||||
public const OWNER = 'AUTH_OWNER';
|
||||
public const OWNED = 'USER_OWNED';
|
||||
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
return in_array($attribute, [self::OWNER])
|
||||
&& $subject !== null
|
||||
&& method_exists($subject, 'isOwnedBy');
|
||||
if (!in_array($attribute, [self::OWNED])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $subject instanceof UserOwnedInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User|UserOwnedInterface $subject
|
||||
*/
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
$user = $token->getUser();
|
||||
@@ -27,7 +34,5 @@ class AuthOwnerVoter extends Voter
|
||||
}
|
||||
|
||||
return $subject->isOwnedBy($user);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Security\Voter;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
class UserVoter extends Voter
|
||||
{
|
||||
public const EDIT = 'USER_EDIT';
|
||||
public const VIEW = 'USER_VIEW';
|
||||
|
||||
protected function supports(string $attribute, mixed $subject): bool
|
||||
{
|
||||
return in_array($attribute, [self::EDIT, self::VIEW])
|
||||
&& $subject instanceof User;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $subject
|
||||
*/
|
||||
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
|
||||
{
|
||||
$user = $token->getUser();
|
||||
|
||||
if (!$user instanceof UserInterface) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ($attribute) {
|
||||
case self::EDIT:
|
||||
return $user->getUserIdentifier() === $subject->getUserIdentifier()
|
||||
|| \in_array('ROLE_ADMIN', $user->getRoles());
|
||||
case self::VIEW:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ class AuthService
|
||||
{
|
||||
$token = new UserToken;
|
||||
|
||||
$token->setOwnedBy($user);
|
||||
$token->setOwner($user);
|
||||
$token->setToken(sprintf('%s%s', $type->value, hash(
|
||||
self::TOKEN_HASH_ALGO,
|
||||
join('', [
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\State;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProcessorInterface;
|
||||
use App\Entity\GatewayCheckout;
|
||||
use App\Library\Economy\Payment\GatewayLocator;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class GatewayCheckoutProcessor implements ProcessorInterface
|
||||
{
|
||||
public function __construct(
|
||||
private GatewayLocator $gatewayLocator,
|
||||
private EntityManagerInterface $entityManager
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param GatewayCheckout $data
|
||||
*/
|
||||
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): GatewayCheckout
|
||||
{
|
||||
$gateway = $this->gatewayLocator->getGatewayOf($data);
|
||||
|
||||
$this->entityManager->persist($data);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$gateway->process($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\State;
|
||||
|
||||
use ApiPlatform\Metadata as API;
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\ApiResource\Version;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Gedmo\Loggable\Entity\LogEntry;
|
||||
use Gedmo\Loggable\Entity\Repository\LogEntryRepository;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
class VersionStateProvider implements ProviderInterface
|
||||
{
|
||||
private LogEntryRepository $versionRepository;
|
||||
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager
|
||||
) {
|
||||
$this->versionRepository = $this->entityManager->getRepository(LogEntry::class);
|
||||
}
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
|
||||
{
|
||||
switch ($operation::class) {
|
||||
case API\Get::class:
|
||||
return $this->getVersion($uriVariables['id']);
|
||||
case API\GetCollection::class:
|
||||
return $this->getVersions(
|
||||
$context['request']->query->get('resource'),
|
||||
$context['request']->query->get('resourceId')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundHttpException
|
||||
*/
|
||||
private function getVersion(int $id): Version
|
||||
{
|
||||
$log = $this->versionRepository->find($id);
|
||||
|
||||
if (!$log) {
|
||||
throw new NotFoundHttpException("Not Found");
|
||||
}
|
||||
|
||||
$entity = $this->entityManager->find($log->getObjectClass(), $log->getObjectId());
|
||||
|
||||
return new Version($log, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Version[]
|
||||
*/
|
||||
private function getVersions(string $resource, int $resourceId): array
|
||||
{
|
||||
$entity = $this->entityManager->find($this->resourceToEntity($resource), $resourceId);
|
||||
$logs = $this->versionRepository->getLogEntries($entity);
|
||||
|
||||
$versions = [];
|
||||
foreach ($logs as $log) {
|
||||
$versions[] = new Version($log, $entity);
|
||||
}
|
||||
|
||||
return $versions;
|
||||
}
|
||||
|
||||
private function resourceToEntity(string $resource): string
|
||||
{
|
||||
return sprintf("App\\Entity\\%s", ucfirst(strtolower($resource)));
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,18 @@
|
||||
"tests/bootstrap.php"
|
||||
]
|
||||
},
|
||||
"stof/doctrine-extensions-bundle": {
|
||||
"version": "1.11",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "main",
|
||||
"version": "1.2",
|
||||
"ref": "e805aba9eff5372e2d149a9ff56566769e22819d"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/stof_doctrine_extensions.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/console": {
|
||||
"version": "6.3",
|
||||
"recipe": {
|
||||
|
||||
Reference in New Issue
Block a user