Skip to content

Commit

Permalink
new: Allow to delete the organizations
Browse files Browse the repository at this point in the history
  • Loading branch information
marien-probesys committed May 11, 2023
1 parent da08569 commit 0d85a97
Show file tree
Hide file tree
Showing 13 changed files with 399 additions and 7 deletions.
137 changes: 137 additions & 0 deletions migrations/Version20230511091245SetDeleteCascadeOnOrgaConstraints.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<?php

// This file is part of Bileto.
// Copyright 2022-2023 Probesys
// SPDX-License-Identifier: AGPL-3.0-or-later

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version20230511091245SetDeleteCascadeOnOrgaConstraints extends AbstractMigration
{
public function getDescription(): string
{
return 'Set DELETE CASCADE on organization foreign keys in authorizations and ticket tables.';
}

public function up(Schema $schema): void
{
$dbPlatform = $this->connection->getDatabasePlatform()->getName();
if ($dbPlatform === 'postgresql') {
$this->addSql('ALTER TABLE authorizations DROP CONSTRAINT FK_2BC15D6932C8A3DE');
$this->addSql(<<<SQL
ALTER TABLE authorizations
ADD CONSTRAINT FK_2BC15D6932C8A3DE
FOREIGN KEY (organization_id)
REFERENCES organization (id)
ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);

$this->addSql('ALTER TABLE ticket DROP CONSTRAINT FK_97A0ADA332C8A3DE');
$this->addSql(<<<SQL
ALTER TABLE ticket
ADD CONSTRAINT FK_97A0ADA332C8A3DE
FOREIGN KEY (organization_id)
REFERENCES organization (id)
ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);

$this->addSql('ALTER TABLE message DROP CONSTRAINT FK_B6BD307F700047D2');
$this->addSql(<<<SQL
ALTER TABLE message
ADD CONSTRAINT FK_B6BD307F700047D2
FOREIGN KEY (ticket_id)
REFERENCES ticket (id)
ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
} elseif ($dbPlatform === 'mysql') {
$this->addSql('ALTER TABLE authorizations DROP FOREIGN KEY FK_2BC15D6932C8A3DE');
$this->addSql(<<<SQL
ALTER TABLE authorizations
ADD CONSTRAINT FK_2BC15D6932C8A3DE
FOREIGN KEY (organization_id)
REFERENCES organization (id)
ON DELETE CASCADE
SQL);

$this->addSql('ALTER TABLE message DROP FOREIGN KEY FK_B6BD307F700047D2');
$this->addSql(<<<SQL
ALTER TABLE message
ADD CONSTRAINT FK_B6BD307F700047D2
FOREIGN KEY (ticket_id)
REFERENCES ticket (id)
ON DELETE CASCADE
SQL);

$this->addSql('ALTER TABLE ticket DROP FOREIGN KEY FK_97A0ADA332C8A3DE');
$this->addSql(<<<SQL
ALTER TABLE ticket
ADD CONSTRAINT FK_97A0ADA332C8A3DE
FOREIGN KEY (organization_id)
REFERENCES organization (id)
ON DELETE CASCADE
SQL);
}
}

public function down(Schema $schema): void
{
$dbPlatform = $this->connection->getDatabasePlatform()->getName();
if ($dbPlatform === 'postgresql') {
$this->addSql('ALTER TABLE ticket DROP CONSTRAINT fk_97a0ada332c8a3de');
$this->addSql(<<<SQL
ALTER TABLE ticket
ADD CONSTRAINT fk_97a0ada332c8a3de
FOREIGN KEY (organization_id)
REFERENCES organization (id)
NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);

$this->addSql('ALTER TABLE "authorizations" DROP CONSTRAINT fk_2bc15d6932c8a3de');
$this->addSql(<<<SQL
ALTER TABLE "authorizations"
ADD CONSTRAINT fk_2bc15d6932c8a3de
FOREIGN KEY (organization_id)
REFERENCES organization (id)
NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);

$this->addSql('ALTER TABLE message DROP CONSTRAINT fk_b6bd307f700047d2');
$this->addSql(<<<SQL
ALTER TABLE message
ADD CONSTRAINT fk_b6bd307f700047d2
FOREIGN KEY (ticket_id)
REFERENCES ticket (id)
NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
} elseif ($dbPlatform === 'mysql') {
$this->addSql('ALTER TABLE `authorizations` DROP FOREIGN KEY FK_2BC15D6932C8A3DE');
$this->addSql(<<<SQL
ALTER TABLE `authorizations`
ADD CONSTRAINT FK_2BC15D6932C8A3DE
FOREIGN KEY (organization_id)
REFERENCES organization (id)
SQL);

$this->addSql('ALTER TABLE message DROP FOREIGN KEY FK_B6BD307F700047D2');
$this->addSql(<<<SQL
ALTER TABLE message
ADD CONSTRAINT FK_B6BD307F700047D2
FOREIGN KEY (ticket_id)
REFERENCES ticket (id)
SQL);

$this->addSql('ALTER TABLE ticket DROP FOREIGN KEY FK_97A0ADA332C8A3DE');
$this->addSql(<<<SQL
ALTER TABLE ticket
ADD CONSTRAINT FK_97A0ADA332C8A3DE
FOREIGN KEY (organization_id)
REFERENCES organization (id)
SQL);
}
}
}
38 changes: 38 additions & 0 deletions src/Controller/OrganizationsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use App\Repository\OrganizationRepository;
use App\Service\OrganizationSorter;
use App\Utils\Time;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
Expand Down Expand Up @@ -206,4 +207,41 @@ public function update(

return $this->redirectToRoute('organizations');
}

#[Route('/organizations/{uid}/deletion', name: 'deletion organization', methods: ['GET', 'HEAD'])]
public function deletion(Organization $organization): Response
{
$this->denyAccessUnlessGranted('admin:manage:organizations');

return $this->render('organizations/deletion.html.twig', [
'organization' => $organization,
]);
}

#[Route('/organizations/{uid}/deletion', name: 'delete organization', methods: ['POST'])]
public function delete(
Organization $organization,
Request $request,
OrganizationRepository $organizationRepository,
TranslatorInterface $translator,
): Response {
$this->denyAccessUnlessGranted('admin:manage:organizations');

/** @var \App\Entity\User $user */
$user = $this->getUser();

/** @var string $csrfToken */
$csrfToken = $request->request->get('_csrf_token', '');

if (!$this->isCsrfTokenValid('delete organization', $csrfToken)) {
$this->addFlash('error', $translator->trans('csrf.invalid', [], 'errors'));
return $this->redirectToRoute('organizations');
}

$organizations = $organizationRepository->findSubOrganizations($organization);
$organizations[] = $organization;
$organizationRepository->removeInBatch($organizations);

return $this->redirectToRoute('organizations');
}
}
1 change: 1 addition & 0 deletions src/Entity/Authorization.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class Authorization implements MetaEntityInterface, ActivityRecordableInterface
private ?User $holder = null;

#[ORM\ManyToOne(inversedBy: 'authorizations')]
#[ORM\JoinColumn(onDelete: 'CASCADE')]
private ?Organization $organization = null;

public function getId(): ?int
Expand Down
2 changes: 1 addition & 1 deletion src/Entity/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class Message implements MetaEntityInterface, ActivityRecordableInterface
private string $content = '';

#[ORM\ManyToOne(inversedBy: 'messages')]
#[ORM\JoinColumn(nullable: false)]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private ?Ticket $ticket = null;

public function getId(): ?int
Expand Down
2 changes: 1 addition & 1 deletion src/Entity/Ticket.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class Ticket implements MetaEntityInterface, ActivityRecordableInterface
private ?User $assignee = null;

#[ORM\ManyToOne(inversedBy: 'tickets')]
#[ORM\JoinColumn(nullable: false)]
#[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")]
private ?Organization $organization = null;

/** @var Collection<int, Message> $messages */
Expand Down
43 changes: 43 additions & 0 deletions src/Repository/OrganizationRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,27 @@ public function remove(Organization $entity, bool $flush = false): void
}
}

