-
Notifications
You must be signed in to change notification settings - Fork 11
Added user blocking functionality #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace App\Actions; | ||
|
|
||
| use App\Models\BlockedUser; | ||
| use App\Models\User; | ||
|
|
||
| final class BlockUser | ||
| { | ||
| public function handle(User $user, User $toBlock): void | ||
| { | ||
| BlockedUser::query()->firstOrCreate([ | ||
| 'user_id' => $user->id, | ||
| 'blocked_user_id' => $toBlock->id, | ||
| ]); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace App\Actions; | ||
|
|
||
| use App\Models\BlockedUser; | ||
| use App\Models\User; | ||
|
|
||
| final class UnBlockUser | ||
| { | ||
| public function handle(User $user, User $toBlock): void | ||
| { | ||
|
|
||
| BlockedUser::query() | ||
| ->where([ | ||
| 'user_id' => $user->id, | ||
| 'blocked_user_id' => $toBlock->id, | ||
| ]) | ||
| ->delete(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace App\Http\Controllers; | ||
|
|
||
| use App\Actions\BlockUser; | ||
| use App\Actions\UnBlockUser; | ||
| use App\Models\User; | ||
| use Illuminate\Container\Attributes\CurrentUser; | ||
| use Illuminate\Http\Response; | ||
|
|
||
| final class BlockController | ||
| { | ||
| public function store( | ||
| #[CurrentUser] User $loggedInUser, | ||
| User $user, | ||
| BlockUser $action | ||
| ): Response { | ||
| $action->handle($loggedInUser, $user); | ||
|
|
||
| return response(status: 201); | ||
|
|
||
| } | ||
|
|
||
| public function destroy( | ||
| #[CurrentUser] User $loggedInUser, | ||
| User $user, | ||
| UnBlockUser $action | ||
| ): Response { | ||
| $action->handle($loggedInUser, $user); | ||
|
|
||
| return response(status: 204); | ||
|
|
||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,28 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <?php | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| declare(strict_types=1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace App\Models; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use Database\Factories\BlockedUserFactory; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use Illuminate\Database\Eloquent\Model; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use Illuminate\Database\Eloquent\Relations\BelongsTo; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final class BlockedUser extends Model | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** @use HasFactory<BlockedUserFactory> */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use HasFactory; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** @return BelongsTo<User, $this> */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public function user(): BelongsTo | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return $this->belongsTo(User::class); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** @return BelongsTo<User, $this> */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public function blockedUser(): BelongsTo | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return $this->belongsTo(User::class, 'blocked_user_id'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+12
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add $fillable to prevent MassAssignmentException. The model lacks Apply this diff: final class BlockedUser extends Model
{
/** @use HasFactory<BlockedUserFactory> */
use HasFactory;
+ /**
+ * @var list<string>
+ */
+ protected $fillable = [
+ 'user_id',
+ 'blocked_user_id',
+ ];
+
/** @return BelongsTo<User, $this> */
public function user(): BelongsTo📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Model::unguard(); already at AppServiceProvider. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: Length of output: 28 @hasansoyalan, you're absolutely right! I've confirmed that I apologize for the false positive—the model works correctly as-is with your project's configuration. Thank you for the clarification! 🐰 ✏️ Learnings added
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Database\Factories; | ||
|
|
||
| use App\Models\BlockedUser; | ||
| use App\Models\User; | ||
| use Illuminate\Database\Eloquent\Factories\Factory; | ||
| use Illuminate\Support\Carbon; | ||
|
|
||
| /** | ||
| * @extends Factory<BlockedUser> | ||
| */ | ||
| final class BlockedUserFactory extends Factory | ||
| { | ||
| protected $model = BlockedUser::class; | ||
|
|
||
| public function definition(): array | ||
| { | ||
| return [ | ||
| 'created_at' => Carbon::now(), | ||
| 'updated_at' => Carbon::now(), | ||
| 'user_id' => User::factory(), | ||
| 'blocked_user_id' => User::factory(), | ||
| ]; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| use Illuminate\Database\Migrations\Migration; | ||
| use Illuminate\Database\Schema\Blueprint; | ||
| use Illuminate\Support\Facades\Schema; | ||
|
|
||
| return new class extends Migration | ||
| { | ||
| public function up(): void | ||
| { | ||
| Schema::create('blocked_users', function (Blueprint $table): void { | ||
| $table->id(); | ||
| $table->foreignId('user_id')->constrained(); | ||
| $table->foreignId('blocked_user_id')->constrained('users'); | ||
| $table->unique(['user_id', 'blocked_user_id']); | ||
| $table->timestamps(); | ||
| }); | ||
| } | ||
|
|
||
| public function down(): void | ||
| { | ||
| Schema::dropIfExists('blocked_users'); | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| use App\Models\User; | ||
| use Laravel\Sanctum\Sanctum; | ||
|
|
||
| it('may block a user', function () { | ||
| $user = User::factory()->create(); | ||
| $targetUser = User::factory()->create(); | ||
|
|
||
| Sanctum::actingAs($user, ['*']); | ||
|
|
||
| $response = $this->postJson(route('blocks.store', $targetUser)); | ||
|
|
||
| $response->assertStatus(201); | ||
|
|
||
| expect($user->blockedUsers()->count())->toBe(1); | ||
| }); | ||
|
|
||
| it('may unblock a user', function () { | ||
| $user = User::factory()->create(); | ||
| $targetUser = User::factory()->create(); | ||
|
|
||
| App\Models\BlockedUser::factory()->create([ | ||
| 'user_id' => $user->id, | ||
| 'blocked_user_id' => $targetUser->id, | ||
| ]); | ||
| expect($user->blockedUsers()->count())->toBe(1); | ||
|
|
||
| Sanctum::actingAs($user, ['*']); | ||
|
|
||
| $response = $this->deleteJson(route('blocks.destroy', $targetUser)); | ||
|
|
||
| $response->assertStatus(204); | ||
|
|
||
| expect($user->refresh()->blockedUsers()->count())->toBe(0); | ||
| }); | ||
|
|
||
| it('cannot block a user twice', function () { | ||
| $user = User::factory()->create(); | ||
| $targetUser = User::factory()->create(); | ||
|
|
||
| App\Models\BlockedUser::factory()->create([ | ||
| 'user_id' => $user->id, | ||
| 'blocked_user_id' => $targetUser->id, | ||
| ]); | ||
| Sanctum::actingAs($user, ['*']); | ||
|
|
||
| $response = $this->postJson(route('blocks.store', $targetUser)); | ||
|
|
||
| $response->assertStatus(201); | ||
|
|
||
| expect($user->blockedUsers()->count())->toBe(1); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| use App\Models\User; | ||
|
|
||
| it('may block a user', function (): void { | ||
| $user = User::factory()->create(); | ||
| $targetUser = User::factory()->create(); | ||
| $action = app(App\Actions\BlockUser::class); | ||
|
|
||
| $action->handle($user, $targetUser); | ||
|
|
||
| expect($user->blockedUsers()->count())->toBe(1) | ||
| ->and($targetUser->blockedByUsers()->count())->toBe(1); | ||
| }); | ||
|
|
||
| test('re-block a user does not duplicate the block', function (): void { | ||
| $user = User::factory()->create(); | ||
| $targetUser = User::factory()->create(); | ||
| $action = app(App\Actions\BlockUser::class); | ||
|
|
||
| $action->handle($user, $targetUser); | ||
|
|
||
| expect($user->blockedUsers()->count())->toBe(1) | ||
| ->and($targetUser->blockedByUsers()->count())->toBe(1); | ||
|
|
||
| $action->handle($user, $targetUser); | ||
|
|
||
| expect($user->blockedUsers()->count())->toBe(1) | ||
| ->and($targetUser->blockedByUsers()->count())->toBe(1); | ||
| }); | ||
|
|
||
| test('block users models returns correct data', function (): void { | ||
| $user = User::factory()->create(); | ||
| $targetUser = User::factory()->create(); | ||
| $action = app(App\Actions\BlockUser::class); | ||
| $action->handle($user, $targetUser); | ||
| $blockedUser = App\Models\BlockedUser::first(); | ||
| expect($blockedUser->user->id)->toEqual($user->id); | ||
| expect($blockedUser->blockedUser->id)->toEqual($targetUser->id); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| use App\Models\User; | ||
|
|
||
| it('may unblock a user', function (): void { | ||
| $user = User::factory()->create(); | ||
| $targetUser = User::factory()->create(); | ||
| app(App\Actions\BlockUser::class)->handle($user, $targetUser); | ||
| $action = app(App\Actions\UnBlockUser::class); | ||
|
|
||
| expect($user->blockedUsers()->count())->toBe(1) | ||
| ->and($targetUser->blockedByUsers()->count())->toBe(1); | ||
| $action->handle($user, $targetUser); | ||
|
|
||
| expect($user->blockedUsers()->count())->toBe(0) | ||
| ->and($targetUser->blockedByUsers()->count())->toBe(0); | ||
|
|
||
| }); | ||
|
|
||
| test('re-unblock does nothing', function (): void { | ||
| $user = User::factory()->create(); | ||
| $targetUser = User::factory()->create(); | ||
| $action = app(App\Actions\UnBlockUser::class); | ||
|
|
||
| expect($user->blockedUsers()->count())->toBe(0); | ||
|
|
||
| $action->handle($user, $targetUser); | ||
|
|
||
| expect($user->blockedUsers()->count())->toBe(0); | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.