Skip to content

Commit

Permalink
[Api] Create endpoint to manage deputies (#11175)
Browse files Browse the repository at this point in the history
* [Api] Create endpoint to manage deputies

* Add tests

* Add missing serialization group

* Fix zone denormalization

* Update security check

* Fix tests

* Validate zones in scope

* Update zone serialization

* Adjust tests

* Add Default validation group

* Update behat scenario description
  • Loading branch information
Remg authored Jan 29, 2025
1 parent f8b8d03 commit c255895
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 12 deletions.
49 changes: 49 additions & 0 deletions features/api/adherent_zone_based_roles.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
@api
Feature:
In order to manage adherent zone based roles
As a logged-in user granted with a specific feature
I should be able to create, read and update adherent zone based roles

Scenario Outline: As a user granted with local scope, I can create an adherent zone based role
Given I am logged with "<user>" via OAuth client "JeMengage Web" with scope "jemengage_admin"
And I send a "POST" request to "/api/v3/zone_based_role?scope=<scope>" with body:
"""
{
"adherent": "d0a0935f-da7c-4caa-b582-a8c2376e5158",
"type": "deputy",
"zones": ["e3efe6fd-906e-11eb-a875-0242ac150002"]
}
"""
Then the response status code should be 201
And the response should be in JSON
And the JSON should be equal to:
"""
{
"type": "deputy",
"adherent": {
"uuid": "d0a0935f-da7c-4caa-b582-a8c2376e5158",
"zones": [
{
"uuid": "@uuid@",
"type": "department",
"code": "77",
"name": "Seine-et-Marne"
}
]
},
"uuid": "@uuid@",
"zones": [
{
"uuid": "e3efe6fd-906e-11eb-a875-0242ac150002",
"code": "92",
"name": "Hauts-de-Seine",
"type": "department"
}
]
}
"""

Examples:
| user | scope |
| referent@en-marche-dev.fr | president_departmental_assembly |
| senateur@en-marche-dev.fr | delegated_08f40730-d807-4975-8773-69d8fae1da74 |
6 changes: 4 additions & 2 deletions features/api/my_teams.feature
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,8 @@ Feature:
"statutory_message",
"procurations",
"actions",
"featurebase"
"featurebase",
"circonscriptions"
],
"uuid": "e0da56db-c4c6-4aa4-ad8d-7e9505dfdd93"
}
Expand Down Expand Up @@ -425,7 +426,8 @@ Feature:
"statutory_message",
"procurations",
"actions",
"featurebase"
"featurebase",
"circonscriptions"
],
"uuid": "@uuid@"
}
Expand Down
12 changes: 8 additions & 4 deletions features/api/scopes.feature
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ Feature:
"statutory_message",
"procurations",
"actions",
"featurebase"
"featurebase",
"circonscriptions"
]
},
{
Expand Down Expand Up @@ -222,7 +223,8 @@ Feature:
"statutory_message",
"procurations",
"actions",
"featurebase"
"featurebase",
"circonscriptions"
],
"attributes": null,
"delegated_access": null
Expand Down Expand Up @@ -471,7 +473,8 @@ Feature:
"statutory_message",
"procurations",
"actions",
"featurebase"
"featurebase",
"circonscriptions"
],
"delegated_access": {
"delegator": {
Expand Down Expand Up @@ -524,7 +527,8 @@ Feature:
"statutory_message",
"procurations",
"actions",
"featurebase"
"featurebase",
"circonscriptions"
],
"attributes": {
"committees": [{ "name": "Comité des 3 communes", "uuid": "@uuid@" }],
Expand Down
35 changes: 35 additions & 0 deletions src/Entity/AdherentZoneBasedRole.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,42 @@

namespace App\Entity;

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use App\Adherent\Authorization\ZoneBasedRoleTypeEnum;
use App\Collection\ZoneCollection;
use App\Entity\Geo\Zone;
use App\Scope\ScopeEnum;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Uuid;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;

#[ApiResource(
operations: [
new GetCollection(
uriTemplate: '/v3/zone_based_role',
),
new Post(
uriTemplate: '/v3/zone_based_role',
),
new Put(
uriTemplate: '/v3/zone_based_role/{uuid}',
requirements: ['uuid' => '%pattern_uuid%']
),
new Delete(
uriTemplate: '/v3/zone_based_role/{uuid}',
requirements: ['uuid' => '%pattern_uuid%']
),
],
normalizationContext: ['groups' => ['zone_based_role_read']],
denormalizationContext: ['groups' => ['zone_based_role_write']],
validationContext: ['groups' => ['Default', 'zone_based_role_write']],
security: 'is_granted(\'REQUEST_SCOPE_GRANTED\', \'circonscriptions\')'
)]
#[ORM\Entity]
class AdherentZoneBasedRole
{
Expand All @@ -19,10 +47,12 @@ class AdherentZoneBasedRole

#[Assert\Choice(choices: ZoneBasedRoleTypeEnum::ALL)]
#[Assert\NotBlank]
#[Groups(['zone_based_role_read', 'zone_based_role_write'])]
#[ORM\Column]
private ?string $type;

#[Assert\NotBlank]
#[Groups(['zone_based_role_read', 'zone_based_role_write'])]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
#[ORM\ManyToOne(targetEntity: Adherent::class, inversedBy: 'zoneBasedRoles')]
private ?Adherent $adherent = null;
Expand Down Expand Up @@ -90,6 +120,11 @@ public function setType(?string $type): void
$this->type = $type;
}