/**
* @param Organization[] $organizations
*/
public function removeInBatch(array $organizations): void
{
$entityManager = $this->getEntityManager();

$organizationIds = array_map(function (Organization $organization) {
return $organization->getId();
}, $organizations);

$query = $entityManager->createQuery(<<<SQL
DELETE FROM App\Entity\Organization o
WHERE o.id IN (:ids)
SQL);

$query->setParameter('ids', $organizationIds);

$query->execute();
}

/**
* @return Organization[]
*/
Expand Down Expand Up @@ -94,4 +115,26 @@ public function findWithSubOrganizations(array $organizationIds): array
$query = $queryBuilder->getQuery();
return $query->getResult();
}

/**
* @return Organization[]
*/
public function findSubOrganizations(Organization $organization): array
{
$entityManager = $this->getEntityManager();
$queryBuilder = $entityManager->createQueryBuilder();

$queryBuilder->select('o');
$queryBuilder->from('\App\Entity\Organization', 'o');

$expr = $queryBuilder->expr()->like(
'o.parentsPath',
"CONCAT('%/', :id, '/%')"
);
$queryBuilder->where($expr);
$queryBuilder->setParameter('id', $organization->getId());

$query = $queryBuilder->getQuery();
return $query->getResult();
}
}
10 changes: 9 additions & 1 deletion templates/alerts/_alert.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
#}

