diff --git a/src/Entity/Parts/InfoProviderReference.php b/src/Entity/Parts/InfoProviderReference.php index 53b81a0a..ea0fae7f 100644 --- a/src/Entity/Parts/InfoProviderReference.php +++ b/src/Entity/Parts/InfoProviderReference.php @@ -27,6 +27,7 @@ use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Embeddable; +use Symfony\Component\Serializer\Annotation\Groups; /** * This class represents a reference to a info provider inside a part. @@ -37,19 +38,23 @@ class InfoProviderReference /** @var string|null The key referencing the provider used to get this part, or null if it was not provided by a data provider */ #[Column(type: 'string', nullable: true)] + #[Groups(['provider_reference:read'])] private ?string $provider_key = null; /** @var string|null The id of this part inside the provider system or null if the part was not provided by a data provider */ #[Column(type: 'string', nullable: true)] + #[Groups(['provider_reference:read'])] private ?string $provider_id = null; /** * @var string|null The url of this part inside the provider system or null if this info is not existing */ #[Column(type: 'string', nullable: true)] + #[Groups(['provider_reference:read'])] private ?string $provider_url = null; #[Column(type: Types::DATETIME_MUTABLE, nullable: true, options: ['default' => null])] + #[Groups(['provider_reference:read'])] private ?\DateTimeInterface $last_updated = null; /** diff --git a/src/Entity/Parts/MeasurementUnit.php b/src/Entity/Parts/MeasurementUnit.php index d54ecf3b..18c6e2ed 100644 --- a/src/Entity/Parts/MeasurementUnit.php +++ b/src/Entity/Parts/MeasurementUnit.php @@ -61,7 +61,7 @@ use Symfony\Component\Validator\Constraints as Assert; #[ApiResource( operations: [ new Get(security: 'is_granted("read", object)'), - new GetCollection(security: 'is_granted("@measurement_unit.read")'), + new GetCollection(security: 'is_granted("@measurement_units.read")'), new Post(securityPostDenormalize: 'is_granted("create", object)'), new Patch(security: 'is_granted("edit", object)'), new Delete(security: 'is_granted("delete", object)'), @@ -73,7 +73,7 @@ use Symfony\Component\Validator\Constraints as Assert; uriTemplate: '/footprints/{id}/children.{_format}', operations: [ new GetCollection(openapiContext: ['summary' => 'Retrieves the children elements of a MeasurementUnit.'], - security: 'is_granted("@measurement_unit.read")') + security: 'is_granted("@measurement_units.read")') ], uriVariables: [ 'id' => new Link(fromProperty: 'children', fromClass: MeasurementUnit::class) diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index 5916569b..096c36b0 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -22,6 +22,15 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Serializer\Filter\PropertyFilter; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Repository\PartRepository; use Doctrine\DBAL\Types\Types; @@ -60,6 +69,18 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; #[ORM\Index(name: 'parts_idx_datet_name_last_id_needs', columns: ['datetime_added', 'name', 'last_modified', 'id', 'needs_review'])] #[ORM\Index(name: 'parts_idx_name', columns: ['name'])] #[ORM\Index(name: 'parts_idx_ipn', columns: ['ipn'])] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@parts.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['part:read', 'provider_reference:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['part:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiFilter(PropertyFilter::class)] class Part extends AttachmentContainingDBElement { use AdvancedPropertyTrait; diff --git a/src/Entity/Parts/PartLot.php b/src/Entity/Parts/PartLot.php index e552c06a..b4f22eab 100644 --- a/src/Entity/Parts/PartLot.php +++ b/src/Entity/Parts/PartLot.php @@ -22,6 +22,14 @@ declare(strict_types=1); namespace App\Entity\Parts; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\Delete; +use ApiPlatform\Metadata\Get; +use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Patch; +use ApiPlatform\Metadata\Post; +use ApiPlatform\Serializer\Filter\PropertyFilter; use App\Repository\PartLotRepository; use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; @@ -50,6 +58,18 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; #[ORM\Index(name: 'part_lots_idx_instock_un_expiration_id_part', columns: ['instock_unknown', 'expiration_date', 'id_part'])] #[ORM\Index(name: 'part_lots_idx_needs_refill', columns: ['needs_refill'])] #[ValidPartLot] +#[ApiResource( + operations: [ + new Get(security: 'is_granted("read", object)'), + new GetCollection(security: 'is_granted("@parts.read")'), + new Post(securityPostDenormalize: 'is_granted("create", object)'), + new Patch(security: 'is_granted("edit", object)'), + new Delete(security: 'is_granted("delete", object)'), + ], + normalizationContext: ['groups' => ['part_lot:read', 'api:basic:read'], 'openapi_definition_name' => 'Read'], + denormalizationContext: ['groups' => ['part_lot:write', 'api:basic:write'], 'openapi_definition_name' => 'Write'], +)] +#[ApiFilter(PropertyFilter::class)] class PartLot extends AbstractDBElement implements TimeStampableInterface, NamedElementInterface { use TimestampTrait; @@ -57,14 +77,14 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * @var string A short description about this lot, shown in table */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(type: Types::TEXT)] protected string $description = ''; /** * @var string a comment stored with this lot */ - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(type: Types::TEXT)] protected string $comment = ''; @@ -72,14 +92,14 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named * @var \DateTimeInterface|null Set a time until when the lot must be used. * Set to null, if the lot can be used indefinitely. */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(type: Types::DATETIME_MUTABLE, name: 'expiration_date', nullable: true)] protected ?\DateTimeInterface $expiration_date = null; /** * @var Storelocation|null The storelocation of this lot */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\ManyToOne(targetEntity: Storelocation::class, fetch: 'EAGER')] #[ORM\JoinColumn(name: 'id_store_location')] #[Selectable()] @@ -88,7 +108,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * @var bool If this is set to true, the instock amount is marked as not known */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $instock_unknown = false; @@ -96,14 +116,14 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named * @var float For continuous sizes (length, volume, etc.) the instock is saved here. */ #[Assert\PositiveOrZero] - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(type: Types::FLOAT)] protected float $amount = 0.0; /** * @var bool determines if this lot was manually marked for refilling */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part_lot:read', 'part_lot:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $needs_refill = false; @@ -113,6 +133,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named #[Assert\NotNull] #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'partLots')] #[ORM\JoinColumn(name: 'id_part', nullable: false, onDelete: 'CASCADE')] + #[Groups(['part_lot:read', 'part_lot:write'])] protected ?Part $part = null; /** @@ -120,6 +141,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named */ #[ORM\ManyToOne(targetEntity: User::class)] #[ORM\JoinColumn(name: 'id_owner', onDelete: 'SET NULL')] + #[Groups(['part_lot:read', 'part_lot:write'])] protected ?User $owner = null; public function __clone() diff --git a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php index 648cf2a5..712d1c2a 100644 --- a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php +++ b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts\PartTraits; +use ApiPlatform\Metadata\ApiProperty; use App\Entity\Parts\InfoProviderReference; use Doctrine\DBAL\Types\Types; use App\Entity\Parts\Part; @@ -37,14 +38,14 @@ trait AdvancedPropertyTrait /** * @var bool Determines if this part entry needs review (for example, because it is work in progress) */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $needs_review = false; /** * @var string a comma separated list of tags, associated with the part */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::TEXT)] protected string $tags = ''; @@ -52,7 +53,7 @@ trait AdvancedPropertyTrait * @var float|null how much a single part unit weighs in grams */ #[Assert\PositiveOrZero] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::FLOAT, nullable: true)] protected ?float $mass = null; @@ -60,14 +61,15 @@ trait AdvancedPropertyTrait * @var string|null The internal part number of the part */ #[Assert\Length(max: 100)] - #[Groups(['extended', 'full', 'import'])] - #[ORM\Column(type: Types::STRING, length: 100, nullable: true, unique: true)] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] + #[ORM\Column(type: Types::STRING, length: 100, unique: true, nullable: true)] protected ?string $ipn = null; /** * @var InfoProviderReference The reference to the info provider, that provided the information about this part */ #[ORM\Embedded(class: InfoProviderReference::class, columnPrefix: 'provider_reference_')] + #[Groups(['full', 'part:read'])] protected InfoProviderReference $providerReference; /** diff --git a/src/Entity/Parts/PartTraits/BasicPropertyTrait.php b/src/Entity/Parts/PartTraits/BasicPropertyTrait.php index b0f593d6..ea68db38 100644 --- a/src/Entity/Parts/PartTraits/BasicPropertyTrait.php +++ b/src/Entity/Parts/PartTraits/BasicPropertyTrait.php @@ -35,14 +35,14 @@ trait BasicPropertyTrait /** * @var string A text describing what this part does */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::TEXT)] protected string $description = ''; /** * @var string A comment/note related to this part */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::TEXT)] protected string $comment = ''; @@ -55,7 +55,7 @@ trait BasicPropertyTrait /** * @var bool true, if the part is marked as favorite */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::BOOLEAN)] protected bool $favorite = false; @@ -65,7 +65,7 @@ trait BasicPropertyTrait */ #[Assert\NotNull(message: 'validator.select_valid_category')] #[Selectable()] - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', "part:read", "part:write"])] #[ORM\ManyToOne(targetEntity: Category::class)] #[ORM\JoinColumn(name: 'id_category', nullable: false)] protected ?Category $category = null; @@ -73,7 +73,7 @@ trait BasicPropertyTrait /** * @var Footprint|null The footprint of this part (e.g. DIP8) */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\ManyToOne(targetEntity: Footprint::class)] #[ORM\JoinColumn(name: 'id_footprint')] #[Selectable()] diff --git a/src/Entity/Parts/PartTraits/InstockTrait.php b/src/Entity/Parts/PartTraits/InstockTrait.php index 068173a7..f8d27cb3 100644 --- a/src/Entity/Parts/PartTraits/InstockTrait.php +++ b/src/Entity/Parts/PartTraits/InstockTrait.php @@ -49,14 +49,14 @@ trait InstockTrait * Given in the partUnit. */ #[Assert\PositiveOrZero] - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::FLOAT)] protected float $minamount = 0; /** * @var ?MeasurementUnit the unit in which the part's amount is measured */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\ManyToOne(targetEntity: MeasurementUnit::class)] #[ORM\JoinColumn(name: 'id_part_unit')] protected ?MeasurementUnit $partUnit = null; diff --git a/src/Entity/Parts/PartTraits/ManufacturerTrait.php b/src/Entity/Parts/PartTraits/ManufacturerTrait.php index 97ef246b..12643768 100644 --- a/src/Entity/Parts/PartTraits/ManufacturerTrait.php +++ b/src/Entity/Parts/PartTraits/ManufacturerTrait.php @@ -39,7 +39,7 @@ trait ManufacturerTrait /** * @var Manufacturer|null The manufacturer of this part */ - #[Groups(['simple', 'extended', 'full', 'import'])] + #[Groups(['simple', 'extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\ManyToOne(targetEntity: Manufacturer::class)] #[ORM\JoinColumn(name: 'id_manufacturer')] #[Selectable()] @@ -49,21 +49,21 @@ trait ManufacturerTrait * @var string the url to the part on the manufacturer's homepage */ #[Assert\Url] - #[Groups(['full', 'import'])] + #[Groups(['full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::TEXT)] protected string $manufacturer_product_url = ''; /** * @var string The product number used by the manufacturer. If this is set to "", the name field is used. */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::STRING)] protected string $manufacturer_product_number = ''; /** * @var ManufacturingStatus|null The production status of this part. Can be one of the specified ones. */ - #[Groups(['extended', 'full', 'import'])] + #[Groups(['extended', 'full', 'import', 'part:read', 'part:write'])] #[ORM\Column(type: Types::STRING, length: 255, nullable: true, enumType: ManufacturingStatus::class)] protected ?ManufacturingStatus $manufacturing_status = ManufacturingStatus::NOT_SET; diff --git a/src/Entity/Parts/PartTraits/ProjectTrait.php b/src/Entity/Parts/PartTraits/ProjectTrait.php index 51208b6a..71673f06 100644 --- a/src/Entity/Parts/PartTraits/ProjectTrait.php +++ b/src/Entity/Parts/PartTraits/ProjectTrait.php @@ -9,12 +9,10 @@ use App\Entity\ProjectSystem\ProjectBOMEntry; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Serializer\Annotation\Groups; trait ProjectTrait { - /** - * @var Collection $project_bom_entries - */ /** * @var Collection $project_bom_entries */ @@ -42,6 +40,7 @@ trait ProjectTrait * Checks whether this part represents the builds of a project * @return bool True if it represents the builds, false if not */ + #[Groups(['part:read'])] public function isProjectBuildPart(): bool { return $this->built_project !== null;