public function getAdherent(): ?Adherent
{
return $this->adherent;
}

public function setAdherent(Adherent $adherent): void
{
$this->adherent = $adherent;
Expand Down
2 changes: 2 additions & 0 deletions src/Entity/EntityIdentityTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ trait EntityIdentityTrait
'zone_read',
'tax_receipt:list',
'event_registration_list',
'zone_based_role_read',
'zone_based_role_write',
])]
#[ORM\Column(type: 'uuid', unique: true)]
protected $uuid;
Expand Down
16 changes: 15 additions & 1 deletion src/Entity/EntityZoneTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,30 @@

use App\Collection\ZoneCollection;
use App\Entity\Geo\Zone;
use App\Validator\ZoneInScopeZones as AssertZoneInScopeZones;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Validator\Constraints as Assert;

trait EntityZoneTrait
{
/**
* @var ZoneCollection|Zone[]
*/
#[Groups(['phoning_campaign_read', 'phoning_campaign_write', 'read_api', 'managed_users_list', 'managed_user_read'])]
#[Assert\All(
constraints: [new AssertZoneInScopeZones()],
groups: ['zone_based_role_write'],
)]
#[Groups([
'phoning_campaign_read',
'phoning_campaign_write',
'read_api',
'managed_users_list',
'managed_user_read',
'zone_based_role_read',
'zone_based_role_write',
])]
#[ORM\ManyToMany(targetEntity: Zone::class, cascade: ['persist'])]
protected Collection $zones;

