diff --git a/api/src/Entity/Camp.php b/api/src/Entity/Camp.php index e9e82f69dc..d183eee36a 100644 --- a/api/src/Entity/Camp.php +++ b/api/src/Entity/Camp.php @@ -420,9 +420,13 @@ public function getCamp(): ?Camp { * * @return CampCollaboration[] */ - #[ApiProperty(writable: false, example: '["/camp_collaborations/1a2b3c4d"]')] + #[ApiProperty( + writable: false, + uriTemplate: CampCollaboration::CAMP_SUBRESOURCE_URI_TEMPLATE, + example: '["/camps/1a2b3c4d/camp_collaborations"]' + )] public function getCampCollaborations(): array { - return $this->collaborations->getValues(); + return []; } /** diff --git a/api/src/Entity/CampCollaboration.php b/api/src/Entity/CampCollaboration.php index aeeb4304c7..a13659fc4a 100644 --- a/api/src/Entity/CampCollaboration.php +++ b/api/src/Entity/CampCollaboration.php @@ -9,6 +9,7 @@ use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Get; use ApiPlatform\Metadata\GetCollection; +use ApiPlatform\Metadata\Link; use ApiPlatform\Metadata\Patch; use ApiPlatform\Metadata\Post; use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation; @@ -60,6 +61,18 @@ security: 'is_fully_authenticated()', normalizationContext: self::COLLECTION_NORMALIZATION_CONTEXT ), + new GetCollection( + uriTemplate: self::CAMP_SUBRESOURCE_URI_TEMPLATE, + uriVariables: [ + 'campId' => new Link( + toProperty: 'camp', + fromClass: Camp::class, + security: 'is_granted("CAMP_COLLABORATOR", camp) or is_granted("CAMP_IS_PROTOTYPE", camp)' + ), + ], + security: 'is_fully_authenticated()', + normalizationContext: self::COLLECTION_NORMALIZATION_CONTEXT + ), new Post( processor: CampCollaborationCreateProcessor::class, denormalizationContext: ['groups' => ['write', 'create']], @@ -88,6 +101,8 @@ #[ORM\UniqueConstraint(name: 'inviteEmail_camp_unique', fields: ['inviteEmail', 'camp'])] #[ORM\Index(columns: ['status'])] class CampCollaboration extends BaseEntity implements BelongsToCampInterface { + public const CAMP_SUBRESOURCE_URI_TEMPLATE = '/camps/{campId}/camp_collaborations{._format}'; + public const ITEM_NORMALIZATION_CONTEXT = [ 'groups' => ['read', 'CampCollaboration:User'], 'swagger_definition_name' => 'read', diff --git a/api/tests/Api/CampCollaborations/ListCampCollaborationsTest.php b/api/tests/Api/CampCollaborations/ListCampCollaborationsTest.php index d01c9f023e..17a2d064b1 100644 --- a/api/tests/Api/CampCollaborations/ListCampCollaborationsTest.php +++ b/api/tests/Api/CampCollaborations/ListCampCollaborationsTest.php @@ -71,6 +71,29 @@ public function testListCampCollaborationsFilteredByCampIsAllowedForCollaborator ], $response->toArray()['_links']['items']); } + public function testListCampCollaborationsAsCampSubResourceIsAllowedForCollaborator() { + $camp = static::getFixture('camp1'); + $response = static::createClientWithCredentials()->request('GET', "/camps/{$camp->getId()}/camp_collaborations"); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 6, + '_links' => [ + 'items' => [], + ], + '_embedded' => [ + 'items' => [], + ], + ]); + $this->assertEqualsCanonicalizing([ + ['href' => $this->getIriFor('campCollaboration1manager')], + ['href' => $this->getIriFor('campCollaboration2member')], + ['href' => $this->getIriFor('campCollaboration3guest')], + ['href' => $this->getIriFor('campCollaboration4invited')], + ['href' => $this->getIriFor('campCollaboration5inactive')], + ['href' => $this->getIriFor('campCollaboration6manager')], + ], $response->toArray()['_links']['items']); + } + public function testListCampCollaborationsFilteredByCampIsDeniedForUnrelatedUser() { $camp = static::getFixture('camp1'); $response = static::createClientWithCredentials(['email' => static::$fixtures['user4unrelated']->getEmail()]) @@ -81,6 +104,15 @@ public function testListCampCollaborationsFilteredByCampIsDeniedForUnrelatedUser $this->assertArrayNotHasKey('items', $response->toArray()['_links']); } + public function testListCampCollaborationsAsCampSubResourceIsDeniedForUnrelatedUser() { + $camp = static::getFixture('camp1'); + static::createClientWithCredentials(['email' => static::$fixtures['user4unrelated']->getEmail()]) + ->request('GET', "/camps/{$camp->getId()}/camp_collaborations") + ; + + $this->assertResponseStatusCodeSame(404); + } + public function testListCampCollaborationsFilteredByCampIsDeniedForInactiveCollaborator() { $camp = static::getFixture('camp1'); $response = static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) @@ -91,6 +123,15 @@ public function testListCampCollaborationsFilteredByCampIsDeniedForInactiveColla $this->assertArrayNotHasKey('items', $response->toArray()['_links']); } + public function testListCampCollaborationsAsCampSubResourceIsDeniedForInactiveCollaborator() { + $camp = static::getFixture('camp1'); + static::createClientWithCredentials(['email' => static::$fixtures['user5inactive']->getEmail()]) + ->request('GET', "/camps/{$camp->getId()}/camp_collaborations") + ; + + $this->assertResponseStatusCodeSame(404); + } + public function testListCampCollaborationsFilteredByCampPrototypeIsAllowedForUnrelatedUser() { $camp = static::getFixture('campPrototype'); $response = static::createClientWithCredentials()->request('GET', '/camp_collaborations?camp=%2Fcamps%2F'.$camp->getId()); diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml index c8f8d87b09..eeec30957b 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml @@ -24484,6 +24484,132 @@ paths: summary: 'Retrieves the collection of ActivityProgressLabel resources.' tags: - ActivityProgressLabel + '/camps/{campId}/camp_collaborations': + get: + deprecated: false + description: 'Retrieves the collection of CampCollaboration resources.' + operationId: api_camps_campIdcamp_collaborations_get_collection + parameters: + - + allowEmptyValue: false + allowReserved: false + deprecated: false + description: '' + explode: false + in: query + name: activityResponsibles.activity + required: false + schema: + type: string + style: form + - + allowEmptyValue: false + allowReserved: false + deprecated: false + description: '' + explode: false + in: query + name: camp + required: false + schema: + type: string + style: form + - + allowEmptyValue: false + allowReserved: false + deprecated: false + description: '' + explode: true + in: query + name: 'activityResponsibles.activity[]' + required: false + schema: + items: + type: string + type: array + style: form + - + allowEmptyValue: false + allowReserved: false + deprecated: false + description: '' + explode: true + in: query + name: 'camp[]' + required: false + schema: + items: + type: string + type: array + style: form + - + allowEmptyValue: false + allowReserved: false + deprecated: false + description: 'CampCollaboration identifier' + explode: false + in: path + name: campId + required: true + schema: + type: string + style: simple + responses: + 200: + content: + application/hal+json: + schema: + properties: + _embedded: { anyOf: [{ properties: { item: { items: { $ref: '#/components/schemas/CampCollaboration.jsonhal-read_CampCollaboration.Camp_CampCollaboration.User' }, type: array } }, type: object }, { type: object }] } + _links: { properties: { first: { properties: { href: { format: iri-reference, type: string } }, type: object }, last: { properties: { href: { format: iri-reference, type: string } }, type: object }, next: { properties: { href: { format: iri-reference, type: string } }, type: object }, previous: { properties: { href: { format: iri-reference, type: string } }, type: object }, self: { properties: { href: { format: iri-reference, type: string } }, type: object } }, type: object } + itemsPerPage: { minimum: 0, type: integer } + totalItems: { minimum: 0, type: integer } + required: + - _embedded + - _links + type: object + application/json: + schema: + items: + $ref: '#/components/schemas/CampCollaboration-read_CampCollaboration.Camp_CampCollaboration.User' + type: array + application/ld+json: + schema: + properties: + member: { items: { $ref: '#/components/schemas/CampCollaboration.jsonld-read_CampCollaboration.Camp_CampCollaboration.User' }, type: array } + search: { properties: { '@type': { type: string }, mapping: { items: { properties: { '@type': { type: string }, property: { type: ['null', string] }, required: { type: boolean }, variable: { type: string } }, type: object }, type: array }, template: { type: string }, variableRepresentation: { type: string } }, type: object } + totalItems: { minimum: 0, type: integer } + view: { example: { '@id': string, first: string, last: string, next: string, previous: string, type: string }, properties: { '@id': { format: iri-reference, type: string }, '@type': { type: string }, first: { format: iri-reference, type: string }, last: { format: iri-reference, type: string }, next: { format: iri-reference, type: string }, previous: { format: iri-reference, type: string } }, type: object } + required: + - member + type: object + application/vnd.api+json: + schema: + items: + $ref: '#/components/schemas/CampCollaboration.jsonapi' + type: array + text/html: + schema: + items: + $ref: '#/components/schemas/CampCollaboration-read_CampCollaboration.Camp_CampCollaboration.User' + type: array + description: 'CampCollaboration collection' + 403: + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + application/ld+json: + schema: + $ref: '#/components/schemas/Error.jsonld' + application/problem+json: + schema: + $ref: '#/components/schemas/Error' + description: Forbidden + links: [] + summary: 'Retrieves the collection of CampCollaboration resources.' + tags: + - CampCollaboration '/camps/{campId}/categories': get: deprecated: false diff --git a/api/tests/Api/SnapshotTests/__snapshots__/test_EndpointPerformanceTest__testPerformanceDidNotChangeForStableEndpoints__1.yml b/api/tests/Api/SnapshotTests/__snapshots__/test_EndpointPerformanceTest__testPerformanceDidNotChangeForStableEndpoints__1.yml index 24e2ff6931..a36a795d0c 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/test_EndpointPerformanceTest__testPerformanceDidNotChangeForStableEndpoints__1.yml +++ b/api/tests/Api/SnapshotTests/__snapshots__/test_EndpointPerformanceTest__testPerformanceDidNotChangeForStableEndpoints__1.yml @@ -4,7 +4,7 @@ /activity_progress_labels/item: 7 /activity_responsibles: 6 /activity_responsibles/item: 8 -/camps: 20 +/camps: 15 /camps/item: 19 /camp_collaborations: 16 /camp_collaborations/item: 7 @@ -25,7 +25,7 @@ /material_lists: 6 /material_lists/item: 7 /periods: 6 -/periods/item: 15 +/periods/item: 12 /profiles: 6 /profiles/item: 6 /schedule_entries: 23 @@ -35,7 +35,7 @@ '/activity_progress_labels?camp=': 6 '/activity_responsibles?activity.camp=': 6 '/camp_collaborations?camp=': 10 -'/camp_collaborations?activityResponsibles.activity=': 12 +'/camp_collaborations?activityResponsibles.activity=': 9 '/categories?camp=': 9 '/content_types?categories=': 6 '/day_responsibles?day.period=': 6