From f26b20310164e6454b77a52a71cf5d84e1f6a7f4 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli Date: Mon, 26 Oct 2026 22:06:54 +0100 Subject: [PATCH] fix: look users up by handle in the who-to-follow API and README tests The users table lost its username column when handles moved to profiles (#1157), but the who-to-follow API (#990) and the README tests (#886) were written against it: the dismiss route bound users by username, and the username filter of the suggestions validated and looked it up on users. They now go through the profile's handle, like the other API routes. The API keeps calling it username, as its user resources do. --- app/Http/Requests/ListApiSuggestedUsersRequest.php | 11 +++++++---- routes/api.php | 2 +- .../Controllers/Api/SuggestedUserControllerTest.php | 10 +++++----- tests/Feature/Controllers/UserControllerTest.php | 6 +++--- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/app/Http/Requests/ListApiSuggestedUsersRequest.php b/app/Http/Requests/ListApiSuggestedUsersRequest.php index 1003d56fc..6a2d49a98 100644 --- a/app/Http/Requests/ListApiSuggestedUsersRequest.php +++ b/app/Http/Requests/ListApiSuggestedUsersRequest.php @@ -4,20 +4,23 @@ namespace App\Http\Requests; +use App\Enums\ProfileType; use App\Models\Commit; use App\Models\User; use App\Rules\VisibleCommit; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Validation\Rule; +use Illuminate\Validation\Rules\Exists; /** * Who to follow may be asked for the page it shows on, as the web asks for it beside a profile or a commit. The commit - * has to be one the user can see, as its page requires. + * has to be one the user can see, as its page requires. The API calls a handle a username, as in its user resources. */ final class ListApiSuggestedUsersRequest extends FormRequest { /** - * @return array> + * @return array> */ public function rules(): array { @@ -26,7 +29,7 @@ public function rules(): array return [ 'round' => ['nullable', 'integer', 'min:0'], - 'username' => ['nullable', 'string', 'exists:users,username'], + 'username' => ['nullable', 'string', Rule::exists('profiles', 'handle')->where('type', ProfileType::User->value)], 'commit_id' => ['bail', 'nullable', 'uuid', 'prohibits:username', new VisibleCommit($user, __('That commit is no longer available.'))], ]; } @@ -37,7 +40,7 @@ public function context(): User|Commit|null assert($user instanceof User); return match (true) { - $this->filled('username') => User::query()->where('username', $this->string('username')->value())->firstOrFail(), + $this->filled('username') => User::query()->whereRelation('profile', 'handle', $this->string('username')->value())->firstOrFail(), $this->filled('commit_id') => Commit::query()->visibleTo($user)->findOrFail($this->string('commit_id')->value()), default => null, }; diff --git a/routes/api.php b/routes/api.php index 7fa00c293..e36e4a655 100644 --- a/routes/api.php +++ b/routes/api.php @@ -185,7 +185,7 @@ // Who to Follow... Route::get('me/suggested-users', [SuggestedUserController::class, 'index'])->name('api-suggested-user.index'); - Route::post('users/{user:username}/dismiss', [SuggestionDismissalController::class, 'store'])->name('api-suggestion-dismissal.store'); + Route::post('users/{user:handle}/dismiss', [SuggestionDismissalController::class, 'store'])->name('api-suggestion-dismissal.store'); // User Mute... Route::get('me/muted', [UserMuteController::class, 'index'])->name('api-user-mute.index'); diff --git a/tests/Feature/Controllers/Api/SuggestedUserControllerTest.php b/tests/Feature/Controllers/Api/SuggestedUserControllerTest.php index 1b0aebcc9..ea4eeb622 100644 --- a/tests/Feature/Controllers/Api/SuggestedUserControllerTest.php +++ b/tests/Feature/Controllers/Api/SuggestedUserControllerTest.php @@ -13,7 +13,7 @@ it('suggests people to follow, with why and whether they follow the user', function (): void { $friend = User::factory()->create(); - $vouched = User::factory()->has(Commit::factory())->create(['username' => 'octocat']); + $vouched = User::factory()->has(Commit::factory())->create(['handle' => 'octocat']); $this->user->following()->attach($friend); $friend->following()->attach($vouched); @@ -108,7 +108,7 @@ $profile->following()->attach($followed); - signedApiRequest($this->keyPair, 'GET', route('api-suggested-user.index', ['username' => $profile->username])) + signedApiRequest($this->keyPair, 'GET', route('api-suggested-user.index', ['username' => $profile->handle])) ->assertOk() ->assertJsonPath('data.*.id', [$followed->id, $regular->id]); }); @@ -144,7 +144,7 @@ $people->last()->followers()->attach($people->take(3)); [$webRoute, $context] = $page === 'profile' - ? [route('user.show', $author), ['username' => $author->username]] + ? [route('user.show', $author), ['username' => $author->handle]] : [route('commit.show', [$author, $root]), ['commit_id' => $root->id]]; $web = null; @@ -188,7 +188,7 @@ $regular = signedApiRequest($this->keyPair, 'GET', route('api-suggested-user.index'))->json('data.*.id'); - signedApiRequest($this->keyPair, 'GET', route('api-suggested-user.index', ['username' => $profile->username])) + signedApiRequest($this->keyPair, 'GET', route('api-suggested-user.index', ['username' => $profile->handle])) ->assertOk() ->assertJsonPath('data.*.id', $regular); })->with([ @@ -247,7 +247,7 @@ 'an unknown username' => fn (): array => [['username' => 'nobody-here'], 'username'], 'an unknown commit' => fn (): array => [['commit_id' => '01a16c57-e371-73fb-a4d7-6ad455c9dd57'], 'commit_id'], 'a commit id that is not one' => fn (): array => [['commit_id' => 'latest'], 'commit_id'], - 'a profile and a commit' => fn (): array => [['username' => User::factory()->create()->username, 'commit_id' => Commit::factory()->create()->id], 'commit_id'], + 'a profile and a commit' => fn (): array => [['username' => User::factory()->create()->handle, 'commit_id' => Commit::factory()->create()->id], 'commit_id'], ]); it('refuses a round that is not a whole number from zero', function (mixed $round): void { diff --git a/tests/Feature/Controllers/UserControllerTest.php b/tests/Feature/Controllers/UserControllerTest.php index fde0e559f..f8b9be053 100644 --- a/tests/Feature/Controllers/UserControllerTest.php +++ b/tests/Feature/Controllers/UserControllerTest.php @@ -704,7 +704,7 @@ }); it('shows the github profile readme on the readme tab, without looking for commits', function (): void { - $user = User::factory()->create(['username' => 'octocat']); + $user = User::factory()->create(['handle' => 'octocat']); Commit::factory()->for($user)->create(); Http::fake([ @@ -720,7 +720,7 @@ }); it('tells the readme tab when github did not answer', function (): void { - $user = User::factory()->create(['username' => 'octocat']); + $user = User::factory()->create(['handle' => 'octocat']); Http::fake(["api.github.com/user/{$user->github_id}" => Http::response(status: 502)]); @@ -730,7 +730,7 @@ it('does not ask github for the readme on the other tabs', function (string $tab): void { Http::preventStrayRequests(); - $user = User::factory()->create(['username' => 'octocat']); + $user = User::factory()->create(['handle' => 'octocat']); $this->get(route('user.show', ['user' => $user, 'tab' => $tab]))->assertOk()->assertInertia(fn ($page) => $page ->where('readme', null));