Expand Down
4 changes: 2 additions & 2 deletions src/Entity/Geo/GeoTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ trait GeoTrait
/**
* @var string
*/
#[Groups(['zone_read', 'department_read', 'region_read', 'survey_list', 'survey_list_dc', 'survey_read_dc', 'scopes', 'scope', 'jecoute_news_read_dc', 'audience_read', 'audience_segment_read', 'phoning_campaign_read', 'team_read', 'team_list_read', 'pap_campaign_read', 'pap_campaign_read_after_write', 'phoning_campaign_read', 'phoning_campaign_list', 'read_api', 'department_site_read', 'department_site_read_list', 'elected_representative_read', 'elected_representative_list', 'formation_list_read', 'formation_read', 'elected_mandate_read', 'adherent_elect_read', 'general_meeting_report_list_read', 'general_meeting_report_read', 'committee:read', 'zone:code,type', 'managed_users_list', 'managed_user_read', 'procuration_request_read', 'procuration_request_list', 'procuration_proxy_list', 'procuration_matched_proxy', 'action_read', 'profile_read'])]
#[Groups(['zone_read', 'department_read', 'region_read', 'survey_list', 'survey_list_dc', 'survey_read_dc', 'scopes', 'scope', 'jecoute_news_read_dc', 'audience_read', 'audience_segment_read', 'phoning_campaign_read', 'team_read', 'team_list_read', 'pap_campaign_read', 'pap_campaign_read_after_write', 'phoning_campaign_read', 'phoning_campaign_list', 'read_api', 'department_site_read', 'department_site_read_list', 'elected_representative_read', 'elected_representative_list', 'formation_list_read', 'formation_read', 'elected_mandate_read', 'adherent_elect_read', 'general_meeting_report_list_read', 'general_meeting_report_read', 'committee:read', 'zone:code,type', 'managed_users_list', 'managed_user_read', 'procuration_request_read', 'procuration_request_list', 'procuration_proxy_list', 'procuration_matched_proxy', 'action_read', 'profile_read', 'zone_based_role_read'])]
#[ORM\Column(unique: true)]
private $code;

/**
* @var string
*/
#[Groups(['zone_read', 'department_read', 'region_read', 'survey_list', 'survey_list_dc', 'survey_read_dc', 'scopes', 'scope', 'jecoute_news_read_dc', 'audience_read', 'audience_segment_read', 'phoning_campaign_read', 'team_read', 'team_list_read', 'pap_campaign_read', 'pap_campaign_read_after_write', 'phoning_campaign_read', 'phoning_campaign_list', 'department_site_read', 'department_site_read_list', 'elected_representative_read', 'elected_representative_list', 'formation_list_read', 'formation_read', 'elected_mandate_read', 'adherent_elect_read', 'general_meeting_report_list_read', 'general_meeting_report_read', 'committee:read', 'managed_users_list', 'managed_user_read', 'procuration_request_read', 'procuration_request_list', 'procuration_proxy_list', 'procuration_matched_proxy', 'action_read', 'profile_read'])]
#[Groups(['zone_read', 'department_read', 'region_read', 'survey_list', 'survey_list_dc', 'survey_read_dc', 'scopes', 'scope', 'jecoute_news_read_dc', 'audience_read', 'audience_segment_read', 'phoning_campaign_read', 'team_read', 'team_list_read', 'pap_campaign_read', 'pap_campaign_read_after_write', 'phoning_campaign_read', 'phoning_campaign_list', 'department_site_read', 'department_site_read_list', 'elected_representative_read', 'elected_representative_list', 'formation_list_read', 'formation_read', 'elected_mandate_read', 'adherent_elect_read', 'general_meeting_report_list_read', 'general_meeting_report_read', 'committee:read', 'managed_users_list', 'managed_user_read', 'procuration_request_read', 'procuration_request_list', 'procuration_proxy_list', 'procuration_matched_proxy', 'action_read', 'profile_read', 'zone_based_role_read'])]
#[ORM\Column]
private $name;

