Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions api/migrations/schema/Version20240928121040.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?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 Version20240928121040 extends AbstractMigration {
public function getDescription(): string {
return 'restricts deletion of checklists to ensure no checklist nodes references a checklist when deleting';
}

public function up(Schema $schema): void {
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE checklistnode_checklistitem DROP CONSTRAINT FK_5A2B5B318A09A289');
$this->addSql('ALTER TABLE checklistnode_checklistitem ADD CONSTRAINT FK_5A2B5B318A09A289 FOREIGN KEY (checklistitem_id) REFERENCES checklist_item (id) ON DELETE RESTRICT NOT DEFERRABLE INITIALLY IMMEDIATE');
}

public function down(Schema $schema): void {
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE checklistnode_checklistitem DROP CONSTRAINT fk_5a2b5b318a09a289');
$this->addSql('ALTER TABLE checklistnode_checklistitem ADD CONSTRAINT fk_5a2b5b318a09a289 FOREIGN KEY (checklistitem_id) REFERENCES checklist_item (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
}
}
9 changes: 6 additions & 3 deletions api/src/Entity/Checklist.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
#[ApiResource(
operations: [
new Get(
security: 'is_granted("CHECKLIST_IS_PROTOTYPE", object) or
is_granted("CAMP_IS_PROTOTYPE", object) or
security: 'is_granted("CHECKLIST_IS_PROTOTYPE", object) or
is_granted("CAMP_IS_PROTOTYPE", object) or
is_granted("CAMP_COLLABORATOR", object)
'
),
Expand All @@ -42,7 +42,9 @@
new Delete(
security: '(is_granted("CHECKLIST_IS_PROTOTYPE", object) and is_granted("ROLE_ADMIN")) or
(is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object))
'
',
validate: true,
validationContext: ['groups' => ['delete']],
),
new GetCollection(
security: 'is_authenticated()'
Expand Down Expand Up @@ -95,6 +97,7 @@ class Checklist extends BaseEntity implements BelongsToCampInterface, CopyFromPr
/**
* All ChecklistItems that belong to this Checklist.
*/
#[Assert\Valid(groups: ['delete'])]
#[ApiProperty(writable: false, uriTemplate: ChecklistItem::CHECKLIST_SUBRESOURCE_URI_TEMPLATE)]
#[Groups(['read'])]
#[ORM\OneToMany(targetEntity: ChecklistItem::class, mappedBy: 'checklist', cascade: ['persist'])]
Expand Down
9 changes: 8 additions & 1 deletion api/src/Entity/ChecklistItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
security: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)'
),
new Delete(
security: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)'
security: 'is_granted("CAMP_MEMBER", object) or is_granted("CAMP_MANAGER", object)',
validate: true,
validationContext: ['groups' => ['delete']],
),
new GetCollection(
security: 'is_authenticated()'
Expand Down Expand Up @@ -103,6 +105,11 @@ class ChecklistItem extends BaseEntity implements BelongsToCampInterface, CopyFr
/**
* All ChecklistNodes that have selected this ChecklistItem.
*/
#[Assert\Count(
exactly: 0,
exactMessage: 'It\'s not possible to delete a checklist item as long as checklist nodes are referencing it.',
groups: ['delete']
)]
#[ORM\ManyToMany(targetEntity: ChecklistNode::class, mappedBy: 'checklistItems')]
public Collection $checklistNodes;

Expand Down
2 changes: 1 addition & 1 deletion api/src/Entity/ContentNode/ChecklistNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class ChecklistNode extends ContentNode {
#[ORM\ManyToMany(targetEntity: ChecklistItem::class, inversedBy: 'checklistNodes')]
#[ORM\JoinTable(name: 'checklistnode_checklistitem')]
#[ORM\JoinColumn(name: 'checklistnode_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
#[ORM\InverseJoinColumn(name: 'checklistitem_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
#[ORM\InverseJoinColumn(name: 'checklistitem_id', referencedColumnName: 'id', onDelete: 'RESTRICT')]
#[ORM\OrderBy(['position' => 'ASC'])]
public Collection $checklistItems;

Expand Down
10 changes: 10 additions & 0 deletions api/tests/Api/ChecklistItems/DeleteChecklistItemTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,14 @@ public function testDeleteChecklistItemFromCampPrototypeIsDeniedForUnrelatedUser
'detail' => 'Access Denied.',
]);
}

public function testDeleteChecklistItemIsDeniedWhenUsedInChecklistNode() {
$checklistItem = static::getFixture('checklistItem1_1_1');
static::createClientWithCredentials()->request('DELETE', '/checklist_items/'.$checklistItem->getId());
$this->assertResponseStatusCodeSame(422);
$this->assertJsonContains([
'title' => 'An error occurred',
'detail' => 'checklistNodes: It\'s not possible to delete a checklist item as long as checklist nodes are referencing it.',
]);
}
}
10 changes: 10 additions & 0 deletions api/tests/Api/Checklists/DeleteChecklistTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,14 @@ public function testDeleteChecklistFromCampPrototypeIsDeniedForUnrelatedUser() {
'detail' => 'Access Denied.',
]);
}

public function testDeleteChecklisIsDeniedWhenUsedInChecklistNode() {
$Checklist = static::getFixture('checklist1');
static::createClientWithCredentials()->request('DELETE', '/checklists/'.$Checklist->getId());
$this->assertResponseStatusCodeSame(422);
$this->assertJsonContains([
'title' => 'An error occurred',
'detail' => 'checklistItems[0].checklistNodes: It\'s not possible to delete a checklist item as long as checklist nodes are referencing it.',
]);
}
}
Loading