{% if raw is not defined %}
{% set raw = false %}
{% endif %}

<div
class="alert alert--{{ type }}"
role="alert"
Expand All @@ -13,6 +17,10 @@
<div class="alert__title">{{ title }}</div>

<p class="alert__message">
{{ message }}
{% if raw %}
{{ message | raw }}
{% else %}
{{ message }}
{% endif %}
</p>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@
{{ organization.name }}
</span>

<span class="row__item--noshrink row flow-small">
<div class="row__item--noshrink row row--always row--center flow-small">
<a href="{{ path('edit organization', { uid: organization.uid }) }}">
{{ 'organizations.index.edit' | trans }}
</a>
</span>

<a href="{{ path('deletion organization', { uid: organization.uid }) }}">
{{ 'organizations.index.delete' | trans }}
</a>
</div>
</li>

{{ include(
Expand Down
50 changes: 50 additions & 0 deletions templates/organizations/deletion.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{#
# This file is part of Bileto.
# Copyright 2022-2023 Probesys
# SPDX-License-Identifier: AGPL-3.0-or-later
#}

{% extends 'base.html.twig' %}

{% set currentPage = 'settings' %}

{% block title %}{{ 'organizations.deletion.title' | trans }}{% endblock %}

{% block sidebar %}
{{ include('settings/_sidebar.html.twig', { current: 'organizations' }, with_context = false) }}
{% endblock %}

{% block body %}
<main class="layout__body flow">
<div class="layout__breadcrumb">
<a href="{{ path('organizations') }}">{{ 'organizations.index.title' | trans }}</a>
<h1>{{ 'organizations.deletion.title' | trans }}</h1>
</div>

<form action="{{ path('delete organization', { uid: organization.uid }) }}" method="post" class="wrapper-small flow">
<input type="hidden" name="_csrf_token" value="{{ csrf_token('delete organization') }}">

{{ include('alerts/_alert.html.twig', {
type: 'warning',
title: 'organizations.deletion.caution' | trans,
message: 'organizations.deletion.going_delete' | trans({ organization: organization.name }),
raw: true,
}, with_context = false) }}

{% if error %}
{{ include('alerts/_error.html.twig', { message: error }, with_context = false) }}
{% endif %}

<div class="form__actions">
<button
id="form-delete-organization-submit"
class="button--primary"
type="submit"
data-turbo-confirm="{{ 'organizations.deletion.confirm' | trans }}"
>
{{ 'organizations.deletion.submit' | trans }}
</button>
</div>
</form>
</main>
{% endblock %}
8 changes: 6 additions & 2 deletions templates/organizations/index.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,15 @@
{{ organization.name }}
</span>

<span class="row__item--noshrink row flow-small">
<div class="row__item--noshrink row row--always row--center flow-small">
<a href="{{ path('edit organization', { uid: organization.uid }) }}">
{{ 'organizations.index.edit' | trans }}
</a>
</span>

<a href="{{ path('deletion organization', { uid: organization.uid }) }}">
{{ 'organizations.index.delete' | trans }}
</a>
</div>
</div>

<ul class="list--padded list--nostyle list--strip text--small">
Expand Down
Loading

0 comments on commit 0d85a97

Please sign in to comment.