Expand Down
4 changes: 2 additions & 2 deletions src/Entity/Geo/Zone.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,14 @@ class Zone implements GeoInterface, UuidEntityInterface
* @var UuidInterface
*/
#[ApiProperty(identifier: true, openapiContext: ['type' => 'string', 'format' => 'uuid', 'example' => 'b4219d47-3138-5efd-9762-2ef9f9495084'])]
#[Groups(['zone_read', 'survey_write_dc', 'scopes', 'scope', 'jecoute_news_read_dc', 'audience_read', 'audience_segment_read', 'phoning_campaign_read', 'survey_list_dc', 'survey_read_dc', 'team_read', 'team_list_read', 'pap_campaign_read', 'pap_campaign_read_after_write', 'phoning_campaign_read', 'phoning_campaign_list', 'department_site_read', 'department_site_read_list', 'elected_representative_read', 'elected_representative_list', 'formation_list_read', 'formation_read', 'formation_write', 'elected_mandate_read', 'adherent_elect_read', 'general_meeting_report_list_read', 'general_meeting_report_read', 'committee:read', 'managed_users_list', 'managed_user_read', 'procuration_request_read', 'procuration_request_list', 'procuration_proxy_list', 'procuration_matched_proxy', 'general_meeting_report_write', 'elected_mandate_write', 'action_read', 'department_site_write', 'committee:write', 'audience_write', 'profile_read'])]
#[Groups(['zone_read', 'survey_write_dc', 'scopes', 'scope', 'jecoute_news_read_dc', 'audience_read', 'audience_segment_read', 'survey_list_dc', 'survey_read_dc', 'team_read', 'team_list_read', 'pap_campaign_read', 'pap_campaign_read_after_write', 'phoning_campaign_read', 'phoning_campaign_list', 'department_site_read', 'department_site_read_list', 'elected_representative_read', 'elected_representative_list', 'formation_list_read', 'formation_read', 'formation_write', 'elected_mandate_read', 'adherent_elect_read', 'general_meeting_report_list_read', 'general_meeting_report_read', 'committee:read', 'managed_users_list', 'managed_user_read', 'procuration_request_read', 'procuration_request_list', 'procuration_proxy_list', 'procuration_matched_proxy', 'general_meeting_report_write', 'elected_mandate_write', 'action_read', 'department_site_write', 'committee:write', 'audience_write', 'profile_read', 'zone_based_role_write', 'zone_based_role_read'])]
#[ORM\Column(type: 'uuid', unique: true)]
protected $uuid;

/**
* @var string
*/
#[Groups(['zone_read', 'scope', 'read_api', 'committee:read', 'zone:code,type', 'managed_users_list', 'managed_user_read', 'procuration_request_read', 'procuration_request_list', 'procuration_proxy_list', 'procuration_matched_proxy', 'action_read', 'profile_read'])]
#[Groups(['zone_read', 'scope', 'read_api', 'committee:read', 'zone:code,type', 'managed_users_list', 'managed_user_read', 'procuration_request_read', 'procuration_request_list', 'procuration_proxy_list', 'procuration_matched_proxy', 'action_read', 'profile_read', 'zone_based_role_read'])]
#[ORM\Column]
private $type;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public function onKernelResponse(ResponseEvent $event): void
$request = $event->getRequest();
$routeName = $request->get('_route');

if (!preg_match('/^admin_(.)+_export$/', $routeName)) {
if (!$routeName || !preg_match('/^admin_(.)+_export$/', $routeName)) {
return;
}

Expand Down
2 changes: 2 additions & 0 deletions src/Scope/FeatureEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class FeatureEnum extends Enum
public const PROCURATIONS = 'procurations';
public const ACTIONS = 'actions';
public const FEATUREBASE = 'featurebase';
public const CIRCONSCRIPTIONS = 'circonscriptions';

public const ALL = [
self::DASHBOARD,
Expand Down Expand Up @@ -60,6 +61,7 @@ class FeatureEnum extends Enum
self::PROCURATIONS,
self::ACTIONS,
self::FEATUREBASE,
self::CIRCONSCRIPTIONS,
];

public const DELEGATED_ACCESSES_BY_DEFAULT = [
Expand Down
1 change: 1 addition & 0 deletions translations/messages+intl-icu.fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,7 @@ scope.feature.committee: Comités locaux
scope.feature.designation: Désignation
scope.feature.actions: Actions
scope.feature.featurebase: 🗣 Featurebase ️
scope.feature.circonscriptions: Gestion des circonscriptions

scope.app.data_corner: DataCorner
scope.app.jemarche: Je m'engage
Expand Down

0 comments on commit c255895

Please sign in to comment.