diff --git a/KNOWLEDGE_BASE.md b/KNOWLEDGE_BASE.md index 97b28096d..0f2940300 100644 --- a/KNOWLEDGE_BASE.md +++ b/KNOWLEDGE_BASE.md @@ -482,7 +482,7 @@ An app can use the same `/api/v1` for you with signed requests. It keeps an SSH A signed request sees what the web shows you, such as your own pending commits. It can also, with the web's validation, moderation, and rate limits: -- read your account, logs, replies, notifications (including grouped activity), and starred commits; +- read your account, logs, replies (50 a page, or fewer with `limit`), notifications (including grouped activity), and starred commits; - commit, reply, and quote, with images, and delete your commits; - preview commit drafts without publishing them; - follow or unfollow, mute or unmute, and block or unblock, and list who you muted and blocked; diff --git a/app/Http/Controllers/Api/CommitReplyController.php b/app/Http/Controllers/Api/CommitReplyController.php index 7506a4a07..e39486c86 100644 --- a/app/Http/Controllers/Api/CommitReplyController.php +++ b/app/Http/Controllers/Api/CommitReplyController.php @@ -16,6 +16,6 @@ { public function index(ListApiCommitRepliesRequest $request, #[CurrentUser] User $user, Commit $commit, CommitRepliesQuery $replies): AnonymousResourceCollection { - return CommitResource::collection($replies->paginate($commit, $user, $request->boolean('newest'))->withQueryString()); + return CommitResource::collection($replies->paginate($commit, $user, $request->boolean('newest'), $request->integer('limit', CommitRepliesQuery::PER_PAGE))->withQueryString()); } } diff --git a/app/Http/Requests/ListApiCommitRepliesRequest.php b/app/Http/Requests/ListApiCommitRepliesRequest.php index 359a8f9b0..f1a6312dd 100644 --- a/app/Http/Requests/ListApiCommitRepliesRequest.php +++ b/app/Http/Requests/ListApiCommitRepliesRequest.php @@ -26,7 +26,8 @@ public function authorize(): Response } /** - * Newest first continues the next_cursor of a reply preview, as on the web. + * Newest first continues the next_cursor of a reply preview, as on the web, and a limit asks for fewer replies a + * page, as the web's threads do. * * @return array> */ @@ -34,6 +35,7 @@ public function rules(): array { return [ 'newest' => ['sometimes', 'boolean'], + 'limit' => ['sometimes', 'integer', 'between:1,50'], ]; } } diff --git a/tests/Feature/Controllers/Api/CommitReplyControllerTest.php b/tests/Feature/Controllers/Api/CommitReplyControllerTest.php index adfc109fc..f4336b4c3 100644 --- a/tests/Feature/Controllers/Api/CommitReplyControllerTest.php +++ b/tests/Feature/Controllers/Api/CommitReplyControllerTest.php @@ -141,6 +141,31 @@ ->assertJsonPath('data.*.body', ['Reply 1', 'Reply 0']); }); +it('lists fewer replies a page when asked, as the web does, and keeps asking on the next page', function (): void { + $parent = Commit::factory()->create(); + + Commit::factory()->for($parent, 'parent')->count(12) + ->state(new Sequence(fn (Sequence $sequence): array => [ + 'body' => "Reply {$sequence->index}", + 'created_at' => now()->subMinutes(100 - $sequence->index), + ])) + ->create(); + + $first = signedApiRequest($this->keyPair, 'GET', route('api-commit-reply.index', [$parent, 'limit' => 5])); + + $first->assertOk()->assertJsonPath('data.*.body', ['Reply 0', 'Reply 1', 'Reply 2', 'Reply 3', 'Reply 4']); + + signedApiRequest($this->keyPair, 'GET', (string) $first->json('links.next')) + ->assertOk() + ->assertJsonPath('data.*.body', ['Reply 5', 'Reply 6', 'Reply 7', 'Reply 8', 'Reply 9']); +}); + +it('rejects a page of replies outside 1 to 50', function (int $limit): void { + signedApiRequest($this->keyPair, 'GET', route('api-commit-reply.index', [Commit::factory()->create(), 'limit' => $limit])) + ->assertUnprocessable() + ->assertJsonValidationErrors('limit'); +})->with([0, 51]); + it('hides the replies of commits the user may not see', function (): void { $parent = Commit::factory()->pending()->create(); Commit::factory()->for($parent, 'parent')->create();