Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion KNOWLEDGE_BASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ Sources: [group creation](app/Http/Requests/CreateOrgRequest.php), [invitation e

**Mute** someone to remove their commits and replies from your logs, threads, search, and suggestions and stop their notifications. Muting is silent, leaves follows intact, and still lets you visit their profile, which shows a **Muted** badge. Commits you starred stay in Starred.

**Block** someone to stop messaging and interactions such as following, replies, stars, pastéis, and Tom's replies between the two accounts, and hide each other's commits while signed in. Their profile and follower lists become unavailable to you. Blocking removes follows in both directions. Unblocking does not restore those follows. Manage and reverse either choice in Settings → Safety. Blocking is an account-level interaction control, not a way to make public posts inaccessible to signed-out visitors.
**Block** someone to stop messaging and interactions such as following, replies, stars, pastéis, and Tom's replies between the two accounts, and hide each other's commits while signed in. Their profile and follower lists become unavailable to you. Blocking removes follows and stars in both directions, with the notifications for those stars. Unblocking does not restore them. Manage and reverse either choice in Settings → Safety. Blocking is an account-level interaction control, not a way to make public posts inaccessible to signed-out visitors.

Sources: [safety settings](resources/js/pages/safety/edit.tsx), [mute action](app/Actions/CreateUserMute.php), [block action](app/Actions/CreateUserBlock.php), [public routes](routes/web.php).

Expand Down
45 changes: 44 additions & 1 deletion app/Actions/CreateUserBlock.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@

namespace App\Actions;

use App\Enums\NotificationType;
use App\Models\Commit;
use App\Models\Notification;
use App\Models\User;
use Illuminate\Support\Facades\DB;

final readonly class CreateUserBlock
{
public function __construct(private UpdateCommitHotScores $updateCommitHotScores)
{
//
}

/**
* Blocking works both ways, so it also ends the follows in both directions.
* Blocking works both ways, so it also ends the follows and removes the stars in both directions.
*/
public function handle(User $user, User $blocked): void
{
Expand All @@ -19,6 +27,41 @@ public function handle(User $user, User $blocked): void

$user->following()->detach($blocked->id);
$blocked->following()->detach($user->id);

$this->updateCommitHotScores->handle([
...$this->deleteStars($blocked, $user),
...$this->deleteStars($user, $blocked),
]);
});
}

/**
* Removes the stars the stargazer gave the author's commits, with their notifications, so none of them come back
* after an unblock.
*
* @return array<int, string> The ids of the commits that lost a star.
*/
private function deleteStars(User $stargazer, User $author): array
{
$commitIds = $stargazer->starredCommits()
->withTrashed()
->where('commits.user_id', $author->id)
->get(['commits.id'])
->map(fn (Commit $commit): string => $commit->id)
->all();

if ($commitIds === []) {
return [];
}

$stargazer->starredCommits()->detach($commitIds);

Notification::query()
->where('type', NotificationType::Starred)
->where('actor_id', $stargazer->id)
->whereIn('commit_id', $commitIds)
->delete();

return $commitIds;
}
}
57 changes: 57 additions & 0 deletions tests/Unit/Actions/CreateUserBlockTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

declare(strict_types=1);

use App\Actions\CreateCommitStar;
use App\Actions\CreateUserBlock;
use App\Dto\ThreadEngagement;
use App\Models\Commit;
use App\Models\User;
use App\Services\TheAlgorithm;

it('may block a user, removing follows in both directions', function (): void {
$user = User::factory()->create();
Expand Down Expand Up @@ -34,3 +38,56 @@

expect($user->blocking()->count())->toBeOne();
});

it('removes the stars in both directions, with their notifications', function (): void {
$user = User::factory()->create();
$blocked = User::factory()->create();
$friend = User::factory()->create();
$userCommit = Commit::factory()->for($user)->create();
$blockedCommit = Commit::factory()->for($blocked)->create();
$friendCommit = Commit::factory()->for($friend)->create();

resolve(CreateCommitStar::class)->handle($blocked, $userCommit);
resolve(CreateCommitStar::class)->handle($user, $blockedCommit);
resolve(CreateCommitStar::class)->handle($friend, $userCommit);
resolve(CreateCommitStar::class)->handle($user, $friendCommit);
resolve(CreateCommitStar::class)->handle($blocked, $friendCommit);

resolve(CreateUserBlock::class)->handle($user, $blocked);

expect($blocked->hasStarred($userCommit))->toBeFalse()
->and($user->hasStarred($blockedCommit))->toBeFalse()
->and($friend->hasStarred($userCommit))->toBeTrue()
->and($user->hasStarred($friendCommit))->toBeTrue()
->and($blocked->hasStarred($friendCommit))->toBeTrue()
->and($user->notifications()->sole()->actor_id)->toBe($friend->id)
->and($blocked->notifications()->count())->toBe(0)
->and($friend->notifications()->count())->toBe(2);
});

it('removes the stars on deleted commits too', function (): void {
$user = User::factory()->create();
$blocked = User::factory()->create();
$commit = Commit::factory()->for($user)->create();

resolve(CreateCommitStar::class)->handle($blocked, $commit);
$commit->delete();

resolve(CreateUserBlock::class)->handle($user, $blocked);

expect($blocked->starredCommits()->withTrashed()->count())->toBe(0)
->and($user->notifications()->count())->toBe(0);
});

it('lowers the commits that lost a star back', function (): void {
$user = User::factory()->create();
$blocked = User::factory()->create();
$commit = Commit::factory()->for($user)->create();

resolve(CreateCommitStar::class)->handle($blocked, $commit);

resolve(CreateUserBlock::class)->handle($user, $blocked);

expect($commit->fresh()->hot_score)
->toEqualWithDelta(resolve(TheAlgorithm::class)->score($commit->created_at, ThreadEngagement::none()), 1e-9);
});