| @@ -1,73 +0,0 @@ | |||
| <?php | |||
| declare(strict_types=1); | |||
| namespace DoctrineMigrations; | |||
| use Doctrine\DBAL\Schema\Schema; | |||
| use Doctrine\Migrations\AbstractMigration; | |||
| /** | |||
| * Auto-generated Migration: Please modify to your needs! | |||
| */ | |||
| final class Version20240318162352 extends AbstractMigration | |||
| { | |||
| public function getDescription(): string | |||
| { | |||
| return ''; | |||
| } | |||
| public function up(Schema $schema): void | |||
| { | |||
| // this up() migration is auto-generated, please modify it to your needs | |||
| $this->addSql('CREATE TABLE comment (id INT AUTO_INCREMENT NOT NULL, owner_id INT NOT NULL, posting_id INT NOT NULL, message LONGTEXT NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_9474526C7E3C61F9 (owner_id), INDEX IDX_9474526C9AE985F6 (posting_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||
| $this->addSql('CREATE TABLE contact (id INT AUTO_INCREMENT NOT NULL, partner_id INT NOT NULL, image_id INT DEFAULT NULL, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, birthday DATE DEFAULT NULL, position VARCHAR(255) DEFAULT NULL, phone VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_4C62E6389393F8FE (partner_id), INDEX IDX_4C62E6383DA5256D (image_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||
| $this->addSql('CREATE TABLE media_object (id INT AUTO_INCREMENT NOT NULL, file_path VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||
| $this->addSql('CREATE TABLE partner (id INT AUTO_INCREMENT NOT NULL, logo_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, type VARCHAR(255) NOT NULL, street VARCHAR(255) DEFAULT NULL, street_no VARCHAR(255) DEFAULT NULL, zip VARCHAR(255) DEFAULT NULL, city VARCHAR(255) DEFAULT NULL, country VARCHAR(255) DEFAULT NULL, website VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_312B3E16F98F144A (logo_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||
| $this->addSql('CREATE TABLE posting (id INT AUTO_INCREMENT NOT NULL, owner_id INT NOT NULL, partner_id INT NOT NULL, contact_id INT DEFAULT NULL, headline VARCHAR(255) NOT NULL, message LONGTEXT NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_BD275D737E3C61F9 (owner_id), INDEX IDX_BD275D739393F8FE (partner_id), INDEX IDX_BD275D73E7A1254A (contact_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||
| $this->addSql('CREATE TABLE product (id INT AUTO_INCREMENT NOT NULL, image_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, description LONGTEXT DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_D34A04AD3DA5256D (image_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||
| $this->addSql('CREATE TABLE task (id INT AUTO_INCREMENT NOT NULL, created_by_id INT NOT NULL, assigned_to_id INT NOT NULL, partner_id INT DEFAULT NULL, contact_id INT DEFAULT NULL, headline VARCHAR(255) NOT NULL, description LONGTEXT DEFAULT NULL, due_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', prio VARCHAR(255) NOT NULL, completed TINYINT(1) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_527EDB25B03A8386 (created_by_id), INDEX IDX_527EDB25F4BD7827 (assigned_to_id), INDEX IDX_527EDB259393F8FE (partner_id), INDEX IDX_527EDB25E7A1254A (contact_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||
| $this->addSql('CREATE TABLE `user` (id INT AUTO_INCREMENT NOT NULL, image_id INT DEFAULT NULL, email VARCHAR(180) NOT NULL, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, roles JSON NOT NULL COMMENT \'(DC2Type:json)\', password VARCHAR(255) NOT NULL, active TINYINT(1) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_8D93D649E7927C74 (email), INDEX IDX_8D93D6493DA5256D (image_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||
| $this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526C7E3C61F9 FOREIGN KEY (owner_id) REFERENCES `user` (id)'); | |||
| $this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526C9AE985F6 FOREIGN KEY (posting_id) REFERENCES posting (id) ON DELETE CASCADE'); | |||
| $this->addSql('ALTER TABLE contact ADD CONSTRAINT FK_4C62E6389393F8FE FOREIGN KEY (partner_id) REFERENCES partner (id) ON DELETE CASCADE'); | |||
| $this->addSql('ALTER TABLE contact ADD CONSTRAINT FK_4C62E6383DA5256D FOREIGN KEY (image_id) REFERENCES media_object (id) ON DELETE SET NULL'); | |||
| $this->addSql('ALTER TABLE partner ADD CONSTRAINT FK_312B3E16F98F144A FOREIGN KEY (logo_id) REFERENCES media_object (id) ON DELETE SET NULL'); | |||
| $this->addSql('ALTER TABLE posting ADD CONSTRAINT FK_BD275D737E3C61F9 FOREIGN KEY (owner_id) REFERENCES `user` (id) ON DELETE CASCADE'); | |||
| $this->addSql('ALTER TABLE posting ADD CONSTRAINT FK_BD275D739393F8FE FOREIGN KEY (partner_id) REFERENCES partner (id) ON DELETE CASCADE'); | |||
| $this->addSql('ALTER TABLE posting ADD CONSTRAINT FK_BD275D73E7A1254A FOREIGN KEY (contact_id) REFERENCES contact (id) ON DELETE CASCADE'); | |||
| $this->addSql('ALTER TABLE product ADD CONSTRAINT FK_D34A04AD3DA5256D FOREIGN KEY (image_id) REFERENCES media_object (id) ON DELETE SET NULL'); | |||
| $this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB25B03A8386 FOREIGN KEY (created_by_id) REFERENCES `user` (id)'); | |||
| $this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB25F4BD7827 FOREIGN KEY (assigned_to_id) REFERENCES `user` (id)'); | |||
| $this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB259393F8FE FOREIGN KEY (partner_id) REFERENCES partner (id) ON DELETE CASCADE'); | |||
| $this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB25E7A1254A FOREIGN KEY (contact_id) REFERENCES contact (id) ON DELETE CASCADE'); | |||
| $this->addSql('ALTER TABLE `user` ADD CONSTRAINT FK_8D93D6493DA5256D FOREIGN KEY (image_id) REFERENCES media_object (id) ON DELETE SET NULL'); | |||
| } | |||
| public function down(Schema $schema): void | |||
| { | |||
| // this down() migration is auto-generated, please modify it to your needs | |||
| $this->addSql('ALTER TABLE comment DROP FOREIGN KEY FK_9474526C7E3C61F9'); | |||
| $this->addSql('ALTER TABLE comment DROP FOREIGN KEY FK_9474526C9AE985F6'); | |||
| $this->addSql('ALTER TABLE contact DROP FOREIGN KEY FK_4C62E6389393F8FE'); | |||
| $this->addSql('ALTER TABLE contact DROP FOREIGN KEY FK_4C62E6383DA5256D'); | |||
| $this->addSql('ALTER TABLE partner DROP FOREIGN KEY FK_312B3E16F98F144A'); | |||
| $this->addSql('ALTER TABLE posting DROP FOREIGN KEY FK_BD275D737E3C61F9'); | |||
| $this->addSql('ALTER TABLE posting DROP FOREIGN KEY FK_BD275D739393F8FE'); | |||
| $this->addSql('ALTER TABLE posting DROP FOREIGN KEY FK_BD275D73E7A1254A'); | |||
| $this->addSql('ALTER TABLE product DROP FOREIGN KEY FK_D34A04AD3DA5256D'); | |||
| $this->addSql('ALTER TABLE task DROP FOREIGN KEY FK_527EDB25B03A8386'); | |||
| $this->addSql('ALTER TABLE task DROP FOREIGN KEY FK_527EDB25F4BD7827'); | |||
| $this->addSql('ALTER TABLE task DROP FOREIGN KEY FK_527EDB259393F8FE'); | |||
| $this->addSql('ALTER TABLE task DROP FOREIGN KEY FK_527EDB25E7A1254A'); | |||
| $this->addSql('ALTER TABLE `user` DROP FOREIGN KEY FK_8D93D6493DA5256D'); | |||
| $this->addSql('DROP TABLE comment'); | |||
| $this->addSql('DROP TABLE contact'); | |||
| $this->addSql('DROP TABLE media_object'); | |||
| $this->addSql('DROP TABLE partner'); | |||
| $this->addSql('DROP TABLE posting'); | |||
| $this->addSql('DROP TABLE product'); | |||
| $this->addSql('DROP TABLE task'); | |||
| $this->addSql('DROP TABLE `user`'); | |||
| } | |||
| } | |||
| @@ -0,0 +1,49 @@ | |||
| <?php | |||
| declare(strict_types=1); | |||
| namespace DoctrineMigrations; | |||
| use Doctrine\DBAL\Schema\Schema; | |||
| use Doctrine\Migrations\AbstractMigration; | |||
| /** | |||
| * Auto-generated Migration: Please modify to your needs! | |||
| */ | |||
| final class Version20240319114858 extends AbstractMigration | |||
| { | |||
| public function getDescription(): string | |||
| { | |||
| return ''; | |||
| } | |||
| public function up(Schema $schema): void | |||
| { | |||
| // this up() migration is auto-generated, please modify it to your needs | |||
| $this->addSql('ALTER TABLE sale ADD CONSTRAINT FK_E54BC0057E3C61F9 FOREIGN KEY (owner_id) REFERENCES `user` (id) ON DELETE CASCADE'); | |||
| $this->addSql('ALTER TABLE sale ADD CONSTRAINT FK_E54BC0059393F8FE FOREIGN KEY (partner_id) REFERENCES partner (id) ON DELETE CASCADE'); | |||
| $this->addSql('ALTER TABLE sale ADD CONSTRAINT FK_E54BC0054584665A FOREIGN KEY (product_id) REFERENCES product (id) ON DELETE CASCADE'); | |||
| $this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB25B03A8386 FOREIGN KEY (created_by_id) REFERENCES `user` (id)'); | |||
| $this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB25F4BD7827 FOREIGN KEY (assigned_to_id) REFERENCES `user` (id)'); | |||
| $this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB259393F8FE FOREIGN KEY (partner_id) REFERENCES partner (id) ON DELETE CASCADE'); | |||
| $this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB25E7A1254A FOREIGN KEY (contact_id) REFERENCES contact (id) ON DELETE CASCADE'); | |||
| $this->addSql('ALTER TABLE task_note ADD CONSTRAINT FK_BC0E6E6F7E3C61F9 FOREIGN KEY (owner_id) REFERENCES `user` (id) ON DELETE CASCADE'); | |||
| $this->addSql('ALTER TABLE task_note ADD CONSTRAINT FK_BC0E6E6F8DB60186 FOREIGN KEY (task_id) REFERENCES task (id) ON DELETE CASCADE'); | |||
| $this->addSql('ALTER TABLE user ADD CONSTRAINT FK_8D93D6493DA5256D FOREIGN KEY (image_id) REFERENCES media_object (id) ON DELETE SET NULL'); | |||
| } | |||
| public function down(Schema $schema): void | |||
| { | |||
| // this down() migration is auto-generated, please modify it to your needs | |||
| $this->addSql('ALTER TABLE task_note DROP FOREIGN KEY FK_BC0E6E6F7E3C61F9'); | |||
| $this->addSql('ALTER TABLE task_note DROP FOREIGN KEY FK_BC0E6E6F8DB60186'); | |||
| $this->addSql('ALTER TABLE sale DROP FOREIGN KEY FK_E54BC0057E3C61F9'); | |||
| $this->addSql('ALTER TABLE sale DROP FOREIGN KEY FK_E54BC0059393F8FE'); | |||
| $this->addSql('ALTER TABLE sale DROP FOREIGN KEY FK_E54BC0054584665A'); | |||
| $this->addSql('ALTER TABLE task DROP FOREIGN KEY FK_527EDB25B03A8386'); | |||
| $this->addSql('ALTER TABLE task DROP FOREIGN KEY FK_527EDB25F4BD7827'); | |||
| $this->addSql('ALTER TABLE task DROP FOREIGN KEY FK_527EDB259393F8FE'); | |||
| $this->addSql('ALTER TABLE task DROP FOREIGN KEY FK_527EDB25E7A1254A'); | |||
| $this->addSql('ALTER TABLE `user` DROP FOREIGN KEY FK_8D93D6493DA5256D'); | |||
| } | |||
| } | |||
| @@ -13,6 +13,7 @@ use ApiPlatform\Metadata\ApiFilter; | |||
| use ApiPlatform\Metadata\ApiProperty; | |||
| use ApiPlatform\Metadata\ApiResource; | |||
| use App\Entity\Comment; | |||
| use App\Interface\OwnerInterface; | |||
| use App\State\EntityClassDtoStateProcessor; | |||
| use App\State\EntityToDtoStateProvider; | |||
| use ApiPlatform\Metadata\Delete; | |||
| @@ -30,7 +31,9 @@ use Symfony\Component\Validator\Constraints\NotBlank; | |||
| new Get( | |||
| security: 'is_granted("ROLE_USER")' | |||
| ), | |||
| new GetCollection(), | |||
| new GetCollection( | |||
| security: 'is_granted("ROLE_USER")', | |||
| ), | |||
| new Post( | |||
| security: 'is_granted("ROLE_USER")', | |||
| ), | |||
| @@ -47,7 +50,7 @@ use Symfony\Component\Validator\Constraints\NotBlank; | |||
| stateOptions: new Options(entityClass: Comment::class), | |||
| )] | |||
| #[ApiFilter(SearchFilter::class, properties: ['partner' => 'exact', 'contact' => 'exact'])] | |||
| class CommentApi | |||
| class CommentApi implements OwnerInterface | |||
| { | |||
| #[ApiProperty(readable: false, writable: false, identifier: true)] | |||
| public ?int $id = null; | |||
| @@ -66,4 +69,8 @@ class CommentApi | |||
| #[ApiProperty(writable: false)] | |||
| public ?\DateTimeImmutable $createdAt = null; | |||
| public function getOwner(): UserApi | |||
| { | |||
| return $this->owner; | |||
| } | |||
| } | |||
| @@ -83,7 +83,7 @@ class PartnerApi | |||
| public ?\DateTimeImmutable $createdAt = null; | |||
| /** | |||
| * @var array<int, PostingApi> | |||
| * @var $posts array<int, PostingApi> | |||
| */ | |||
| #[ApiProperty(writable: false)] | |||
| public array $posts = []; | |||
| @@ -13,6 +13,7 @@ use ApiPlatform\Metadata\ApiFilter; | |||
| use ApiPlatform\Metadata\ApiProperty; | |||
| use ApiPlatform\Metadata\ApiResource; | |||
| use App\Entity\Posting; | |||
| use App\Interface\OwnerInterface; | |||
| use App\State\EntityClassDtoStateProcessor; | |||
| use App\State\EntityToDtoStateProvider; | |||
| use ApiPlatform\Metadata\Delete; | |||
| @@ -53,7 +54,7 @@ use Symfony\Component\Validator\Constraints\NotBlank; | |||
| stateOptions: new Options(entityClass: Posting::class) | |||
| )] | |||
| #[ApiFilter(SearchFilter::class, properties: ['partner' => 'exact', 'contact' => 'exact'])] | |||
| class PostingApi | |||
| class PostingApi implements OwnerInterface | |||
| { | |||
| #[ApiProperty(readable: false, writable: false, identifier: true)] | |||
| public ?int $id = null; | |||
| @@ -81,6 +82,10 @@ class PostingApi | |||
| #[Groups(['posting:create'])] | |||
| public ?ContactApi $contact = null; | |||
| #[ApiProperty(writable: true)] | |||
| #[Groups(['posting:create'])] | |||
| public ?SaleApi $sale = null; | |||
| /** | |||
| * @var $comments array<int, CommentApi> | |||
| */ | |||
| @@ -101,4 +106,8 @@ class PostingApi | |||
| #[ApiProperty(writable: false)] | |||
| public ?\DateTimeImmutable $createdAt = null; | |||
| public function getOwner(): UserApi | |||
| { | |||
| return $this->owner; | |||
| } | |||
| } | |||
| @@ -0,0 +1,92 @@ | |||
| <?php | |||
| /** | |||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||
| * @date 12.12.23 | |||
| */ | |||
| namespace App\ApiResource; | |||
| use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; | |||
| use ApiPlatform\Doctrine\Orm\State\Options; | |||
| use ApiPlatform\Metadata\ApiFilter; | |||
| use ApiPlatform\Metadata\ApiProperty; | |||
| use ApiPlatform\Metadata\ApiResource; | |||
| use App\Entity\Sale; | |||
| use App\Interface\OwnerInterface; | |||
| use App\State\EntityClassDtoStateProcessor; | |||
| use App\State\EntityToDtoStateProvider; | |||
| use ApiPlatform\Metadata\Delete; | |||
| use ApiPlatform\Metadata\Get; | |||
| use ApiPlatform\Metadata\GetCollection; | |||
| use ApiPlatform\Metadata\Patch; | |||
| use ApiPlatform\Metadata\Post; | |||
| use Symfony\Component\Validator\Constraints\NotBlank; | |||
| #[ApiResource( | |||
| shortName: 'Sale', | |||
| operations: [ | |||
| new Get( | |||
| security: 'is_granted("ROLE_USER")' | |||
| ), | |||
| new GetCollection(), | |||
| new Post( | |||
| security: 'is_granted("ROLE_USER")', | |||
| ), | |||
| new Patch( | |||
| security: 'is_granted("EDIT", object)', | |||
| ), | |||
| new Delete( | |||
| security: 'is_granted("ROLE_ADMIN")', | |||
| ) | |||
| ], | |||
| security: 'is_granted("ROLE_USER")', | |||
| provider: EntityToDtoStateProvider::class, | |||
| processor: EntityClassDtoStateProcessor::class, | |||
| stateOptions: new Options(entityClass: Sale::class), | |||
| )] | |||
| #[ApiFilter(SearchFilter::class, properties: ['owner' => 'exact', 'partner' => 'exact'])] | |||
| class SaleApi implements OwnerInterface | |||
| { | |||
| #[ApiProperty(readable: false, writable: false, identifier: true)] | |||
| public ?int $id = null; | |||
| #[ApiProperty(writable: false)] | |||
| public ?UserApi $owner = null; | |||
| #[ApiProperty(writable: false)] | |||
| public ?string $ownerName = null; | |||
| #[ApiProperty(writable: true)] | |||
| public ?PartnerApi $partner = null; | |||
| #[ApiProperty(writable: false)] | |||
| public ?string $partnerName = null; | |||
| #[ApiProperty(writable: true)] | |||
| public ?ProductApi $product = null; | |||
| #[ApiProperty(writable: false)] | |||
| public ?string $productName = null; | |||
| #[NotBlank] | |||
| public ?int $turnover = null; | |||
| public ?int $profit = null; | |||
| public ?string $comment = null; | |||
| #[ApiProperty(writable: false)] | |||
| public ?\DateTimeImmutable $createdAt = null; | |||
| /** | |||
| * @var $posts array<int, PostingApi> | |||
| */ | |||
| #[ApiProperty(writable: false)] | |||
| public array $posts = []; | |||
| public function getOwner(): UserApi | |||
| { | |||
| return $this->owner; | |||
| } | |||
| } | |||
| @@ -0,0 +1,47 @@ | |||
| <?php | |||
| /** | |||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||
| * @date 12.12.23 | |||
| */ | |||
| namespace App\ApiResource; | |||
| use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; | |||
| use ApiPlatform\Doctrine\Orm\State\Options; | |||
| use ApiPlatform\Metadata\ApiFilter; | |||
| use ApiPlatform\Metadata\ApiProperty; | |||
| use ApiPlatform\Metadata\ApiResource; | |||
| use App\Entity\Comment; | |||
| use App\Interface\OwnerInterface; | |||
| use App\State\EntityClassDtoStateProcessor; | |||
| use App\State\EntityToDtoStateProvider; | |||
| use ApiPlatform\Metadata\Delete; | |||
| use ApiPlatform\Metadata\Get; | |||
| use ApiPlatform\Metadata\GetCollection; | |||
| use ApiPlatform\Metadata\Patch; | |||
| use ApiPlatform\Metadata\Post; | |||
| use App\State\SaleSummaryStateProvider; | |||
| use App\Validator\IsValidOwner; | |||
| use Symfony\Component\PropertyInfo\Type; | |||
| use Symfony\Component\Serializer\Attribute\Groups; | |||
| use Symfony\Component\Validator\Constraints\NotBlank; | |||
| #[ApiResource( | |||
| shortName: 'SaleSummary', | |||
| operations: [ | |||
| new GetCollection(), | |||
| ], | |||
| security: 'is_granted("ROLE_USER")', | |||
| provider: SaleSummaryStateProvider::class, | |||
| )] | |||
| class SaleSummary | |||
| { | |||
| public ?UserApi $owner = null; | |||
| public ?string $ownerName = null; | |||
| public ?int $turnover = null; | |||
| public ?int $profit = null; | |||
| } | |||
| @@ -21,6 +21,7 @@ use App\Entity\Task; | |||
| use App\Enum\PrioType; | |||
| use App\State\EntityClassDtoStateProcessor; | |||
| use App\State\EntityToDtoStateProvider; | |||
| use Symfony\Component\PropertyInfo\Type; | |||
| use Symfony\Component\Validator\Constraints as Assert; | |||
| #[ApiResource( | |||
| @@ -49,7 +50,11 @@ use Symfony\Component\Validator\Constraints as Assert; | |||
| processor: EntityClassDtoStateProcessor::class, | |||
| stateOptions: new Options(entityClass: Task::class), | |||
| )] | |||
| #[ApiFilter(SearchFilter::class, properties: ['partner' => 'exact', 'contact' => 'exact'])] | |||
| #[ApiFilter(SearchFilter::class, properties: [ | |||
| 'owner' => 'exact', | |||
| 'partner' => 'exact', | |||
| 'contact' => 'exact' | |||
| ])] | |||
| class TaskApi | |||
| { | |||
| #[ApiProperty(readable: false, writable: false, identifier: true)] | |||
| @@ -89,8 +94,26 @@ class TaskApi | |||
| #[Assert\NotBlank] | |||
| public PrioType $prio; | |||
| #[Assert\NotNull] | |||
| public ?bool $completed = null; | |||
| /** | |||
| * @var $taskNotes array<int, TaskNoteApi> | |||
| */ | |||
| #[ApiProperty( | |||
| readableLink: true, | |||
| writableLink: true, | |||
| builtinTypes: [ | |||
| new Type( | |||
| 'object', | |||
| collection: true, | |||
| collectionKeyType: [new Type('int')], | |||
| collectionValueType: new Type('object', class: TaskNoteApi::class) | |||
| ) | |||
| ] | |||
| )] | |||
| public array $taskNotes = []; | |||
| #[ApiProperty(writable: false)] | |||
| public ?\DateTimeImmutable $createdAt = null; | |||
| @@ -0,0 +1,72 @@ | |||
| <?php | |||
| /** | |||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||
| * @date 12.12.23 | |||
| */ | |||
| namespace App\ApiResource; | |||
| use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; | |||
| use ApiPlatform\Doctrine\Orm\State\Options; | |||
| use ApiPlatform\Metadata\ApiFilter; | |||
| use ApiPlatform\Metadata\ApiProperty; | |||
| use ApiPlatform\Metadata\ApiResource; | |||
| use App\Entity\TaskNote; | |||
| use App\Interface\OwnerInterface; | |||
| use App\State\EntityClassDtoStateProcessor; | |||
| use App\State\EntityToDtoStateProvider; | |||
| use ApiPlatform\Metadata\Delete; | |||
| use ApiPlatform\Metadata\Get; | |||
| use ApiPlatform\Metadata\GetCollection; | |||
| use ApiPlatform\Metadata\Patch; | |||
| use ApiPlatform\Metadata\Post; | |||
| use Symfony\Component\Validator\Constraints\NotBlank; | |||
| #[ApiResource( | |||
| shortName: 'TaskNote', | |||
| operations: [ | |||
| new Get( | |||
| security: 'is_granted("ROLE_USER")' | |||
| ), | |||
| new GetCollection(), | |||
| new Post( | |||
| security: 'is_granted("ROLE_USER")', | |||
| ), | |||
| new Patch( | |||
| security: 'is_granted("EDIT", object)', | |||
| ), | |||
| new Delete( | |||
| security: 'is_granted("ROLE_ADMIN")', | |||
| ) | |||
| ], | |||
| security: 'is_granted("ROLE_USER")', | |||
| provider: EntityToDtoStateProvider::class, | |||
| processor: EntityClassDtoStateProcessor::class, | |||
| stateOptions: new Options(entityClass: TaskNote::class), | |||
| )] | |||
| #[ApiFilter(SearchFilter::class, properties: ['task' => 'exact'])] | |||
| class TaskNoteApi implements OwnerInterface | |||
| { | |||
| #[ApiProperty(readable: false, writable: false, identifier: true)] | |||
| public ?int $id = null; | |||
| #[NotBlank] | |||
| public ?string $message = null; | |||
| #[ApiProperty(writable: false)] | |||
| public ?UserApi $owner = null; | |||
| #[ApiProperty(writable: false)] | |||
| public ?string $ownerName = null; | |||
| public ?TaskApi $task = null; | |||
| #[ApiProperty(writable: false)] | |||
| public ?\DateTimeImmutable $createdAt = null; | |||
| public function getOwner(): UserApi | |||
| { | |||
| return $this->owner; | |||
| } | |||
| } | |||
| @@ -16,6 +16,7 @@ use ApiPlatform\Metadata\Patch; | |||
| use ApiPlatform\Metadata\Post; | |||
| use App\Entity\MediaObject; | |||
| use App\Entity\User; | |||
| use App\Interface\AdminOrOwnerInterface; | |||
| use App\State\EntityClassDtoStateProcessor; | |||
| use App\State\EntityToDtoStateProvider; | |||
| use Symfony\Component\Validator\Constraints as Assert; | |||
| @@ -76,4 +77,5 @@ class UserApi | |||
| #[ApiProperty(writable: false)] | |||
| public ?\DateTimeImmutable $createdAt = null; | |||
| } | |||
| @@ -11,7 +11,9 @@ use App\Factory\MediaObjectUserFactory; | |||
| use App\Factory\PartnerFactory; | |||
| use App\Factory\PostingFactory; | |||
| use App\Factory\ProductFactory; | |||
| use App\Factory\SaleFactory; | |||
| use App\Factory\TaskFactory; | |||
| use App\Factory\TaskNoteFactory; | |||
| use App\Factory\UserFactory; | |||
| use Doctrine\Bundle\FixturesBundle\Fixture; | |||
| use Doctrine\Persistence\ObjectManager; | |||
| @@ -64,10 +66,12 @@ class AppFixtures extends Fixture | |||
| PartnerFactory::createMany(100); | |||
| MediaObjectContactFactory::createMany(50); | |||
| ContactFactory::createMany(200); | |||
| ProductFactory::createMany(100); | |||
| SaleFactory::createMany(50); | |||
| PostingFactory::createMany(200); | |||
| CommentFactory::createMany(300); | |||
| MediaObjectProductFactory::createMany(50); | |||
| ProductFactory::createMany(100); | |||
| TaskFactory::createMany(50); | |||
| TaskNoteFactory::createMany(100); | |||
| } | |||
| } | |||
| @@ -54,11 +54,15 @@ class Partner | |||
| #[ORM\OneToMany(mappedBy: 'partner', targetEntity: Posting::class)] | |||
| private Collection $postings; | |||
| #[ORM\OneToMany(mappedBy: 'partner', targetEntity: Sale::class)] | |||
| private Collection $sales; | |||
| public function __construct() | |||
| { | |||
| $this->createdAt = new \DateTimeImmutable(); | |||
| $this->contacts = new ArrayCollection(); | |||
| $this->postings = new ArrayCollection(); | |||
| $this->sales = new ArrayCollection(); | |||
| } | |||
| public function getId(): ?int | |||
| @@ -206,4 +210,34 @@ class Partner | |||
| return $this; | |||
| } | |||
| /** | |||
| * @return Collection<int, Sale> | |||
| */ | |||
| public function getSales(): Collection | |||
| { | |||
| return $this->sales; | |||
| } | |||
| public function addSale(Sale $sale): static | |||
| { | |||
| if (!$this->sales->contains($sale)) { | |||
| $this->sales->add($sale); | |||
| $sale->setPartner($this); | |||
| } | |||
| return $this; | |||
| } | |||
| public function removeSale(Sale $sale): static | |||
| { | |||
| if ($this->sales->removeElement($sale)) { | |||
| // set the owning side to null (unless already changed) | |||
| if ($sale->getPartner() === $this) { | |||
| $sale->setPartner(null); | |||
| } | |||
| } | |||
| return $this; | |||
| } | |||
| } | |||
| @@ -34,17 +34,26 @@ class Posting | |||
| #[ORM\JoinColumn(nullable: true, onDelete: "CASCADE")] | |||
| private ?Contact $contact = null; | |||
| #[ORM\ManyToOne(inversedBy: 'postings')] | |||
| #[ORM\JoinColumn(nullable: true, onDelete: "CASCADE")] | |||
| private ?Sale $sale = null; | |||
| #[ORM\Column] | |||
| private ?\DateTimeImmutable $createdAt = null; | |||
| #[ORM\OneToMany(mappedBy: 'posting', targetEntity: Comment::class)] | |||
| private Collection $comments; | |||
| public function __construct(User $owner, Partner $partner, Contact $contact = null) | |||
| { | |||
| public function __construct( | |||
| User $owner, | |||
| Partner $partner, | |||
| Contact $contact = null, | |||
| Sale $sale = null | |||
| ) { | |||
| $this->owner = $owner; | |||
| $this->partner = $partner; | |||
| $this->contact = $contact; | |||
| $this->sale = $sale; | |||
| $this->createdAt = new \DateTimeImmutable(); | |||
| $this->comments = new ArrayCollection(); | |||
| } | |||
| @@ -146,4 +155,16 @@ class Posting | |||
| return $this; | |||
| } | |||
| public function getSale(): ?Sale | |||
| { | |||
| return $this->sale; | |||
| } | |||
| public function setSale(?Sale $sale): static | |||
| { | |||
| $this->sale = $sale; | |||
| return $this; | |||
| } | |||
| } | |||
| @@ -3,6 +3,8 @@ | |||
| namespace App\Entity; | |||
| use App\Repository\ProductRepository; | |||
| use Doctrine\Common\Collections\ArrayCollection; | |||
| use Doctrine\Common\Collections\Collection; | |||
| use Doctrine\DBAL\Types\Types; | |||
| use Doctrine\ORM\Mapping as ORM; | |||
| @@ -27,9 +29,13 @@ class Product | |||
| #[ORM\Column] | |||
| private ?\DateTimeImmutable $createdAt = null; | |||
| #[ORM\OneToMany(mappedBy: 'product', targetEntity: Sale::class)] | |||
| private Collection $sales; | |||
| public function __construct() | |||
| { | |||
| $this->createdAt = new \DateTimeImmutable(); | |||
| $this->sales = new ArrayCollection(); | |||
| } | |||
| public function getId(): ?int | |||
| @@ -77,4 +83,34 @@ class Product | |||
| { | |||
| return $this->createdAt; | |||
| } | |||
| /** | |||
| * @return Collection<int, Sale> | |||
| */ | |||
| public function getSales(): Collection | |||
| { | |||
| return $this->sales; | |||
| } | |||
| public function addSale(Sale $sale): static | |||
| { | |||
| if (!$this->sales->contains($sale)) { | |||
| $this->sales->add($sale); | |||
| $sale->setProduct($this); | |||
| } | |||
| return $this; | |||
| } | |||
| public function removeSale(Sale $sale): static | |||
| { | |||
| if ($this->sales->removeElement($sale)) { | |||
| // set the owning side to null (unless already changed) | |||
| if ($sale->getProduct() === $this) { | |||
| $sale->setProduct(null); | |||
| } | |||
| } | |||
| return $this; | |||
| } | |||
| } | |||
| @@ -0,0 +1,173 @@ | |||
| <?php | |||
| namespace App\Entity; | |||
| use App\Repository\SaleRepository; | |||
| use Doctrine\Common\Collections\ArrayCollection; | |||
| use Doctrine\Common\Collections\Collection; | |||
| use Doctrine\DBAL\Types\Types; | |||
| use Doctrine\ORM\Mapping as ORM; | |||
| #[ORM\Entity(repositoryClass: SaleRepository::class)] | |||
| class Sale | |||
| { | |||
| #[ORM\Id] | |||
| #[ORM\GeneratedValue] | |||
| #[ORM\Column] | |||
| private ?int $id = null; | |||
| #[ORM\ManyToOne(inversedBy: 'sales')] | |||
| #[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")] | |||
| private ?User $owner = null; | |||
| #[ORM\ManyToOne(inversedBy: 'sales')] | |||
| #[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")] | |||
| private ?Partner $partner = null; | |||
| #[ORM\ManyToOne(inversedBy: 'sales')] | |||
| #[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")] | |||
| private ?Product $product = null; | |||
| #[ORM\Column] | |||
| private ?int $turnover = null; | |||
| #[ORM\Column(nullable: true)] | |||
| private ?int $profit = null; | |||
| #[ORM\Column(type: Types::TEXT, nullable: true)] | |||
| private ?string $comment = null; | |||
| #[ORM\Column] | |||
| private ?\DateTimeImmutable $createdAt = null; | |||
| #[ORM\OneToMany(mappedBy: 'sale', targetEntity: Posting::class)] | |||
| private Collection $postings; | |||
| public function __construct(User $owner, Partner $partner, Product $product) | |||
| { | |||
| $this->owner = $owner; | |||
| $this->partner = $partner; | |||
| $this->product = $product; | |||
| $this->createdAt = new \DateTimeImmutable(); | |||
| $this->postings = new ArrayCollection(); | |||
| } | |||
| public function getId(): ?int | |||
| { | |||
| return $this->id; | |||
| } | |||
| public function getOwner(): ?User | |||
| { | |||
| return $this->owner; | |||
| } | |||
| public function setOwner(?User $owner): static | |||
| { | |||
| $this->owner = $owner; | |||
| return $this; | |||
| } | |||
| public function getPartner(): ?Partner | |||
| { | |||
| return $this->partner; | |||
| } | |||
| public function setPartner(?Partner $partner): static | |||
| { | |||
| $this->partner = $partner; | |||
| return $this; | |||
| } | |||
| public function getProduct(): ?Product | |||
| { | |||
| return $this->product; | |||
| } | |||
| public function setProduct(?Product $product): static | |||
| { | |||
| $this->product = $product; | |||
| return $this; | |||
| } | |||
| public function getTurnover(): ?int | |||
| { | |||
| return $this->turnover; | |||
| } | |||
| public function setTurnover(int $turnover): static | |||
| { | |||
| $this->turnover = $turnover; | |||
| return $this; | |||
| } | |||
| public function getProfit(): ?int | |||
| { | |||
| return $this->profit; | |||
| } | |||
| public function setProfit(?int $profit): static | |||
| { | |||
| $this->profit = $profit; | |||
| return $this; | |||
| } | |||
| public function getComment(): ?string | |||
| { | |||
| return $this->comment; | |||
| } | |||
| public function setComment(?string $comment): static | |||
| { | |||
| $this->comment = $comment; | |||
| return $this; | |||
| } | |||
| public function getCreatedAt(): ?\DateTimeImmutable | |||
| { | |||
| return $this->createdAt; | |||
| } | |||
| public function setCreatedAt(\DateTimeImmutable $createdAt): static | |||
| { | |||
| $this->createdAt = $createdAt; | |||
| return $this; | |||
| } | |||
| /** | |||
| * @return Collection<int, Posting> | |||
| */ | |||
| public function getPostings(): Collection | |||
| { | |||
| return $this->postings; | |||
| } | |||
| public function addPosting(Posting $posting): static | |||
| { | |||
| if (!$this->postings->contains($posting)) { | |||
| $this->postings->add($posting); | |||
| $posting->setSale($this); | |||
| } | |||
| return $this; | |||
| } | |||
| public function removePosting(Posting $posting): static | |||
| { | |||
| if ($this->postings->removeElement($posting)) { | |||
| // set the owning side to null (unless already changed) | |||
| if ($posting->getSale() === $this) { | |||
| $posting->setSale(null); | |||
| } | |||
| } | |||
| return $this; | |||
| } | |||
| } | |||
| @@ -4,6 +4,7 @@ namespace App\Entity; | |||
| use App\Enum\PrioType; | |||
| use App\Repository\TaskRepository; | |||
| use Doctrine\Common\Collections\Collection; | |||
| use Doctrine\DBAL\Types\Types; | |||
| use Doctrine\ORM\Mapping as ORM; | |||
| @@ -43,12 +44,15 @@ class Task | |||
| #[ORM\Column(type: 'string', enumType: PrioType::class)] | |||
| private PrioType $prio; | |||
| #[ORM\Column] | |||
| #[ORM\Column(nullable: false)] | |||
| private ?bool $completed = null; | |||
| #[ORM\Column] | |||
| private ?\DateTimeImmutable $createdAt = null; | |||
| #[ORM\OneToMany(mappedBy: 'task', targetEntity: TaskNote::class)] | |||
| private Collection $taskNotes; | |||
| public function __construct(User $createdBy, User $assignedTo) | |||
| { | |||
| $this->createdBy = $createdBy; | |||
| @@ -156,4 +160,9 @@ class Task | |||
| return $this->createdAt; | |||
| } | |||
| public function getTaskNotes(): Collection | |||
| { | |||
| return $this->taskNotes; | |||
| } | |||
| } | |||
| @@ -0,0 +1,90 @@ | |||
| <?php | |||
| namespace App\Entity; | |||
| use App\Repository\TaskNoteRepository; | |||
| use Doctrine\DBAL\Types\Types; | |||
| use Doctrine\ORM\Mapping as ORM; | |||
| #[ORM\Entity(repositoryClass: TaskNoteRepository::class)] | |||
| class TaskNote | |||
| { | |||
| #[ORM\Id] | |||
| #[ORM\GeneratedValue] | |||
| #[ORM\Column] | |||
| private ?int $id = null; | |||
| #[ORM\Column(type: Types::TEXT)] | |||
| private ?string $message = null; | |||
| #[ORM\ManyToOne] | |||
| #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] | |||
| private ?User $owner = null; | |||
| #[ORM\ManyToOne] | |||
| #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] | |||
| private ?Task $task = null; | |||
| #[ORM\Column] | |||
| private ?\DateTimeImmutable $createdAt = null; | |||
| public function __construct(User $owner, Task $task) | |||
| { | |||
| $this->owner = $owner; | |||
| $this->task = $task; | |||
| $this->createdAt = new \DateTimeImmutable(); | |||
| } | |||
| public function getId(): ?int | |||
| { | |||
| return $this->id; | |||
| } | |||
| public function getMessage(): ?string | |||
| { | |||
| return $this->message; | |||
| } | |||
| public function setMessage(string $message): static | |||
| { | |||
| $this->message = $message; | |||
| return $this; | |||
| } | |||
| public function getOwner(): ?User | |||
| { | |||
| return $this->owner; | |||
| } | |||
| public function setOwner(?User $owner): static | |||
| { | |||
| $this->owner = $owner; | |||
| return $this; | |||
| } | |||
| public function getTask(): ?Task | |||
| { | |||
| return $this->task; | |||
| } | |||
| public function setTask(?Task $task): static | |||
| { | |||
| $this->task = $task; | |||
| return $this; | |||
| } | |||
| public function getCreatedAt(): ?\DateTimeImmutable | |||
| { | |||
| return $this->createdAt; | |||
| } | |||
| public function setCreatedAt(\DateTimeImmutable $createdAt): static | |||
| { | |||
| $this->createdAt = $createdAt; | |||
| return $this; | |||
| } | |||
| } | |||
| @@ -52,6 +52,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface | |||
| #[ORM\OneToMany(mappedBy: 'owner', targetEntity: Comment::class)] | |||
| private Collection $comments; | |||
| #[ORM\OneToMany(mappedBy: 'owner', targetEntity: Sale::class)] | |||
| private Collection $sales; | |||
| public function __construct() | |||
| { | |||
| @@ -59,6 +62,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface | |||
| $this->postings = new ArrayCollection(); | |||
| $this->active = true; | |||
| $this->comments = new ArrayCollection(); | |||
| $this->sales = new ArrayCollection(); | |||
| } | |||
| public function getId(): ?int | |||
| @@ -246,4 +250,34 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface | |||
| return $this; | |||
| } | |||
| /** | |||
| * @return Collection<int, Sale> | |||
| */ | |||
| public function getSales(): Collection | |||
| { | |||
| return $this->sales; | |||
| } | |||
| public function addSale(Sale $sale): static | |||
| { | |||
| if (!$this->sales->contains($sale)) { | |||
| $this->sales->add($sale); | |||
| $sale->setOwner($this); | |||
| } | |||
| return $this; | |||
| } | |||
| public function removeSale(Sale $sale): static | |||
| { | |||
| if ($this->sales->removeElement($sale)) { | |||
| // set the owning side to null (unless already changed) | |||
| if ($sale->getOwner() === $this) { | |||
| $sale->setOwner(null); | |||
| } | |||
| } | |||
| return $this; | |||
| } | |||
| } | |||
| @@ -47,12 +47,14 @@ final class PostingFactory extends ModelFactory | |||
| protected function getDefaults(): array | |||
| { | |||
| $randomBoolean = ContactFactory::count() > 0 && (bool)random_int(0, 1); | |||
| $random4 = SaleFactory::count() > 0 && !(bool)random_int(0, 3); | |||
| return [ | |||
| 'headline' => self::faker()->words(random_int(1, 5), true), | |||
| 'message' => $randomBoolean ? self::faker()->sentence() : self::faker()->sentences(random_int(1, 3), true), | |||
| 'owner' => UserFactory::random(), | |||
| 'partner' => PartnerFactory::random(), | |||
| 'contact' => $randomBoolean ? ContactFactory::random() : null, | |||
| 'sale' => !$random4 ? SaleFactory::random() : null, | |||
| ]; | |||
| } | |||
| @@ -0,0 +1,77 @@ | |||
| <?php | |||
| namespace App\Factory; | |||
| use App\Entity\Sale; | |||
| use App\Repository\SaleRepository; | |||
| use Zenstruck\Foundry\ModelFactory; | |||
| use Zenstruck\Foundry\Proxy; | |||
| use Zenstruck\Foundry\RepositoryProxy; | |||
| /** | |||
| * @extends ModelFactory<Sale> | |||
| * | |||
| * @method Sale|Proxy create(array|callable $attributes = []) | |||
| * @method static Sale|Proxy createOne(array $attributes = []) | |||
| * @method static Sale|Proxy find(object|array|mixed $criteria) | |||
| * @method static Sale|Proxy findOrCreate(array $attributes) | |||
| * @method static Sale|Proxy first(string $sortedField = 'id') | |||
| * @method static Sale|Proxy last(string $sortedField = 'id') | |||
| * @method static Sale|Proxy random(array $attributes = []) | |||
| * @method static Sale|Proxy randomOrCreate(array $attributes = []) | |||
| * @method static SaleRepository|RepositoryProxy repository() | |||
| * @method static Sale[]|Proxy[] all() | |||
| * @method static Sale[]|Proxy[] createMany(int $number, array|callable $attributes = []) | |||
| * @method static Sale[]|Proxy[] createSequence(iterable|callable $sequence) | |||
| * @method static Sale[]|Proxy[] findBy(array $attributes) | |||
| * @method static Sale[]|Proxy[] randomRange(int $min, int $max, array $attributes = []) | |||
| * @method static Sale[]|Proxy[] randomSet(int $number, array $attributes = []) | |||
| */ | |||
| final class SaleFactory extends ModelFactory | |||
| { | |||
| /** | |||
| * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services | |||
| * | |||
| * @todo inject services if required | |||
| */ | |||
| public function __construct() | |||
| { | |||
| parent::__construct(); | |||
| } | |||
| /** | |||
| * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories | |||
| * | |||
| * @todo add your default values here | |||
| */ | |||
| protected function getDefaults(): array | |||
| { | |||
| $turnover = self::faker()->randomNumber(5); | |||
| $randNumber = self::faker()->randomNumber(2); | |||
| $profit = (int)($turnover * ($randNumber / 100)); | |||
| return [ | |||
| 'createdAt' => \DateTimeImmutable::createFromMutable(self::faker()->dateTime()), | |||
| 'owner' => UserFactory::random(), | |||
| 'partner' => PartnerFactory::random(), | |||
| 'product' => ProductFactory::random(), | |||
| 'turnover' => $turnover, | |||
| 'profit' => $profit | |||
| ]; | |||
| } | |||
| /** | |||
| * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization | |||
| */ | |||
| protected function initialize(): self | |||
| { | |||
| return $this | |||
| // ->afterInstantiate(function(Sale $sale): void {}) | |||
| ; | |||
| } | |||
| protected static function getClass(): string | |||
| { | |||
| return Sale::class; | |||
| } | |||
| } | |||
| @@ -0,0 +1,71 @@ | |||
| <?php | |||
| namespace App\Factory; | |||
| use App\Entity\TaskNote; | |||
| use App\Repository\TaskNoteRepository; | |||
| use Zenstruck\Foundry\ModelFactory; | |||
| use Zenstruck\Foundry\Proxy; | |||
| use Zenstruck\Foundry\RepositoryProxy; | |||
| /** | |||
| * @extends ModelFactory<TaskNote> | |||
| * | |||
| * @method TaskNote|Proxy create(array|callable $attributes = []) | |||
| * @method static TaskNote|Proxy createOne(array $attributes = []) | |||
| * @method static TaskNote|Proxy find(object|array|mixed $criteria) | |||
| * @method static TaskNote|Proxy findOrCreate(array $attributes) | |||
| * @method static TaskNote|Proxy first(string $sortedField = 'id') | |||
| * @method static TaskNote|Proxy last(string $sortedField = 'id') | |||
| * @method static TaskNote|Proxy random(array $attributes = []) | |||
| * @method static TaskNote|Proxy randomOrCreate(array $attributes = []) | |||
| * @method static TaskNoteRepository|RepositoryProxy repository() | |||
| * @method static TaskNote[]|Proxy[] all() | |||
| * @method static TaskNote[]|Proxy[] createMany(int $number, array|callable $attributes = []) | |||
| * @method static TaskNote[]|Proxy[] createSequence(iterable|callable $sequence) | |||
| * @method static TaskNote[]|Proxy[] findBy(array $attributes) | |||
| * @method static TaskNote[]|Proxy[] randomRange(int $min, int $max, array $attributes = []) | |||
| * @method static TaskNote[]|Proxy[] randomSet(int $number, array $attributes = []) | |||
| */ | |||
| final class TaskNoteFactory extends ModelFactory | |||
| { | |||
| /** | |||
| * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services | |||
| * | |||
| * @todo inject services if required | |||
| */ | |||
| public function __construct() | |||
| { | |||
| parent::__construct(); | |||
| } | |||
| /** | |||
| * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories | |||
| * | |||
| * @todo add your default values here | |||
| */ | |||
| protected function getDefaults(): array | |||
| { | |||
| return [ | |||
| 'createdAt' => \DateTimeImmutable::createFromMutable(self::faker()->dateTime()), | |||
| 'message' => self::faker()->text(), | |||
| 'owner' => UserFactory::random(), | |||
| 'task' => TaskFactory::random(), | |||
| ]; | |||
| } | |||
| /** | |||
| * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization | |||
| */ | |||
| protected function initialize(): self | |||
| { | |||
| return $this | |||
| // ->afterInstantiate(function(TaskNote $taskNote): void {}) | |||
| ; | |||
| } | |||
| protected static function getClass(): string | |||
| { | |||
| return TaskNote::class; | |||
| } | |||
| } | |||
| @@ -0,0 +1,8 @@ | |||
| <?php | |||
| namespace App\Interface; | |||
| use App\ApiResource\UserApi; | |||
| interface OwnerInterface { | |||
| public function getOwner(): UserApi; | |||
| } | |||
| @@ -4,8 +4,10 @@ namespace App\Mapper; | |||
| use App\ApiResource\ContactApi; | |||
| use App\ApiResource\PostingApi; | |||
| use App\ApiResource\SaleApi; | |||
| use App\Entity\Contact; | |||
| use App\Entity\Partner; | |||
| use App\Entity\Sale; | |||
| use App\Entity\User; | |||
| use App\Entity\Posting; | |||
| use App\Repository\PostingRepository; | |||
| @@ -49,8 +51,16 @@ class PostingApiToEntityMapper implements MapperInterface | |||
| MicroMapperInterface::MAX_DEPTH => 1, | |||
| ]); | |||
| } | |||
| $sale = null; | |||
| if ($dto->sale) { | |||
| assert($dto->sale instanceof SaleApi); | |||
| $sale = $this->microMapper->map($dto->sale, Sale::class, [ | |||
| MicroMapperInterface::MAX_DEPTH => 1, | |||
| ]); | |||
| } | |||
| assert($partner instanceof Partner); | |||
| assert($contact === null || $contact instanceof Contact); | |||
| assert($sale === null || $sale instanceof Sale); | |||
| $entity = new Posting($user, $partner, $contact); | |||
| } | |||
| @@ -0,0 +1,76 @@ | |||
| <?php | |||
| namespace App\Mapper; | |||
| use App\ApiResource\CommentApi; | |||
| use App\ApiResource\SaleApi; | |||
| use App\Entity\Comment; | |||
| use App\Entity\Partner; | |||
| use App\Entity\Product; | |||
| use App\Entity\Sale; | |||
| use App\Entity\User; | |||
| use App\Repository\SaleRepository; | |||
| use Symfony\Bundle\SecurityBundle\Security; | |||
| use Symfonycasts\MicroMapper\AsMapper; | |||
| use Symfonycasts\MicroMapper\MapperInterface; | |||
| use Symfonycasts\MicroMapper\MicroMapperInterface; | |||
| #[AsMapper(from: CommentApi::class, to: Comment::class)] | |||
| class SaleApiToEntityMapper implements MapperInterface | |||
| { | |||
| public function __construct( | |||
| private SaleRepository $repository, | |||
| private Security $security, | |||
| private MicroMapperInterface $microMapper, | |||
| ) | |||
| { | |||
| } | |||
| public function load(object $from, string $toClass, array $context): object | |||
| { | |||
| $dto = $from; | |||
| assert($dto instanceof SaleApi); | |||
| if ($dto->id) { | |||
| $entity = $this->repository->find($dto->id); | |||
| } else { | |||
| $user = $this->security->getUser(); | |||
| assert($user instanceof User); | |||
| if ($dto->partner === null) { | |||
| throw new \Exception('Partner missing'); | |||
| } | |||
| $partner = $this->microMapper->map($dto->partner, Partner::class, [ | |||
| MicroMapperInterface::MAX_DEPTH => 1, | |||
| ]); | |||
| if ($dto->product === null) { | |||
| throw new \Exception('Product missing'); | |||
| } | |||
| $product = $this->microMapper->map($dto->product, Product::class, [ | |||
| MicroMapperInterface::MAX_DEPTH => 1, | |||
| ]); | |||
| assert($partner instanceof Partner); | |||
| assert($product instanceof Product); | |||
| $entity = new Sale($user, $partner, $product); | |||
| } | |||
| if (!$entity) { | |||
| throw new \Exception('Sale not found'); | |||
| } | |||
| return $entity; | |||
| } | |||
| public function populate(object $from, object $to, array $context): object | |||
| { | |||
| $dto = $from; | |||
| assert($dto instanceof SaleApi); | |||
| $entity = $to; | |||
| assert($entity instanceof Sale); | |||
| $entity->setTurnover($dto->turnover); | |||
| $entity->setProfit($dto->profit); | |||
| $entity->setComment($dto->comment); | |||
| return $entity; | |||
| } | |||
| } | |||
| @@ -0,0 +1,74 @@ | |||
| <?php | |||
| namespace App\Mapper; | |||
| use App\ApiResource\CommentApi; | |||
| use App\ApiResource\PartnerApi; | |||
| use App\ApiResource\ProductApi; | |||
| use App\ApiResource\SaleApi; | |||
| use App\ApiResource\UserApi; | |||
| use App\ApiResource\PostingApi; | |||
| use App\Entity\Comment; | |||
| use App\Entity\Posting; | |||
| use App\Entity\Sale; | |||
| use Symfony\Bundle\SecurityBundle\Security; | |||
| use Symfonycasts\MicroMapper\AsMapper; | |||
| use Symfonycasts\MicroMapper\MapperInterface; | |||
| use Symfonycasts\MicroMapper\MicroMapperInterface; | |||
| #[AsMapper(from: Sale::class, to: SaleApi::class)] | |||
| class SaleEntityToApiMapper implements MapperInterface | |||
| { | |||
| public function __construct( | |||
| private MicroMapperInterface $microMapper | |||
| ) | |||
| { | |||
| } | |||
| public function load(object $from, string $toClass, array $context): object | |||
| { | |||
| $entity = $from; | |||
| assert($entity instanceof Sale); | |||
| $dto = new SaleApi(); | |||
| $dto->id = $entity->getId(); | |||
| return $dto; | |||
| } | |||
| public function populate(object $from, object $to, array $context): object | |||
| { | |||
| $entity = $from; | |||
| $dto = $to; | |||
| assert($entity instanceof Sale); | |||
| assert($dto instanceof SaleApi); | |||
| $dto->owner = $this->microMapper->map($entity->getOwner(), UserApi::class, [ | |||
| MicroMapperInterface::MAX_DEPTH => 1, | |||
| ]); | |||
| $dto->ownerName = $entity->getOwner()?->getFirstName()." ".$entity->getOwner()?->getLastName(); | |||
| $dto->partner = $this->microMapper->map($entity->getPartner(), PartnerApi::class, [ | |||
| MicroMapperInterface::MAX_DEPTH => 1, | |||
| ]); | |||
| $dto->partnerName = $entity->getPartner()?->getName(); | |||
| $dto->product = $this->microMapper->map($entity->getProduct(), ProductApi::class, [ | |||
| MicroMapperInterface::MAX_DEPTH => 1, | |||
| ]); | |||
| $dto->productName = $entity->getProduct()?->getName(); | |||
| $dto->turnover = $entity->getTurnover(); | |||
| $dto->profit = $entity->getProfit(); | |||
| $dto->comment = $entity->getComment(); | |||
| $dto->createdAt = $entity->getCreatedAt(); | |||
| $dto->posts = array_map(function(Posting $posting) { | |||
| return $this->microMapper->map($posting, PostingApi::class, [ | |||
| MicroMapperInterface::MAX_DEPTH => 1, | |||
| ]); | |||
| }, $entity->getPostings()->getValues()); | |||
| return $dto; | |||
| } | |||
| } | |||
| @@ -6,10 +6,12 @@ use App\ApiResource\CommentApi; | |||
| use App\ApiResource\ContactApi; | |||
| use App\ApiResource\PartnerApi; | |||
| use App\ApiResource\TaskApi; | |||
| use App\ApiResource\TaskNoteApi; | |||
| use App\ApiResource\UserApi; | |||
| use App\ApiResource\PostingApi; | |||
| use App\Entity\Comment; | |||
| use App\Entity\Task; | |||
| use App\Entity\TaskNote; | |||
| use Symfony\Bundle\SecurityBundle\Security; | |||
| use Symfonycasts\MicroMapper\AsMapper; | |||
| use Symfonycasts\MicroMapper\MapperInterface; | |||
| @@ -68,6 +70,12 @@ class TaskEntityToApiMapper implements MapperInterface | |||
| $dto->contactName = $entity->getContact()?->getFirstName()." ".$entity->getContact()?->getLastName(); | |||
| } | |||
| $dto->taskNotes = array_map(function(TaskNote $taskNote) { | |||
| return $this->microMapper->map($taskNote, TaskNoteApi::class, [ | |||
| MicroMapperInterface::MAX_DEPTH => 1, | |||
| ]); | |||
| }, $entity->getTaskNotes()->getValues()); | |||
| $dto->prio = $entity->getPrio(); | |||
| $dto->completed = $entity->getCompleted(); | |||
| $dto->createdAt = $entity->getCreatedAt(); | |||
| @@ -0,0 +1,64 @@ | |||
| <?php | |||
| namespace App\Mapper; | |||
| use App\ApiResource\TaskNoteApi; | |||
| use App\Entity\Task; | |||
| use App\Entity\TaskNote; | |||
| use App\Entity\User; | |||
| use App\Repository\TaskNoteRepository; | |||
| use Symfony\Bundle\SecurityBundle\Security; | |||
| use Symfonycasts\MicroMapper\AsMapper; | |||
| use Symfonycasts\MicroMapper\MapperInterface; | |||
| use Symfonycasts\MicroMapper\MicroMapperInterface; | |||
| #[AsMapper(from: TaskNoteApi::class, to: TaskNote::class)] | |||
| class TaskNoteApiToEntityMapper implements MapperInterface | |||
| { | |||
| public function __construct( | |||
| private TaskNoteRepository $repository, | |||
| private Security $security, | |||
| private MicroMapperInterface $microMapper, | |||
| ) | |||
| { | |||
| } | |||
| public function load(object $from, string $toClass, array $context): object | |||
| { | |||
| $dto = $from; | |||
| assert($dto instanceof TaskNoteApi); | |||
| if ($dto->id) { | |||
| $entity = $this->repository->find($dto->id); | |||
| } else { | |||
| $user = $this->security->getUser(); | |||
| assert($user instanceof User); | |||
| if ($dto->task === null) { | |||
| throw new \Exception('Task missing'); | |||
| } | |||
| $task = $this->microMapper->map($dto->task, Task::class, [ | |||
| MicroMapperInterface::MAX_DEPTH => 1, | |||
| ]); | |||
| assert($task instanceof Task); | |||
| $entity = new TaskNote($user, $task); | |||
| } | |||
| if (!$entity) { | |||
| throw new \Exception('TaskNote not found'); | |||
| } | |||
| return $entity; | |||
| } | |||
| public function populate(object $from, object $to, array $context): object | |||
| { | |||
| $dto = $from; | |||
| assert($dto instanceof TaskNoteApi); | |||
| $entity = $to; | |||
| assert($entity instanceof TaskNote); | |||
| $entity->setMessage($dto->message); | |||
| return $entity; | |||
| } | |||
| } | |||
| @@ -0,0 +1,54 @@ | |||
| <?php | |||
| namespace App\Mapper; | |||
| use App\ApiResource\TaskApi; | |||
| use App\ApiResource\TaskNoteApi; | |||
| use App\ApiResource\UserApi; | |||
| use App\Entity\TaskNote; | |||
| use Symfonycasts\MicroMapper\AsMapper; | |||
| use Symfonycasts\MicroMapper\MapperInterface; | |||
| use Symfonycasts\MicroMapper\MicroMapperInterface; | |||
| #[AsMapper(from: TaskNote::class, to: TaskNoteApi::class)] | |||
| class TaskNoteEntityToApiMapper implements MapperInterface | |||
| { | |||
| public function __construct( | |||
| private MicroMapperInterface $microMapper | |||
| ) | |||
| { | |||
| } | |||
| public function load(object $from, string $toClass, array $context): object | |||
| { | |||
| $entity = $from; | |||
| assert($entity instanceof TaskNote); | |||
| $dto = new TaskNoteApi(); | |||
| $dto->id = $entity->getId(); | |||
| return $dto; | |||
| } | |||
| public function populate(object $from, object $to, array $context): object | |||
| { | |||
| $entity = $from; | |||
| $dto = $to; | |||
| assert($entity instanceof TaskNote); | |||
| assert($dto instanceof TaskNoteApi); | |||
| $dto->message = $entity->getMessage(); | |||
| $dto->owner = $this->microMapper->map($entity->getOwner(), UserApi::class, [ | |||
| MicroMapperInterface::MAX_DEPTH => 1, | |||
| ]); | |||
| $dto->ownerName = $entity->getOwner()?->getFirstName()." ".$entity->getOwner()?->getLastName(); | |||
| $dto->task = $this->microMapper->map($entity->getTask(), TaskApi::class, [ | |||
| MicroMapperInterface::MAX_DEPTH => 1, | |||
| ]); | |||
| $dto->createdAt = $entity->getCreatedAt(); | |||
| return $dto; | |||
| } | |||
| } | |||
| @@ -0,0 +1,48 @@ | |||
| <?php | |||
| namespace App\Repository; | |||
| use App\Entity\Sale; | |||
| use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; | |||
| use Doctrine\Persistence\ManagerRegistry; | |||
| /** | |||
| * @extends ServiceEntityRepository<Sale> | |||
| * | |||
| * @method Sale|null find($id, $lockMode = null, $lockVersion = null) | |||
| * @method Sale|null findOneBy(array $criteria, array $orderBy = null) | |||
| * @method Sale[] findAll() | |||
| * @method Sale[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) | |||
| */ | |||
| class SaleRepository extends ServiceEntityRepository | |||
| { | |||
| public function __construct(ManagerRegistry $registry) | |||
| { | |||
| parent::__construct($registry, Sale::class); | |||
| } | |||
| // /** | |||
| // * @return Sale[] Returns an array of Sale objects | |||
| // */ | |||
| // public function findByExampleField($value): array | |||
| // { | |||
| // return $this->createQueryBuilder('s') | |||
| // ->andWhere('s.exampleField = :val') | |||
| // ->setParameter('val', $value) | |||
| // ->orderBy('s.id', 'ASC') | |||
| // ->setMaxResults(10) | |||
| // ->getQuery() | |||
| // ->getResult() | |||
| // ; | |||
| // } | |||
| // public function findOneBySomeField($value): ?Sale | |||
| // { | |||
| // return $this->createQueryBuilder('s') | |||
| // ->andWhere('s.exampleField = :val') | |||
| // ->setParameter('val', $value) | |||
| // ->getQuery() | |||
| // ->getOneOrNullResult() | |||
| // ; | |||
| // } | |||
| } | |||
| @@ -0,0 +1,48 @@ | |||
| <?php | |||
| namespace App\Repository; | |||
| use App\Entity\TaskNote; | |||
| use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; | |||
| use Doctrine\Persistence\ManagerRegistry; | |||
| /** | |||
| * @extends ServiceEntityRepository<TaskNote> | |||
| * | |||
| * @method TaskNote|null find($id, $lockMode = null, $lockVersion = null) | |||
| * @method TaskNote|null findOneBy(array $criteria, array $orderBy = null) | |||
| * @method TaskNote[] findAll() | |||
| * @method TaskNote[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) | |||
| */ | |||
| class TaskNoteRepository extends ServiceEntityRepository | |||
| { | |||
| public function __construct(ManagerRegistry $registry) | |||
| { | |||
| parent::__construct($registry, TaskNote::class); | |||
| } | |||
| // /** | |||
| // * @return TaskNote[] Returns an array of TaskNote objects | |||
| // */ | |||
| // public function findByExampleField($value): array | |||
| // { | |||
| // return $this->createQueryBuilder('t') | |||
| // ->andWhere('t.exampleField = :val') | |||
| // ->setParameter('val', $value) | |||
| // ->orderBy('t.id', 'ASC') | |||
| // ->setMaxResults(10) | |||
| // ->getQuery() | |||
| // ->getResult() | |||
| // ; | |||
| // } | |||
| // public function findOneBySomeField($value): ?TaskNote | |||
| // { | |||
| // return $this->createQueryBuilder('t') | |||
| // ->andWhere('t.exampleField = :val') | |||
| // ->setParameter('val', $value) | |||
| // ->getQuery() | |||
| // ->getOneOrNullResult() | |||
| // ; | |||
| // } | |||
| } | |||
| @@ -0,0 +1,67 @@ | |||
| <?php | |||
| namespace App\State; | |||
| use ApiPlatform\Metadata\CollectionOperationInterface; | |||
| use ApiPlatform\Metadata\Operation; | |||
| use ApiPlatform\State\Pagination\Pagination; | |||
| use ApiPlatform\State\Pagination\TraversablePaginator; | |||
| use ApiPlatform\State\ProviderInterface; | |||
| use App\ApiResource\SaleSummary; | |||
| use App\ApiResource\UserApi; | |||
| use App\Entity\User; | |||
| use App\Repository\UserRepository; | |||
| use Symfonycasts\MicroMapper\MicroMapperInterface; | |||
| class SaleSummaryStateProvider implements ProviderInterface | |||
| { | |||
| public function __construct( | |||
| private UserRepository$userRepository, | |||
| private Pagination $pagination, | |||
| private MicroMapperInterface $microMapper, | |||
| ) | |||
| { | |||
| } | |||
| public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null | |||
| { | |||
| if ($operation instanceof CollectionOperationInterface) { | |||
| $currentPage = $this->pagination->getPage($context); | |||
| $itemsPerPage = $this->pagination->getLimit($operation, $context); | |||
| $offset = $this->pagination->getOffset($operation, $context); | |||
| $salesSummaries = $this->createSaleSummaries($offset, $itemsPerPage); | |||
| return new TraversablePaginator( | |||
| new \ArrayIterator($salesSummaries), | |||
| $currentPage, | |||
| $itemsPerPage, | |||
| count($salesSummaries), | |||
| ); | |||
| } | |||
| $salesSummaries = $this->createSaleSummaries(0); | |||
| return $this->createSaleSummaries(0, count($salesSummaries)); | |||
| } | |||
| private function createSaleSummaries(int $offset, int $limit = 50): array | |||
| { | |||
| $users = $this->userRepository->findBy([], [], $limit, $offset); | |||
| $salesSummaries = []; | |||
| /** @var User $user */ | |||
| foreach ($users as $user) { | |||
| $sales = $user->getSales(); | |||
| $salesSummary = new SaleSummary(); | |||
| $salesSummary->owner = $this->microMapper->map($user, UserApi::class); | |||
| $salesSummary->ownerName = $user->getFirstName() . ' ' . $user->getLastName(); | |||
| $salesSummary->turnover = 0; | |||
| $salesSummary->profit = 0; | |||
| foreach ($sales as $sale) { | |||
| $salesSummary->turnover += $sale->getTurnover(); | |||
| $salesSummary->profit += $sale->getProfit(); | |||
| } | |||
| $salesSummaries[] = $salesSummary; | |||
| } | |||
| return $salesSummaries; | |||
| } | |||
| } | |||
| @@ -1,46 +0,0 @@ | |||
| <?php | |||
| namespace App\Voter; | |||
| use App\ApiResource\CommentApi; | |||
| use App\ApiResource\PostingApi; | |||
| use App\Entity\User; | |||
| use Symfony\Bundle\SecurityBundle\Security; | |||
| use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | |||
| use Symfony\Component\Security\Core\Authorization\Voter\Voter; | |||
| class CommentApiVoter extends Voter | |||
| { | |||
| public const EDIT = 'EDIT'; | |||
| public function __construct() | |||
| { | |||
| } | |||
| protected function supports(string $attribute, mixed $subject): bool | |||
| { | |||
| return $attribute === self::EDIT && $subject instanceof CommentApi; | |||
| } | |||
| protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool | |||
| { | |||
| $user = $token->getUser(); | |||
| // if the user is anonymous, do not grant access | |||
| if (!$user instanceof User) { | |||
| return false; | |||
| } | |||
| assert($subject instanceof CommentApi); | |||
| // ... (check conditions and return true to grant permission) ... | |||
| switch ($attribute) { | |||
| case self::EDIT: | |||
| if ($subject->owner?->id === $user->getId()) { | |||
| return true; | |||
| } | |||
| break; | |||
| } | |||
| return false; | |||
| } | |||
| } | |||
| @@ -2,13 +2,12 @@ | |||
| namespace App\Voter; | |||
| use App\ApiResource\PostingApi; | |||
| use App\Entity\User; | |||
| use Symfony\Bundle\SecurityBundle\Security; | |||
| use App\Interface\OwnerInterface; | |||
| use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | |||
| use Symfony\Component\Security\Core\Authorization\Voter\Voter; | |||
| class PostingApiVoter extends Voter | |||
| class OwnerApiEditVoter extends Voter | |||
| { | |||
| public const EDIT = 'EDIT'; | |||
| @@ -18,7 +17,7 @@ class PostingApiVoter extends Voter | |||
| protected function supports(string $attribute, mixed $subject): bool | |||
| { | |||
| return $attribute === self::EDIT && $subject instanceof PostingApi; | |||
| return $attribute === self::EDIT && ($subject instanceof OwnerInterface); | |||
| } | |||
| protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool | |||
| @@ -29,12 +28,12 @@ class PostingApiVoter extends Voter | |||
| return false; | |||
| } | |||
| assert($subject instanceof PostingApi); | |||
| assert($subject instanceof OwnerInterface); | |||
| // ... (check conditions and return true to grant permission) ... | |||
| switch ($attribute) { | |||
| case self::EDIT: | |||
| if ($subject->owner?->id === $user->getId()) { | |||
| if ($subject->getOwner()?->id === $user->getId()) { | |||
| return true; | |||
| } | |||
| break; | |||
| @@ -0,0 +1,92 @@ | |||
| <?php | |||
| /** | |||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||
| * @date 12.12.23 | |||
| */ | |||
| namespace App\Tests\Functional; | |||
| use App\Enum\PartnerType; | |||
| use App\Factory\CommentFactory; | |||
| use App\Factory\MediaObjectLogoFactory; | |||
| use App\Factory\MediaObjectContactFactory; | |||
| use App\Factory\PartnerFactory; | |||
| use App\Factory\PostingFactory; | |||
| use App\Factory\TaskFactory; | |||
| use App\Factory\TaskNoteFactory; | |||
| use App\Factory\UserFactory; | |||
| use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface; | |||
| use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | |||
| use Zenstruck\Browser\Test\HasBrowser; | |||
| use Zenstruck\Foundry\Test\Factories; | |||
| use Zenstruck\Foundry\Test\ResetDatabase; | |||
| class TaskNoteResourceTest extends KernelTestCase | |||
| { | |||
| use HasBrowser; | |||
| use ResetDatabase; | |||
| use Factories; | |||
| private JWTTokenManagerInterface $JWTManager; | |||
| protected function setUp(): void | |||
| { | |||
| parent::setUp(); | |||
| $this->JWTManager = self::getContainer()->get('lexik_jwt_authentication.jwt_manager'); | |||
| } | |||
| public function testPostTaskNote(): void | |||
| { | |||
| $user = UserFactory::createOne( | |||
| [ | |||
| 'firstName' => 'Peter', | |||
| 'lastName' => 'Test', | |||
| 'password' => 'test', | |||
| 'email' => 'peter@test.de', | |||
| ] | |||
| ); | |||
| PartnerFactory::createOne(); | |||
| $task = TaskFactory::createOne(); | |||
| TaskNoteFactory::createOne(); | |||
| $token = $this->JWTManager->create($user->object()); | |||
| $this->browser() | |||
| ->get('/api/task_notes', [ | |||
| 'headers' => [ | |||
| 'Authorization' => 'Bearer ' . $token, | |||
| ], | |||
| ]) | |||
| ->assertSuccessful() | |||
| ->assertJsonMatches('"hydra:totalItems"', 1) | |||
| ; | |||
| $this->browser() | |||
| ->post('/api/task_notes' , [ | |||
| 'json' => [ | |||
| 'message' => 'my comment', | |||
| 'task' => '/api/tasks/' . $task->getId(), | |||
| ], | |||
| 'headers' => [ | |||
| 'Authorization' => 'Bearer ' . $token, | |||
| ] | |||
| ]) | |||
| ->assertSuccessful() | |||
| ; | |||
| $this->browser() | |||
| ->get('/api/task_notes', [ | |||
| 'headers' => [ | |||
| 'Authorization' => 'Bearer ' . $token, | |||
| ], | |||
| ]) | |||
| ->assertSuccessful() | |||
| ->assertJsonMatches('"hydra:totalItems"', 2) | |||
| ->assertJsonMatches('"hydra:member"[1].message', 'my comment') | |||
| ; | |||
| } | |||
| } | |||