diff --git a/api/fixtures/comments.yml b/api/fixtures/comments.yml new file mode 100644 index 0000000000..395a209f0f --- /dev/null +++ b/api/fixtures/comments.yml @@ -0,0 +1,19 @@ +App\Entity\Comment: + comment1: + camp: '@camp1' + activity: '@activity1' + text: + author: '@user1manager' + orphanDescription: null + comment2: + camp: '@camp1' + activity: '@activity1' + text: + author: '@user4unrelated' + orphanDescription: null + comment3: + camp: '@camp1' + activity: '@activity2' + text: + author: '@user1manager' + orphanDescription: null \ No newline at end of file diff --git a/api/migrations/schema/Version20250413090555.php b/api/migrations/schema/Version20250413090555.php new file mode 100644 index 0000000000..0b22c32e0b --- /dev/null +++ b/api/migrations/schema/Version20250413090555.php @@ -0,0 +1,64 @@ +addSql(<<<'SQL' + CREATE TABLE comment (id VARCHAR(16) NOT NULL, createTime TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updateTime TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, text TEXT NOT NULL, orphanDescription TEXT DEFAULT NULL, campId VARCHAR(16) NOT NULL, activityId VARCHAR(16) DEFAULT NULL, authorId VARCHAR(16) NOT NULL, PRIMARY KEY(id)) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_9474526C6D299429 ON comment (campId) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_9474526C1335E2FC ON comment (activityId) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_9474526CA196F9FD ON comment (authorId) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_9474526C9D468A55 ON comment (createTime) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_9474526C55AA53E2 ON comment (updateTime) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE comment ADD CONSTRAINT FK_9474526C6D299429 FOREIGN KEY (campId) REFERENCES camp (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE comment ADD CONSTRAINT FK_9474526C1335E2FC FOREIGN KEY (activityId) REFERENCES activity (id) NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE comment ADD CONSTRAINT FK_9474526CA196F9FD FOREIGN KEY (authorId) REFERENCES "user" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE + SQL); + } + + public function down(Schema $schema): void { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql(<<<'SQL' + ALTER TABLE comment DROP CONSTRAINT FK_9474526C6D299429 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE comment DROP CONSTRAINT FK_9474526C1335E2FC + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE comment DROP CONSTRAINT FK_9474526CA196F9FD + SQL); + $this->addSql(<<<'SQL' + DROP TABLE comment + SQL); + } +} diff --git a/api/src/Entity/Activity.php b/api/src/Entity/Activity.php index 69519e711b..5ec265e33a 100644 --- a/api/src/Entity/Activity.php +++ b/api/src/Entity/Activity.php @@ -177,6 +177,17 @@ class Activity extends BaseEntity implements BelongsToCampInterface { #[ORM\Column(type: 'text')] public string $location = ''; + /** + * All comments of the activity. + */ + #[ApiProperty( + writable: false, + uriTemplate: Comment::ACTIVITY_SUBRESOURCE_URI_TEMPLATE, + example: '/activity/1a2b3c4d/comments' + )] + #[ORM\OneToMany(targetEntity: Comment::class, mappedBy: 'activity')] + public Collection $comments; + /** * The list of people that are responsible for planning or carrying out this activity. */ @@ -189,6 +200,7 @@ public function __construct() { parent::__construct(); $this->scheduleEntries = new ArrayCollection(); $this->activityResponsibles = new ArrayCollection(); + $this->comments = new ArrayCollection(); } public function getCamp(): ?Camp { @@ -290,4 +302,14 @@ public function removeActivityResponsible(ActivityResponsible $activityResponsib return $this; } + + public function removeComment(Comment $comment): self { + if ($this->comments->removeElement($comment)) { + if ($comment->activity === $this) { + $comment->activity = null; + } + } + + return $this; + } } diff --git a/api/src/Entity/Camp.php b/api/src/Entity/Camp.php index d183eee36a..a8068e87b8 100644 --- a/api/src/Entity/Camp.php +++ b/api/src/Entity/Camp.php @@ -386,6 +386,13 @@ class Camp extends BaseEntity implements BelongsToCampInterface, CopyFromPrototy #[ORM\JoinColumn(nullable: false)] public ?User $owner = null; + /** + * All comments of the camp. + */ + #[ApiProperty(readable: false, writable: false)] + #[ORM\OneToMany(targetEntity: Comment::class, mappedBy: 'camp', cascade: ['persist', 'remove'], orphanRemoval: true)] + public Collection $comments; + public function __construct() { parent::__construct(); $this->collaborations = new ArrayCollection(); @@ -397,6 +404,7 @@ public function __construct() { $this->materialLists = new ArrayCollection(); $this->checklists = new ArrayCollection(); $this->campRootContentNodes = new ArrayCollection(); + $this->comments = new ArrayCollection(); } /** diff --git a/api/src/Entity/Comment.php b/api/src/Entity/Comment.php new file mode 100644 index 0000000000..91693ce4ad --- /dev/null +++ b/api/src/Entity/Comment.php @@ -0,0 +1,121 @@ + ['create', 'write']], + securityPostDenormalize: 'is_granted("CAMP_COLLABORATOR", object)', + ), + new GetCollection( + uriTemplate: self::ACTIVITY_SUBRESOURCE_URI_TEMPLATE, + uriVariables: [ + 'activityId' => new Link( + toProperty: 'activity', + fromClass: Activity::class, + security: 'is_granted("CAMP_COLLABORATOR", activity)', + ), + ], + security: 'is_fully_authenticated()', + ), + ], + denormalizationContext: ['groups' => ['write']], + normalizationContext: ['groups' => ['read']], +)] +#[ApiFilter(filterClass: SearchFilter::class, properties: ['camp', 'activity'])] +#[ORM\Entity(repositoryClass: CommentRepository::class)] +class Comment extends BaseEntity implements BelongsToCampInterface { + public const ACTIVITY_SUBRESOURCE_URI_TEMPLATE = '/activities/{activityId}/comments{._format}'; + + /** + * The camp this comment belongs to. + */ + #[ApiProperty(example: '/camps/1a2b3c4d')] + #[Groups(['read', 'create'])] + #[ORM\ManyToOne(targetEntity: Camp::class, inversedBy: 'comments')] + #[ORM\JoinColumn(nullable: false, onDelete: 'cascade')] + public ?Camp $camp = null; + + /** + * The activity this comment belongs to. + */ + #[Assert\Expression('this.activity.camp == this.camp', 'The activity must belong to the camp.')] + #[ApiProperty(example: '/activities/1a2b3c4d')] + #[Groups(['read', 'create'])] + #[ORM\ManyToOne(targetEntity: Activity::class, inversedBy: 'comments')] + #[ORM\JoinColumn(nullable: true)] + public ?Activity $activity = null; + + /** + * The author of the comment. + */ + #[Assert\DisableAutoMapping] // avoids validation error when author is null in payload + #[ApiProperty(example: '/users/1a2b3c4d', writable: false)] + #[Groups(['read', 'create'])] + #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'comments')] + #[ORM\JoinColumn(nullable: false, onDelete: 'cascade')] + public ?User $author = null; + + /** + * The actual comment. + */ + #[InputFilter\Trim] + #[InputFilter\CleanText] + #[Assert\NotBlank] + #[Assert\Length(max: 1024)] + #[ApiProperty(example: 'This activity is great!')] + #[Groups(['read', 'create'])] + #[ORM\Column(type: 'text', nullable: false)] + public ?string $text = null; + + /** + * Persisted description of the context where the comment was originally writen. + * Only non-null when activity pointer is null, i.e. activity was deleted. + * Currently defined as the title of the activity when it was deleted. + */ + #[InputFilter\Trim] + #[InputFilter\CleanText] + #[Assert\Length(max: 32)] + #[ApiProperty(example: 'Sportolympiade', writable: false)] + #[Groups(['read', 'create'])] + #[ORM\Column(type: 'text', nullable: true)] + public ?string $orphanDescription = null; + + public function __construct() { + parent::__construct(); + } + + public function getCamp(): ?Camp { + return $this->camp; + } +} diff --git a/api/src/Entity/User.php b/api/src/Entity/User.php index 40c960fb46..9356dc1919 100644 --- a/api/src/Entity/User.php +++ b/api/src/Entity/User.php @@ -166,11 +166,19 @@ class User extends BaseEntity implements UserInterface, PasswordAuthenticatedUse #[ORM\JoinColumn(nullable: false, unique: true, onDelete: 'restrict')] public Profile $profile; + /** + * All comments of the user. + */ + #[ApiProperty(readable: false, writable: false)] + #[ORM\OneToMany(targetEntity: Comment::class, mappedBy: 'author')] + public Collection $comments; + public function __construct() { parent::__construct(); $this->ownedCamps = new ArrayCollection(); $this->collaborations = new ArrayCollection(); $this->userCamps = new ArrayCollection(); + $this->comments = new ArrayCollection(); } /** diff --git a/api/src/Repository/CommentRepository.php b/api/src/Repository/CommentRepository.php new file mode 100644 index 0000000000..1456488c99 --- /dev/null +++ b/api/src/Repository/CommentRepository.php @@ -0,0 +1,42 @@ +getRootAliases()[0]; + + $campsQry = $queryBuilder->getEntityManager()->createQueryBuilder(); + $campsQry->select('identity(uc.camp)'); + $campsQry->from(UserCamp::class, 'uc'); + $campsQry->where('uc.user = :current_user'); + + $queryBuilder->andWhere( + $queryBuilder->expr()->orX( + "{$rootAlias}.author = :current_user", + $queryBuilder->expr()->in("{$rootAlias}.camp", $campsQry->getDQL()) + ) + ); + $queryBuilder->setParameter('current_user', $user); + } +} diff --git a/api/src/State/ActivityRemoveProcessor.php b/api/src/State/ActivityRemoveProcessor.php index 652727b048..b58ea1c405 100644 --- a/api/src/State/ActivityRemoveProcessor.php +++ b/api/src/State/ActivityRemoveProcessor.php @@ -5,6 +5,7 @@ use ApiPlatform\Metadata\Operation; use ApiPlatform\State\ProcessorInterface; use App\Entity\Activity; +use App\Entity\Comment; use App\State\Util\AbstractRemoveProcessor; use Doctrine\ORM\EntityManagerInterface; @@ -26,5 +27,12 @@ public function onBefore($data, Operation $operation, array $uriVariables = [], // Deleting rootContentNode would normally be done automatically with orphanRemoval:true // However, this currently runs into an error due to https://github.com/doctrine-extensions/DoctrineExtensions/issues/2510 $this->em->remove($data->rootContentNode); + + /** @var Comment[] $comments */ + $comments = $data->comments; + foreach ($comments as $comment) { + $comment->orphanDescription = $comment->activity->title; + $comment->activity->removeComment($comment); + } } } diff --git a/api/src/State/CommentCreateProcessor.php b/api/src/State/CommentCreateProcessor.php new file mode 100644 index 0000000000..cb0fc3273a --- /dev/null +++ b/api/src/State/CommentCreateProcessor.php @@ -0,0 +1,35 @@ + + */ +class CommentCreateProcessor extends AbstractPersistProcessor { + private Security $security; + + public function __construct(ProcessorInterface $decorated, Security $security) { + parent::__construct($decorated); + $this->security = $security; + } + + /** + * @param Comment $data + */ + public function onBefore($data, Operation $operation, array $uriVariables = [], array $context = []): Comment { + /** @var User $user */ + $user = $this->security->getUser(); + + // Set the user as the author of the comment + $data->author = $user; + + return $data; + } +} diff --git a/api/tests/Api/Activities/DeleteActivityTest.php b/api/tests/Api/Activities/DeleteActivityTest.php index dd036605e3..04637d8087 100644 --- a/api/tests/Api/Activities/DeleteActivityTest.php +++ b/api/tests/Api/Activities/DeleteActivityTest.php @@ -99,4 +99,26 @@ public function testDeleteActivityAlsoDeletesContentNodes() { $client->request('GET', $this->getIriFor(static::$fixtures['multiSelect1'])); $this->assertResponseStatusCodeSame(404); } + + public function testDeleteActivityAlsoNullifiesCommentReferencesAndSetsOrphanDescription() { + $client = static::createClientWithCredentials(); + // Disable resetting the database between the two requests + $client->disableReboot(); + + $activity = static::$fixtures['activity1']; + $comments = $activity->comments; + $client->request('DELETE', $this->getIriFor($activity)); + $this->assertResponseStatusCodeSame(204); + + foreach (['comment1', 'comment2'] as $commentId) { + $client->request('GET', $this->getIriFor(static::$fixtures[$commentId])); + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + '_links' => [ + 'activity' => null, + ], + 'orphanDescription' => $activity->title, + ]); + } + } } diff --git a/api/tests/Api/Comments/CreateCommentTest.php b/api/tests/Api/Comments/CreateCommentTest.php new file mode 100644 index 0000000000..d108487a5c --- /dev/null +++ b/api/tests/Api/Comments/CreateCommentTest.php @@ -0,0 +1,121 @@ +request('POST', '/comments', ['json' => $this->getExampleWritePayload()]); + + $this->assertResponseStatusCodeSame(401); + $this->assertJsonContains([ + 'code' => 401, + 'message' => 'JWT Token not found', + ]); + } + + public function testCreateCommentIsNotPossibleForUnrelatedUserBecauseActivityIsNotReadable() { + static::createClientWithCredentials(['email' => static::$fixtures['user4unrelated']->getEmail()]) + ->request('POST', '/comments', ['json' => $this->getExampleWritePayload()]) + ; + $this->assertResponseStatusCodeSame(400); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Item not found for "'.$this->getIriFor('camp1').'".', + ]); + } + + public function testCreateCommentIsAllowedForGuest() { + static::createClientWithCredentials(['email' => static::$fixtures['user3guest']->getEmail()]) + ->request('POST', '/comments', ['json' => $this->getExampleWritePayload()]) + ; + + $this->assertResponseStatusCodeSame(201); + $this->assertJsonContains($this->getExampleReadPayload()); + } + + public function testCreateCommentIsAllowedForMember() { + static::createClientWithCredentials(['email' => static::$fixtures['user2member']->getEmail()]) + ->request('POST', '/comments', ['json' => $this->getExampleWritePayload()]) + ; + + $this->assertResponseStatusCodeSame(201); + $this->assertJsonContains($this->getExampleReadPayload()); + } + + public function testCreateCommentIsAllowedForManager() { + static::createClientWithCredentials()->request('POST', '/comments', ['json' => $this->getExampleWritePayload()]); + + $this->assertResponseStatusCodeSame(201); + $this->assertJsonContains($this->getExampleReadPayload()); + } + + public function testCreateCommentValidatesMissingText() { + static::createClientWithCredentials()->request('POST', '/comments', ['json' => $this->getExampleWritePayload([], ['text'])]); + + $this->assertResponseStatusCodeSame(422); + $this->assertJsonContains([ + 'violations' => [ + [ + 'propertyPath' => 'text', + 'message' => 'This value should not be blank.', + ], + ], + ]); + } + + public function testCreateCommentValidatesTextMaxLength() { + static::createClientWithCredentials()->request('POST', '/comments', ['json' => $this->getExampleWritePayload([ + 'text' => str_repeat('a', 1025), + ])]); + + $this->assertResponseStatusCodeSame(422); + $this->assertJsonContains([ + 'violations' => [ + [ + 'propertyPath' => 'text', + 'message' => 'This value is too long. It should have 1024 characters or less.', + ], + ], + ]); + } + + public function testCreateCommentRejectsAuthorInPayload() { + static::createClientWithCredentials()->request('POST', '/comments', ['json' => $this->getExampleWritePayload(['author' => $this->getIriFor('user1manager')])]); + + $this->assertResponseStatusCodeSame(400); + $this->assertJsonContains([ + 'detail' => 'Extra attributes are not allowed ("author" is unknown).', + ]); + } + + public function getExampleWritePayload($attributes = [], $except = []) { + return $this->getExamplePayload( + Comment::class, + Post::class, + array_merge([ + 'camp' => $this->getIriFor('camp1'), + 'activity' => $this->getIriFor('activity1'), + ], $attributes), + [], + $except + ); + } + + public function getExampleReadPayload($attributes = [], $except = []) { + return $this->getExamplePayload( + Comment::class, + Get::class, + $attributes, + ['camp', 'activity', 'author'], + $except + ); + } +} diff --git a/api/tests/Api/Comments/DeleteCommentTest.php b/api/tests/Api/Comments/DeleteCommentTest.php new file mode 100644 index 0000000000..5ca06339b5 --- /dev/null +++ b/api/tests/Api/Comments/DeleteCommentTest.php @@ -0,0 +1,43 @@ +request('DELETE', '/comments/'.$comment->getId()); + + $this->assertResponseStatusCodeSame(401); + $this->assertJsonContains([ + 'code' => 401, + 'message' => 'JWT Token not found', + ]); + } + + public function testDeleteCommentIsDeniedForNonAuthor() { + $comment = static::getFixture('comment1'); + static::createClientWithCredentials(['email' => static::$fixtures['user4unrelated']->getEmail()]) + ->request('DELETE', '/comments/'.$comment->getId()) + ; + + $this->assertResponseStatusCodeSame(404); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Not Found', + ]); + } + + public function testDeleteCommentIsAllowedForAuthor() { + $comment = static::getFixture('comment1'); + static::createClientWithCredentials()->request('DELETE', '/comments/'.$comment->getId()); + + $this->assertResponseStatusCodeSame(204); + $this->assertNull($this->getEntityManager()->getRepository(Comment::class)->find($comment->getId())); + } +} diff --git a/api/tests/Api/Comments/ListCommentsTest.php b/api/tests/Api/Comments/ListCommentsTest.php new file mode 100644 index 0000000000..533cdfb52f --- /dev/null +++ b/api/tests/Api/Comments/ListCommentsTest.php @@ -0,0 +1,63 @@ +request('GET', '/comments'); + + $this->assertResponseStatusCodeSame(401); + $this->assertJsonContains([ + 'code' => 401, + 'message' => 'JWT Token not found', + ]); + } + + public function testListCommentsIsAllowedForLoggedInUser() { + $response = static::createClientWithCredentials()->request('GET', '/comments'); + + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 3, + '_embedded' => [ + 'items' => [], + ], + ]); + } + + public function testListCommentsFilteredByActivity() { + $activity = static::getFixture('activity1'); + $response = static::createClientWithCredentials()->request('GET', '/comments?activity='.$this->getIriFor($activity)); + + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 2, + ]); + } + + public function testListCommentsActivitySubresource() { + $activity = static::getFixture('activity1'); + $response = static::createClientWithCredentials()->request('GET', $this->getIriFor($activity).'/comments'); + + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'totalItems' => 2, + ]); + } + + public function testListCommentsActivitySubresourceIsDeniedForUnrelatedUser() { + $activity = static::getFixture('activity1'); + $response = static::createClientWithCredentials(['email' => static::$fixtures['user4unrelated']->getEmail()])->request('GET', $this->getIriFor($activity).'/comments'); + + $this->assertResponseStatusCodeSame(404); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Relation for link security not found.', + ]); + } +} diff --git a/api/tests/Api/Comments/ReadCommentTest.php b/api/tests/Api/Comments/ReadCommentTest.php new file mode 100644 index 0000000000..2c854ba22a --- /dev/null +++ b/api/tests/Api/Comments/ReadCommentTest.php @@ -0,0 +1,58 @@ +request('GET', '/comments/'.$comment->getId()); + + $this->assertResponseStatusCodeSame(401); + $this->assertJsonContains([ + 'code' => 401, + 'message' => 'JWT Token not found', + ]); + } + + public function testGetSingleCommentIsAllowedForAuthor() { + $comment = static::getFixture('comment2'); + static::createClientWithCredentials(['email' => static::$fixtures['user4unrelated']->getEmail()]) + ->request('GET', '/comments/'.$comment->getId()) + ; + + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $comment->getId(), + 'text' => $comment->text, + ]); + } + + public function testGetSingleCommentIsAllowedForCollaborator() { + $comment = static::getFixture('comment1'); + static::createClientWithCredentials()->request('GET', '/comments/'.$comment->getId()); + + $this->assertResponseStatusCodeSame(200); + $this->assertJsonContains([ + 'id' => $comment->getId(), + 'text' => $comment->text, + ]); + } + + public function testGetSingleCommentIsDeniedForUnrelatedUser() { + $comment = static::getFixture('comment1'); + static::createClientWithCredentials(['email' => static::$fixtures['user4unrelated']->getEmail()]) + ->request('GET', '/comments/'.$comment->getId()) + ; + + $this->assertResponseStatusCodeSame(404); + $this->assertJsonContains([ + 'title' => 'An error occurred', + 'detail' => 'Not Found', + ]); + } +} diff --git a/api/tests/Api/SnapshotTests/ReadItemFixtureMap.php b/api/tests/Api/SnapshotTests/ReadItemFixtureMap.php index 86b74b207d..e629891bc7 100644 --- a/api/tests/Api/SnapshotTests/ReadItemFixtureMap.php +++ b/api/tests/Api/SnapshotTests/ReadItemFixtureMap.php @@ -17,6 +17,7 @@ public static function get(string $collectionEndpoint, array $fixtures): mixed { '/content_node/column_layouts' => $fixtures['columnLayout2'], '/content_node/responsive_layouts' => $fixtures['responsiveLayout1'], '/content_types' => $fixtures['contentTypeSafetyConsiderations'], + '/comments' => $fixtures['comment1'], '/day_responsibles' => $fixtures['dayResponsible1'], '/days' => $fixtures['day1period1'], '/material_items' => $fixtures['materialItem1'], diff --git a/api/tests/Api/SnapshotTests/ResponseSnapshotTest.php b/api/tests/Api/SnapshotTests/ResponseSnapshotTest.php index 0253e15c41..b03321cdfc 100644 --- a/api/tests/Api/SnapshotTests/ResponseSnapshotTest.php +++ b/api/tests/Api/SnapshotTests/ResponseSnapshotTest.php @@ -212,6 +212,7 @@ public static function getPatchEndpoints() { '/content_types' => false, '/days' => false, '/day_responsibles' => false, + '/comments' => false, default => true, }; }); diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set comments__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set comments__1.json new file mode 100644 index 0000000000..101b4ff8cf --- /dev/null +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set comments__1.json @@ -0,0 +1,80 @@ +{ + "_embedded": { + "items": [ + { + "_links": { + "activity": { + "href": "escaped_value" + }, + "author": { + "href": "escaped_value" + }, + "camp": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "id": "escaped_value", + "orphanDescription": "escaped_value", + "text": "escaped_value" + }, + { + "_links": { + "activity": { + "href": "escaped_value" + }, + "author": { + "href": "escaped_value" + }, + "camp": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "id": "escaped_value", + "orphanDescription": "escaped_value", + "text": "escaped_value" + }, + { + "_links": { + "activity": { + "href": "escaped_value" + }, + "author": { + "href": "escaped_value" + }, + "camp": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "id": "escaped_value", + "orphanDescription": "escaped_value", + "text": "escaped_value" + } + ] + }, + "_links": { + "items": [ + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + }, + { + "href": "escaped_value" + } + ], + "self": { + "href": "escaped_value" + } + }, + "totalItems": "escaped_value" +} diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set comments__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set comments__1.json new file mode 100644 index 0000000000..60703e8dee --- /dev/null +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set comments__1.json @@ -0,0 +1,19 @@ +{ + "_links": { + "activity": { + "href": "escaped_value" + }, + "author": { + "href": "escaped_value" + }, + "camp": { + "href": "escaped_value" + }, + "self": { + "href": "escaped_value" + } + }, + "id": "escaped_value", + "orphanDescription": "escaped_value", + "text": "escaped_value" +} diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml index 095f4f5ee9..b3956aa3dc 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml @@ -9975,6 +9975,362 @@ components: - parent - position type: object + Comment-create_write: + deprecated: false + description: 'A Comment.' + properties: + activity: + description: 'The activity this comment belongs to.' + example: /activities/1a2b3c4d + format: iri-reference + type: + - 'null' + - string + author: + description: 'The author of the comment.' + example: /users/1a2b3c4d + format: iri-reference + readOnly: true + type: string + camp: + description: 'The camp this comment belongs to.' + example: /camps/1a2b3c4d + format: iri-reference + type: string + orphanDescription: + description: 'Persisted description of the context where the comment was originally writen.' + example: Sportolympiade + maxLength: 32 + readOnly: true + type: + - 'null' + - string + text: + description: 'The actual comment.' + example: 'This activity is great!' + maxLength: 1024 + type: string + required: + - camp + - text + type: object + Comment-read: + deprecated: false + description: 'A Comment.' + properties: + activity: + description: 'The activity this comment belongs to.' + example: /activities/1a2b3c4d + format: iri-reference + type: + - 'null' + - string + author: + description: 'The author of the comment.' + example: /users/1a2b3c4d + format: iri-reference + readOnly: true + type: string + camp: + description: 'The camp this comment belongs to.' + example: /camps/1a2b3c4d + format: iri-reference + type: string + id: + description: 'An internal, unique, randomly generated identifier of this entity.' + example: 1a2b3c4d + maxLength: 16 + readOnly: true + type: string + orphanDescription: + description: 'Persisted description of the context where the comment was originally writen.' + example: Sportolympiade + maxLength: 32 + readOnly: true + type: + - 'null' + - string + text: + description: 'The actual comment.' + example: 'This activity is great!' + maxLength: 1024 + type: string + required: + - camp + - text + type: object + Comment.jsonapi: + deprecated: false + description: 'A Comment.' + properties: + data: + properties: + attributes: + properties: + _id: + description: 'An internal, unique, randomly generated identifier of this entity.' + example: 1a2b3c4d + maxLength: 16 + readOnly: true + type: string + orphanDescription: + description: 'Persisted description of the context where the comment was originally writen.' + example: Sportolympiade + maxLength: 32 + readOnly: true + type: ['null', string] + text: + description: 'The actual comment.' + example: 'This activity is great!' + maxLength: 1024 + readOnly: true + type: string + required: + - text + type: object + id: + type: string + relationships: + properties: + activity: + properties: { data: { properties: { id: { format: iri-reference, type: string }, type: { type: string } }, type: object } } + author: + properties: { data: { properties: { id: { format: iri-reference, type: string }, type: { type: string } }, type: object } } + camp: + properties: { data: { properties: { id: { format: iri-reference, type: string }, type: { type: string } }, type: object } } + required: + - camp + type: object + type: + type: string + required: + - id + - type + type: object + included: + description: 'Related resources requested via the "include" query parameter.' + externalDocs: + url: 'https://jsonapi.org/format/#fetching-includes' + items: + anyOf: + - + $ref: '#/components/schemas/Comment.jsonapi' + - + $ref: '#/components/schemas/Comment.jsonapi' + - + $ref: '#/components/schemas/Comment.jsonapi' + readOnly: true + type: array + type: object + Comment.jsonhal-create_write: + deprecated: false + description: 'A Comment.' + properties: + _links: + properties: + self: + properties: + href: + format: iri-reference + type: string + type: object + type: object + activity: + description: 'The activity this comment belongs to.' + example: /activities/1a2b3c4d + format: iri-reference + type: + - 'null' + - string + author: + description: 'The author of the comment.' + example: /users/1a2b3c4d + format: iri-reference + readOnly: true + type: string + camp: + description: 'The camp this comment belongs to.' + example: /camps/1a2b3c4d + format: iri-reference + type: string + orphanDescription: + description: 'Persisted description of the context where the comment was originally writen.' + example: Sportolympiade + maxLength: 32 + readOnly: true + type: + - 'null' + - string + text: + description: 'The actual comment.' + example: 'This activity is great!' + maxLength: 1024 + type: string + required: + - camp + - text + type: object + Comment.jsonhal-read: + deprecated: false + description: 'A Comment.' + properties: + _links: + properties: + self: + properties: + href: + format: iri-reference + type: string + type: object + type: object + activity: + description: 'The activity this comment belongs to.' + example: /activities/1a2b3c4d + format: iri-reference + type: + - 'null' + - string + author: + description: 'The author of the comment.' + example: /users/1a2b3c4d + format: iri-reference + readOnly: true + type: string + camp: + description: 'The camp this comment belongs to.' + example: /camps/1a2b3c4d + format: iri-reference + type: string + id: + description: 'An internal, unique, randomly generated identifier of this entity.' + example: 1a2b3c4d + maxLength: 16 + readOnly: true + type: string + orphanDescription: + description: 'Persisted description of the context where the comment was originally writen.' + example: Sportolympiade + maxLength: 32 + readOnly: true + type: + - 'null' + - string + text: + description: 'The actual comment.' + example: 'This activity is great!' + maxLength: 1024 + type: string + required: + - camp + - text + type: object + Comment.jsonld-create_write: + deprecated: false + description: 'A Comment.' + properties: + activity: + description: 'The activity this comment belongs to.' + example: /activities/1a2b3c4d + format: iri-reference + type: + - 'null' + - string + author: + description: 'The author of the comment.' + example: /users/1a2b3c4d + format: iri-reference + readOnly: true + type: string + camp: + description: 'The camp this comment belongs to.' + example: /camps/1a2b3c4d + format: iri-reference + type: string + orphanDescription: + description: 'Persisted description of the context where the comment was originally writen.' + example: Sportolympiade + maxLength: 32 + readOnly: true + type: + - 'null' + - string + text: + description: 'The actual comment.' + example: 'This activity is great!' + maxLength: 1024 + type: string + required: + - camp + - text + type: object + Comment.jsonld-read: + deprecated: false + description: 'A Comment.' + properties: + '@context': + oneOf: + - + additionalProperties: true + properties: + '@vocab': + type: string + hydra: + enum: ['http://www.w3.org/ns/hydra/core#'] + type: string + required: + - '@vocab' + - hydra + type: object + - + type: string + readOnly: true + '@id': + readOnly: true + type: string + '@type': + readOnly: true + type: string + activity: + description: 'The activity this comment belongs to.' + example: /activities/1a2b3c4d + format: iri-reference + type: + - 'null' + - string + author: + description: 'The author of the comment.' + example: /users/1a2b3c4d + format: iri-reference + readOnly: true + type: string + camp: + description: 'The camp this comment belongs to.' + example: /camps/1a2b3c4d + format: iri-reference + type: string + id: + description: 'An internal, unique, randomly generated identifier of this entity.' + example: 1a2b3c4d + maxLength: 16 + readOnly: true + type: string + orphanDescription: + description: 'Persisted description of the context where the comment was originally writen.' + example: Sportolympiade + maxLength: 32 + readOnly: true + type: + - 'null' + - string + text: + description: 'The actual comment.' + example: 'This activity is great!' + maxLength: 1024 + type: string + required: + - camp + - text + type: object ConstraintViolation-json: deprecated: false description: 'Unprocessable entity' @@ -22206,19 +22562,158 @@ paths: $ref: '#/components/schemas/Activity.jsonhal-read_Activity.Category_Activity.ActivityProgressLabel_Activity.ActivityResponsibles_Activity.ScheduleEntries_Activity.ContentNodes' application/json: schema: - $ref: '#/components/schemas/Activity-read_Activity.Category_Activity.ActivityProgressLabel_Activity.ActivityResponsibles_Activity.ScheduleEntries_Activity.ContentNodes' + $ref: '#/components/schemas/Activity-read_Activity.Category_Activity.ActivityProgressLabel_Activity.ActivityResponsibles_Activity.ScheduleEntries_Activity.ContentNodes' + application/ld+json: + schema: + $ref: '#/components/schemas/Activity.jsonld-read_Activity.Category_Activity.ActivityProgressLabel_Activity.ActivityResponsibles_Activity.ScheduleEntries_Activity.ContentNodes' + application/vnd.api+json: + schema: + $ref: '#/components/schemas/Activity.jsonapi' + text/html: + schema: + $ref: '#/components/schemas/Activity-read_Activity.Category_Activity.ActivityProgressLabel_Activity.ActivityResponsibles_Activity.ScheduleEntries_Activity.ContentNodes' + description: 'Activity resource created' + links: [] + 400: + 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: 'Invalid input' + links: [] + 422: + content: + application/json: + schema: + $ref: '#/components/schemas/ConstraintViolation-json' + application/ld+json: + schema: + $ref: '#/components/schemas/ConstraintViolation.jsonld-jsonld' + application/problem+json: + schema: + $ref: '#/components/schemas/ConstraintViolation-json' + description: 'An error occurred' + links: [] + summary: 'Creates a Activity resource.' + tags: + - Activity + '/activities/{activityId}/comments': + get: + deprecated: false + description: 'Retrieves the collection of Comment resources.' + operationId: api_activities_activityIdcomments_get_collection + parameters: + - + allowEmptyValue: false + allowReserved: false + deprecated: false + description: '' + explode: false + in: query + name: 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: '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: 'Comment identifier' + explode: false + in: path + name: activityId + required: true + schema: + type: string + style: simple + responses: + 200: + content: + application/hal+json: + schema: + properties: + _embedded: { anyOf: [{ properties: { item: { items: { $ref: '#/components/schemas/Comment.jsonhal-read' }, 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/Comment-read' + type: array application/ld+json: schema: - $ref: '#/components/schemas/Activity.jsonld-read_Activity.Category_Activity.ActivityProgressLabel_Activity.ActivityResponsibles_Activity.ScheduleEntries_Activity.ContentNodes' + properties: + member: { items: { $ref: '#/components/schemas/Comment.jsonld-read' }, 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: - $ref: '#/components/schemas/Activity.jsonapi' + items: + $ref: '#/components/schemas/Comment.jsonapi' + type: array text/html: schema: - $ref: '#/components/schemas/Activity-read_Activity.Category_Activity.ActivityProgressLabel_Activity.ActivityResponsibles_Activity.ScheduleEntries_Activity.ContentNodes' - description: 'Activity resource created' - links: [] - 400: + items: + $ref: '#/components/schemas/Comment-read' + type: array + description: 'Comment collection' + 403: content: application/json: schema: @@ -22229,24 +22724,11 @@ paths: application/problem+json: schema: $ref: '#/components/schemas/Error' - description: 'Invalid input' - links: [] - 422: - content: - application/json: - schema: - $ref: '#/components/schemas/ConstraintViolation-json' - application/ld+json: - schema: - $ref: '#/components/schemas/ConstraintViolation.jsonld-jsonld' - application/problem+json: - schema: - $ref: '#/components/schemas/ConstraintViolation-json' - description: 'An error occurred' + description: Forbidden links: [] - summary: 'Creates a Activity resource.' + summary: 'Retrieves the collection of Comment resources.' tags: - - Activity + - Comment '/activities/{id}': delete: deprecated: false @@ -25670,14 +26152,243 @@ paths: $ref: '#/components/schemas/ChecklistItem-read' application/ld+json: schema: - $ref: '#/components/schemas/ChecklistItem.jsonld-read' + $ref: '#/components/schemas/ChecklistItem.jsonld-read' + application/vnd.api+json: + schema: + $ref: '#/components/schemas/ChecklistItem.jsonapi' + text/html: + schema: + $ref: '#/components/schemas/ChecklistItem-read' + description: 'ChecklistItem resource' + 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: [] + 404: + 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: 'Not found' + links: [] + summary: 'Retrieves a ChecklistItem resource.' + tags: + - ChecklistItem + patch: + deprecated: false + description: 'Updates the ChecklistItem resource.' + operationId: api_checklist_items_id_patch + parameters: + - + allowEmptyValue: false + allowReserved: false + deprecated: false + description: 'ChecklistItem identifier' + explode: false + in: path + name: id + required: true + schema: + type: string + style: simple + requestBody: + content: + application/merge-patch+json: + schema: + $ref: '#/components/schemas/ChecklistItem-write' + application/vnd.api+json: + schema: + $ref: '#/components/schemas/ChecklistItem.jsonapi' + description: 'The updated ChecklistItem resource' + required: true + responses: + 200: + content: + application/hal+json: + schema: + $ref: '#/components/schemas/ChecklistItem.jsonhal-read' + application/json: + schema: + $ref: '#/components/schemas/ChecklistItem-read' + application/ld+json: + schema: + $ref: '#/components/schemas/ChecklistItem.jsonld-read' + application/vnd.api+json: + schema: + $ref: '#/components/schemas/ChecklistItem.jsonapi' + text/html: + schema: + $ref: '#/components/schemas/ChecklistItem-read' + description: 'ChecklistItem resource updated' + links: [] + 400: + 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: 'Invalid input' + links: [] + 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: [] + 404: + 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: 'Not found' + links: [] + 422: + content: + application/json: + schema: + $ref: '#/components/schemas/ConstraintViolation-json' + application/ld+json: + schema: + $ref: '#/components/schemas/ConstraintViolation.jsonld-jsonld' + application/problem+json: + schema: + $ref: '#/components/schemas/ConstraintViolation-json' + description: 'An error occurred' + links: [] + summary: 'Updates the ChecklistItem resource.' + tags: + - ChecklistItem + /checklists: + get: + deprecated: false + description: 'Retrieves the collection of Checklist resources.' + operationId: api_checklists_get_collection + parameters: + - + 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: false + in: query + name: isPrototype + required: false + schema: + type: boolean + 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: '' + explode: true + in: query + name: 'isPrototype[]' + required: false + schema: + items: + type: boolean + type: array + style: form + responses: + 200: + content: + application/hal+json: + schema: + properties: + _embedded: { anyOf: [{ properties: { item: { items: { $ref: '#/components/schemas/Checklist.jsonhal-read' }, 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/Checklist-read' + type: array + application/ld+json: + schema: + properties: + member: { items: { $ref: '#/components/schemas/Checklist.jsonld-read' }, 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: - $ref: '#/components/schemas/ChecklistItem.jsonapi' + items: + $ref: '#/components/schemas/Checklist.jsonapi' + type: array text/html: schema: - $ref: '#/components/schemas/ChecklistItem-read' - description: 'ChecklistItem resource' + items: + $ref: '#/components/schemas/Checklist-read' + type: array + description: 'Checklist collection' 403: content: application/json: @@ -25691,68 +26402,52 @@ paths: $ref: '#/components/schemas/Error' description: Forbidden links: [] - 404: - 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: 'Not found' - links: [] - summary: 'Retrieves a ChecklistItem resource.' + summary: 'Retrieves the collection of Checklist resources.' tags: - - ChecklistItem - patch: + - Checklist + post: deprecated: false - description: 'Updates the ChecklistItem resource.' - operationId: api_checklist_items_id_patch - parameters: - - - allowEmptyValue: false - allowReserved: false - deprecated: false - description: 'ChecklistItem identifier' - explode: false - in: path - name: id - required: true - schema: - type: string - style: simple + description: 'Creates a Checklist resource.' + operationId: api_checklists_post + parameters: [] requestBody: content: - application/merge-patch+json: + application/hal+json: schema: - $ref: '#/components/schemas/ChecklistItem-write' + $ref: '#/components/schemas/Checklist.jsonhal-write_create' + application/json: + schema: + $ref: '#/components/schemas/Checklist-write_create' + application/ld+json: + schema: + $ref: '#/components/schemas/Checklist.jsonld-write_create' application/vnd.api+json: schema: - $ref: '#/components/schemas/ChecklistItem.jsonapi' - description: 'The updated ChecklistItem resource' + $ref: '#/components/schemas/Checklist.jsonapi' + text/html: + schema: + $ref: '#/components/schemas/Checklist-write_create' + description: 'The new Checklist resource' required: true responses: - 200: + 201: content: application/hal+json: schema: - $ref: '#/components/schemas/ChecklistItem.jsonhal-read' + $ref: '#/components/schemas/Checklist.jsonhal-read' application/json: schema: - $ref: '#/components/schemas/ChecklistItem-read' + $ref: '#/components/schemas/Checklist-read' application/ld+json: schema: - $ref: '#/components/schemas/ChecklistItem.jsonld-read' + $ref: '#/components/schemas/Checklist.jsonld-read' application/vnd.api+json: schema: - $ref: '#/components/schemas/ChecklistItem.jsonapi' + $ref: '#/components/schemas/Checklist.jsonapi' text/html: schema: - $ref: '#/components/schemas/ChecklistItem-read' - description: 'ChecklistItem resource updated' + $ref: '#/components/schemas/Checklist-read' + description: 'Checklist resource created' links: [] 400: content: @@ -25767,32 +26462,6 @@ paths: $ref: '#/components/schemas/Error' description: 'Invalid input' links: [] - 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: [] - 404: - 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: 'Not found' - links: [] 422: content: application/json: @@ -25806,14 +26475,14 @@ paths: $ref: '#/components/schemas/ConstraintViolation-json' description: 'An error occurred' links: [] - summary: 'Updates the ChecklistItem resource.' + summary: 'Creates a Checklist resource.' tags: - - ChecklistItem - /checklists: + - Checklist + '/checklists/{checklistId}/checklist_items': get: deprecated: false - description: 'Retrieves the collection of Checklist resources.' - operationId: api_checklists_get_collection + description: 'Retrieves the collection of ChecklistItem resources.' + operationId: api_checklists_checklistIdchecklist_items_get_collection parameters: - allowEmptyValue: false @@ -25822,7 +26491,7 @@ paths: description: '' explode: false in: query - name: camp + name: checklist required: false schema: type: string @@ -25834,10 +26503,10 @@ paths: description: '' explode: false in: query - name: isPrototype + name: checklist.camp required: false schema: - type: boolean + type: string style: form - allowEmptyValue: false @@ -25846,7 +26515,7 @@ paths: description: '' explode: true in: query - name: 'camp[]' + name: 'checklist.camp[]' required: false schema: items: @@ -25860,20 +26529,32 @@ paths: description: '' explode: true in: query - name: 'isPrototype[]' + name: 'checklist[]' required: false schema: items: - type: boolean + type: string type: array style: form + - + allowEmptyValue: false + allowReserved: false + deprecated: false + description: 'ChecklistItem identifier' + explode: false + in: path + name: checklistId + required: true + schema: + type: string + style: simple responses: 200: content: application/hal+json: schema: properties: - _embedded: { anyOf: [{ properties: { item: { items: { $ref: '#/components/schemas/Checklist.jsonhal-read' }, type: array } }, type: object }, { type: object }] } + _embedded: { anyOf: [{ properties: { item: { items: { $ref: '#/components/schemas/ChecklistItem.jsonhal-read' }, 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 } @@ -25884,12 +26565,12 @@ paths: application/json: schema: items: - $ref: '#/components/schemas/Checklist-read' + $ref: '#/components/schemas/ChecklistItem-read' type: array application/ld+json: schema: properties: - member: { items: { $ref: '#/components/schemas/Checklist.jsonld-read' }, type: array } + member: { items: { $ref: '#/components/schemas/ChecklistItem.jsonld-read' }, 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 } @@ -25899,14 +26580,103 @@ paths: application/vnd.api+json: schema: items: - $ref: '#/components/schemas/Checklist.jsonapi' + $ref: '#/components/schemas/ChecklistItem.jsonapi' type: array text/html: schema: items: - $ref: '#/components/schemas/Checklist-read' + $ref: '#/components/schemas/ChecklistItem-read' type: array - description: 'Checklist collection' + description: 'ChecklistItem collection' + summary: 'Retrieves the collection of ChecklistItem resources.' + tags: + - ChecklistItem + '/checklists/{id}': + delete: + deprecated: false + description: 'Removes the Checklist resource.' + operationId: api_checklists_id_delete + parameters: + - + allowEmptyValue: false + allowReserved: false + deprecated: false + description: 'Checklist identifier' + explode: false + in: path + name: id + required: true + schema: + type: string + style: simple + responses: + 204: + description: 'Checklist resource deleted' + 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: [] + 404: + 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: 'Not found' + links: [] + summary: 'Removes the Checklist resource.' + tags: + - Checklist + get: + deprecated: false + description: 'Retrieves a Checklist resource.' + operationId: api_checklists_id_get + parameters: + - + allowEmptyValue: false + allowReserved: false + deprecated: false + description: 'Checklist identifier' + explode: false + in: path + name: id + required: true + schema: + type: string + style: simple + responses: + 200: + content: + application/hal+json: + schema: + $ref: '#/components/schemas/Checklist.jsonhal-read' + application/json: + schema: + $ref: '#/components/schemas/Checklist-read' + application/ld+json: + schema: + $ref: '#/components/schemas/Checklist.jsonld-read' + application/vnd.api+json: + schema: + $ref: '#/components/schemas/Checklist.jsonapi' + text/html: + schema: + $ref: '#/components/schemas/Checklist-read' + description: 'Checklist resource' 403: content: application/json: @@ -25918,37 +26688,53 @@ paths: application/problem+json: schema: $ref: '#/components/schemas/Error' - description: Forbidden + description: Forbidden + links: [] + 404: + 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: 'Not found' links: [] - summary: 'Retrieves the collection of Checklist resources.' + summary: 'Retrieves a Checklist resource.' tags: - Checklist - post: + patch: deprecated: false - description: 'Creates a Checklist resource.' - operationId: api_checklists_post - parameters: [] + description: 'Updates the Checklist resource.' + operationId: api_checklists_id_patch + parameters: + - + allowEmptyValue: false + allowReserved: false + deprecated: false + description: 'Checklist identifier' + explode: false + in: path + name: id + required: true + schema: + type: string + style: simple requestBody: content: - application/hal+json: - schema: - $ref: '#/components/schemas/Checklist.jsonhal-write_create' - application/json: - schema: - $ref: '#/components/schemas/Checklist-write_create' - application/ld+json: + application/merge-patch+json: schema: - $ref: '#/components/schemas/Checklist.jsonld-write_create' + $ref: '#/components/schemas/Checklist-write' application/vnd.api+json: schema: $ref: '#/components/schemas/Checklist.jsonapi' - text/html: - schema: - $ref: '#/components/schemas/Checklist-write_create' - description: 'The new Checklist resource' + description: 'The updated Checklist resource' required: true responses: - 201: + 200: content: application/hal+json: schema: @@ -25965,7 +26751,7 @@ paths: text/html: schema: $ref: '#/components/schemas/Checklist-read' - description: 'Checklist resource created' + description: 'Checklist resource updated' links: [] 400: content: @@ -25980,6 +26766,32 @@ paths: $ref: '#/components/schemas/Error' description: 'Invalid input' links: [] + 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: [] + 404: + 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: 'Not found' + links: [] 422: content: application/json: @@ -25993,14 +26805,14 @@ paths: $ref: '#/components/schemas/ConstraintViolation-json' description: 'An error occurred' links: [] - summary: 'Creates a Checklist resource.' + summary: 'Updates the Checklist resource.' tags: - Checklist - '/checklists/{checklistId}/checklist_items': + /comments: get: deprecated: false - description: 'Retrieves the collection of ChecklistItem resources.' - operationId: api_checklists_checklistIdchecklist_items_get_collection + description: 'Retrieves the collection of Comment resources.' + operationId: api_comments_get_collection parameters: - allowEmptyValue: false @@ -26009,7 +26821,7 @@ paths: description: '' explode: false in: query - name: checklist + name: activity required: false schema: type: string @@ -26021,7 +26833,7 @@ paths: description: '' explode: false in: query - name: checklist.camp + name: camp required: false schema: type: string @@ -26033,7 +26845,7 @@ paths: description: '' explode: true in: query - name: 'checklist.camp[]' + name: 'activity[]' required: false schema: items: @@ -26047,32 +26859,20 @@ paths: description: '' explode: true in: query - name: 'checklist[]' + name: 'camp[]' required: false schema: items: type: string type: array style: form - - - allowEmptyValue: false - allowReserved: false - deprecated: false - description: 'ChecklistItem identifier' - explode: false - in: path - name: checklistId - required: true - schema: - type: string - style: simple responses: 200: content: application/hal+json: schema: properties: - _embedded: { anyOf: [{ properties: { item: { items: { $ref: '#/components/schemas/ChecklistItem.jsonhal-read' }, type: array } }, type: object }, { type: object }] } + _embedded: { anyOf: [{ properties: { item: { items: { $ref: '#/components/schemas/Comment.jsonhal-read' }, 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 } @@ -26083,12 +26883,12 @@ paths: application/json: schema: items: - $ref: '#/components/schemas/ChecklistItem-read' + $ref: '#/components/schemas/Comment-read' type: array application/ld+json: schema: properties: - member: { items: { $ref: '#/components/schemas/ChecklistItem.jsonld-read' }, type: array } + member: { items: { $ref: '#/components/schemas/Comment.jsonld-read' }, 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 } @@ -26098,38 +26898,14 @@ paths: application/vnd.api+json: schema: items: - $ref: '#/components/schemas/ChecklistItem.jsonapi' + $ref: '#/components/schemas/Comment.jsonapi' type: array text/html: schema: items: - $ref: '#/components/schemas/ChecklistItem-read' + $ref: '#/components/schemas/Comment-read' type: array - description: 'ChecklistItem collection' - summary: 'Retrieves the collection of ChecklistItem resources.' - tags: - - ChecklistItem - '/checklists/{id}': - delete: - deprecated: false - description: 'Removes the Checklist resource.' - operationId: api_checklists_id_delete - parameters: - - - allowEmptyValue: false - allowReserved: false - deprecated: false - description: 'Checklist identifier' - explode: false - in: path - name: id - required: true - schema: - type: string - style: simple - responses: - 204: - description: 'Checklist resource deleted' + description: 'Comment collection' 403: content: application/json: @@ -26143,7 +26919,54 @@ paths: $ref: '#/components/schemas/Error' description: Forbidden links: [] - 404: + summary: 'Retrieves the collection of Comment resources.' + tags: + - Comment + post: + deprecated: false + description: 'Creates a Comment resource.' + operationId: api_comments_post + parameters: [] + requestBody: + content: + application/hal+json: + schema: + $ref: '#/components/schemas/Comment.jsonhal-create_write' + application/json: + schema: + $ref: '#/components/schemas/Comment-create_write' + application/ld+json: + schema: + $ref: '#/components/schemas/Comment.jsonld-create_write' + application/vnd.api+json: + schema: + $ref: '#/components/schemas/Comment.jsonapi' + text/html: + schema: + $ref: '#/components/schemas/Comment-create_write' + description: 'The new Comment resource' + required: true + responses: + 201: + content: + application/hal+json: + schema: + $ref: '#/components/schemas/Comment.jsonhal-read' + application/json: + schema: + $ref: '#/components/schemas/Comment-read' + application/ld+json: + schema: + $ref: '#/components/schemas/Comment.jsonld-read' + application/vnd.api+json: + schema: + $ref: '#/components/schemas/Comment.jsonapi' + text/html: + schema: + $ref: '#/components/schemas/Comment-read' + description: 'Comment resource created' + links: [] + 400: content: application/json: schema: @@ -26154,21 +26977,35 @@ paths: application/problem+json: schema: $ref: '#/components/schemas/Error' - description: 'Not found' + description: 'Invalid input' links: [] - summary: 'Removes the Checklist resource.' + 422: + content: + application/json: + schema: + $ref: '#/components/schemas/ConstraintViolation-json' + application/ld+json: + schema: + $ref: '#/components/schemas/ConstraintViolation.jsonld-jsonld' + application/problem+json: + schema: + $ref: '#/components/schemas/ConstraintViolation-json' + description: 'An error occurred' + links: [] + summary: 'Creates a Comment resource.' tags: - - Checklist - get: + - Comment + '/comments/{id}': + delete: deprecated: false - description: 'Retrieves a Checklist resource.' - operationId: api_checklists_id_get + description: 'Removes the Comment resource.' + operationId: api_comments_id_delete parameters: - allowEmptyValue: false allowReserved: false deprecated: false - description: 'Checklist identifier' + description: 'Comment identifier' explode: false in: path name: id @@ -26177,24 +27014,8 @@ paths: type: string style: simple responses: - 200: - content: - application/hal+json: - schema: - $ref: '#/components/schemas/Checklist.jsonhal-read' - application/json: - schema: - $ref: '#/components/schemas/Checklist-read' - application/ld+json: - schema: - $ref: '#/components/schemas/Checklist.jsonld-read' - application/vnd.api+json: - schema: - $ref: '#/components/schemas/Checklist.jsonapi' - text/html: - schema: - $ref: '#/components/schemas/Checklist-read' - description: 'Checklist resource' + 204: + description: 'Comment resource deleted' 403: content: application/json: @@ -26221,19 +27042,19 @@ paths: $ref: '#/components/schemas/Error' description: 'Not found' links: [] - summary: 'Retrieves a Checklist resource.' + summary: 'Removes the Comment resource.' tags: - - Checklist - patch: + - Comment + get: deprecated: false - description: 'Updates the Checklist resource.' - operationId: api_checklists_id_patch + description: 'Retrieves a Comment resource.' + operationId: api_comments_id_get parameters: - allowEmptyValue: false allowReserved: false deprecated: false - description: 'Checklist identifier' + description: 'Comment identifier' explode: false in: path name: id @@ -26241,49 +27062,25 @@ paths: schema: type: string style: simple - requestBody: - content: - application/merge-patch+json: - schema: - $ref: '#/components/schemas/Checklist-write' - application/vnd.api+json: - schema: - $ref: '#/components/schemas/Checklist.jsonapi' - description: 'The updated Checklist resource' - required: true responses: 200: content: application/hal+json: schema: - $ref: '#/components/schemas/Checklist.jsonhal-read' + $ref: '#/components/schemas/Comment.jsonhal-read' application/json: schema: - $ref: '#/components/schemas/Checklist-read' + $ref: '#/components/schemas/Comment-read' application/ld+json: schema: - $ref: '#/components/schemas/Checklist.jsonld-read' + $ref: '#/components/schemas/Comment.jsonld-read' application/vnd.api+json: schema: - $ref: '#/components/schemas/Checklist.jsonapi' + $ref: '#/components/schemas/Comment.jsonapi' text/html: schema: - $ref: '#/components/schemas/Checklist-read' - description: 'Checklist resource updated' - links: [] - 400: - 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: 'Invalid input' - links: [] + $ref: '#/components/schemas/Comment-read' + description: 'Comment resource' 403: content: application/json: @@ -26310,22 +27107,9 @@ paths: $ref: '#/components/schemas/Error' description: 'Not found' links: [] - 422: - content: - application/json: - schema: - $ref: '#/components/schemas/ConstraintViolation-json' - application/ld+json: - schema: - $ref: '#/components/schemas/ConstraintViolation.jsonld-jsonld' - application/problem+json: - schema: - $ref: '#/components/schemas/ConstraintViolation-json' - description: 'An error occurred' - links: [] - summary: 'Updates the Checklist resource.' + summary: 'Retrieves a Comment resource.' tags: - - Checklist + - Comment /content_node/checklist_nodes: get: deprecated: false @@ -33632,6 +34416,9 @@ tags: A Checklist Tree-Structure with ChecklistItems. name: Checklist + - + description: 'A Comment.' + name: Comment - description: |- A calendar event in a period of the camp, at which some activity will take place. The start time diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testRootEndpointMatchesSnapshot__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testRootEndpointMatchesSnapshot__1.json index e59673bc3d..612afd0eca 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testRootEndpointMatchesSnapshot__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testRootEndpointMatchesSnapshot__1.json @@ -40,6 +40,10 @@ "href": "/content_node/column_layouts{/id}{?contentType,contentType[],root,root[],camp,period,isRoot}", "templated": true }, + "comments": { + "href": "/comments{/id}{?camp,camp[],activity,activity[]}", + "templated": true + }, "contentNodes": { "href": "/content_nodes{?contentType,contentType[],root,root[],camp,period,isRoot}", "templated": true 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 a36a795d0c..f9958976ce 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/test_EndpointPerformanceTest__testPerformanceDidNotChangeForStableEndpoints__1.yml +++ b/api/tests/Api/SnapshotTests/__snapshots__/test_EndpointPerformanceTest__testPerformanceDidNotChangeForStableEndpoints__1.yml @@ -14,6 +14,8 @@ /checklists/item: 7 /checklist_items: 6 /checklist_items/item: 8 +/comments: 7 +/comments/item: 7 /content_types: 6 /content_types/item: 6 /days: 26