diff --git a/app/Policies/CommentPolicy.php b/app/Policies/CommentPolicy.php index afa7b5a07..3881cf041 100644 --- a/app/Policies/CommentPolicy.php +++ b/app/Policies/CommentPolicy.php @@ -26,7 +26,7 @@ public function manage(User $user): bool public function view(?User $user, Comment $comment): bool { - return true; + return $user->isNotBanned(); } public function create(User $user, ?Model $commentable = null, ?int $articleType = null): bool diff --git a/app/Policies/UserCommentPolicy.php b/app/Policies/UserCommentPolicy.php index 742f11775..4f2f71d67 100644 --- a/app/Policies/UserCommentPolicy.php +++ b/app/Policies/UserCommentPolicy.php @@ -22,16 +22,14 @@ public function manage(User $user): bool public function viewAny(?User $user, User $commentable): bool { + if (!$commentable->UserWallActive || $commentable->banned_at) { + return false; + } + /* * check guests first */ if (!$user) { - /* - * TODO: check user privacy settings instead of wall_active flag - */ - // $commentable->preferences->wall - // return false; - return true; } @@ -42,12 +40,6 @@ public function viewAny(?User $user, User $commentable): bool return true; } - /* - * TODO: check user privacy settings instead of wall_active flag - */ - // $commentable->preferences->wall - // return false; - return true; } diff --git a/database/factories/CommentFactory.php b/database/factories/CommentFactory.php index 7d881f239..e0213a271 100644 --- a/database/factories/CommentFactory.php +++ b/database/factories/CommentFactory.php @@ -4,8 +4,8 @@ namespace Database\Factories; -use App\Community\Enums\ArticleType; use App\Models\Comment; +use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -17,15 +17,16 @@ class CommentFactory extends Factory public function definition(): array { + $user = User::inRandomOrder()->first(); $isEdited = $this->faker->boolean((1 / 12) * 100); // A one-in-twelve chance of being truthy. return [ 'Payload' => $this->faker->paragraph, 'Submitted' => $this->faker->dateTimeBetween('-1 year', 'now'), 'Edited' => $isEdited ? $this->faker->dateTimeBetween('now', '+1 year') : null, - 'user_id' => 1, - 'ArticleType' => ArticleType::Achievement, - 'ArticleID' => 1, + 'user_id' => $user->ID, + 'ArticleID' => $this->faker->numberBetween(1, 100), + 'ArticleType' => $this->faker->numberBetween(1, 3), ]; } } diff --git a/public/API/API_GetComments.php b/public/API/API_GetComments.php new file mode 100644 index 000000000..4267a0eb3 --- /dev/null +++ b/public/API/API_GetComments.php @@ -0,0 +1,117 @@ +query(); + +$inputIsGameOrAchievement = function () use ($query) { + return isset($query['i']) && is_numeric($query['i']) && intval($query['i']) == $query['i']; +}; + +$rules = [ + 'i' => [ + 'required', + Rule::when(isset($query['t']) && $query['t'] === '3', 'string'), + Rule::when(isset($query['t']) && in_array($query['t'], [1, 2]), 'integer'), + ], + 't' => [ + Rule::requiredIf($inputIsGameOrAchievement()), + 'integer', + ], + 'o' => ['sometimes', 'integer', 'min:0', 'nullable'], + 'c' => ['sometimes', 'integer', 'min:1', 'max:500', 'nullable'], +]; + +$input = Validator::validate(Arr::wrap($query), $rules); + +$offset = $input['o'] ?? 0; +$count = $input['c'] ?? 100; + +$username = null; +$gameOrAchievementId = 0; +$commentType = 0; + +if ($inputIsGameOrAchievement()) { + $gameOrAchievementId = $query['i']; + $commentType = $query['t']; +} else { + $username = $query['i']; + $commentType = 3; +} + +$user = null; +$userPolicy = new UserCommentPolicy(); + +if ($username) { + $user = User::firstWhere('User', $username); + + if (!$user || !$userPolicy->viewAny(null, $user)) { + return response()->json([], 404); + } +} + +$articleId = $user ? $user->ID : $gameOrAchievementId; + +$comments = Comment::withTrashed() + ->with('user') + ->where('ArticleType', $commentType) + ->where('ArticleID', $articleId) + ->whereNull('deleted_at') + ->whereHas('user', function ($query) { + $query->whereNull('banned_at'); + }) + ->offset($offset) + ->limit($count) + ->get(); + +$totalComments = Comment::withTrashed() + ->where('ArticleType', $commentType) + ->where('ArticleID', $articleId) + ->whereNull('deleted_at') + ->whereHas('user', function ($query) { + $query->whereNull('banned_at'); + }) + ->count(); + +$commentPolicy = new CommentPolicy(); + +$results = $comments->filter(function ($nextComment) use ($commentPolicy) { + $user = Auth::user() instanceof User ? Auth::user() : null; + + return $commentPolicy->view($user, $nextComment); +})->map(function ($nextComment) { + return [ + 'User' => $nextComment->user->username, + 'Submitted' => $nextComment->Submitted, + 'CommentText' => $nextComment->Payload, + ]; +}); + +return response()->json([ + 'Count' => $results->count(), + 'Total' => $totalComments, + 'Results' => $results, +]); diff --git a/tests/Feature/Api/V1/CommentsTest.php b/tests/Feature/Api/V1/CommentsTest.php new file mode 100644 index 000000000..6ead80c04 --- /dev/null +++ b/tests/Feature/Api/V1/CommentsTest.php @@ -0,0 +1,235 @@ +get($this->apiUrl('GetComments', ['i' => '1', 't' => 3])) + ->assertJsonMissingValidationErrors([ + 'i', + ]); + + $this->get($this->apiUrl('GetComments', ['i' => 1, 't' => 1])) + ->assertJsonMissingValidationErrors([ + 'i', + 't', + ]); + + $this->get($this->apiUrl('GetComments', ['i' => 'invalid', 't' => 2])) + ->assertJsonValidationErrors(['i']); + + $this->get($this->apiUrl('GetComments', ['i' => 'not-an-integer', 't' => 1])) + ->assertJsonValidationErrors(['i']); + + $this->get($this->apiUrl('GetComments', ['i' => null, 't' => 2])) + ->assertJsonValidationErrors(['i']); + } + + public function testGetCommentsUnknownUser(): void + { + $this->get($this->apiUrl('GetComments', ['i' => 'nonExistant'])) + ->assertNotFound() + ->assertJson([]); + } + + public function testGetCommentsForAchievement(): void + { + // Arrange + $system = System::factory()->create(); + $game = Game::factory()->create(['ConsoleID' => $system->ID]); + $user1 = User::factory()->create(); + $user2 = User::factory()->create(); + $bannedUser = User::factory()->create(['ID' => 309, 'banned_at' => Carbon::now()->subDay()]); + + $achievement = Achievement::factory()->create(['GameID' => $game->ID, 'user_id' => $user1->ID]); + $comment1 = Comment::factory()->create([ + 'ArticleID' => $achievement->ID, + 'ArticleType' => 2, + 'user_id' => $user1->ID, + 'Payload' => 'This is a great achievement!', + ]); + $comment2 = Comment::factory()->create([ + 'ArticleID' => $achievement->ID, + 'ArticleType' => 2, + 'user_id' => $user2->ID, + 'Payload' => 'I agree, this is awesome!', + ]); + $comment3 = Comment::factory()->create([ + 'ArticleID' => $achievement->ID, + 'ArticleType' => 2, + 'user_id' => $bannedUser->ID, + 'Payload' => 'This comment is from a banned user!', + ]); + + // Act + $response = $this->get($this->apiUrl('GetComments', ['i' => $achievement->ID, 't' => 2])) + ->assertSuccessful(); + + // Assert + $response->assertStatus(200); + $response->assertJson([ + 'Count' => 2, + 'Total' => 2, + 'Results' => [ + [ + 'User' => $user1->User, + 'Submitted' => $comment1->Submitted->toISOString(), + 'CommentText' => $comment1->Payload, + ], + [ + 'User' => $user2->User, + 'Submitted' => $comment2->Submitted->toISOString(), + 'CommentText' => $comment2->Payload, + ], + ], + ]); + } + + public function testGetCommentsForGame(): void + { + // Arrange + $system = System::factory()->create(); + $game = Game::factory()->create(['ConsoleID' => $system->ID]); + $user1 = User::factory()->create(); + $user2 = User::factory()->create(); + $bannedUser = User::factory()->create(['banned_at' => Carbon::now()]); + + $comment1 = Comment::factory()->create([ + 'ArticleID' => $game->ID, + 'ArticleType' => 1, + 'user_id' => $user1->ID, + 'Payload' => 'This is a great achievement!', + ]); + $comment2 = Comment::factory()->create([ + 'ArticleID' => $game->ID, + 'ArticleType' => 1, + 'user_id' => $user2->ID, + 'Payload' => 'I agree, this is awesome!', + ]); + $comment3 = Comment::factory()->create([ + 'ArticleID' => $game->ID, + 'ArticleType' => 2, + 'user_id' => $bannedUser->ID, + 'Payload' => 'This comment is from a banned user!', + ]); + $deletedComment = Comment::factory()->create([ + 'ArticleID' => $game->ID, + 'ArticleType' => 2, + 'user_id' => $user1->ID, + 'Payload' => 'This comment has been deleted!', + 'deleted_at' => Carbon::now(), + ]); + + // Act + $response = $this->get($this->apiUrl('GetComments', ['i' => $game->ID, 't' => 1])) + ->assertSuccessful(); + + // Assert + $response->assertStatus(200); + $response->assertJson([ + 'Count' => 2, + 'Total' => 2, + 'Results' => [ + [ + 'User' => $user1->User, + 'Submitted' => $comment1->Submitted->toISOString(), + 'CommentText' => $comment1->Payload, + ], + [ + 'User' => $user2->User, + 'Submitted' => $comment2->Submitted->toISOString(), + 'CommentText' => $comment2->Payload, + ], + ], + ]); + } + + public function testGetCommentsForUser(): void + { + // Arrange + $user = User::factory()->create(); + $user2 = User::factory()->create(); + $bannedUser = User::factory()->create(['banned_at' => Carbon::now()]); + + $comment1 = Comment::factory()->create([ + 'ArticleID' => $user->ID, + 'ArticleType' => 3, + 'user_id' => $user2->ID, + 'Payload' => 'This is my first comment.', + ]); + $comment2 = Comment::factory()->create([ + 'ArticleID' => $user->ID, + 'ArticleType' => 3, + 'user_id' => $user2->ID, + 'Payload' => 'This is my second comment.', + ]); + $comment3 = Comment::factory()->create([ + 'ArticleID' => $user->ID, + 'ArticleType' => 2, + 'user_id' => $bannedUser->ID, + 'Payload' => 'This comment is from a banned user!', + ]); + + // Act + $response = $this->get($this->apiUrl('GetComments', ['i' => $user->User, 't' => 3])) + ->assertSuccessful(); + + // Assert + $response->assertStatus(200); + $response->assertJson([ + 'Count' => 2, + 'Total' => 2, + 'Results' => [ + [ + 'User' => $user2->User, + 'Submitted' => $comment1->Submitted->toISOString(), + 'CommentText' => $comment1->Payload, + ], + [ + 'User' => $user2->User, + 'Submitted' => $comment2->Submitted->toISOString(), + 'CommentText' => $comment2->Payload, + ], + ], + ]); + } + + public function testGetCommentsForUserWithDisabledWall(): void + { + // Arrange + $user = User::factory()->create(['UserWallActive' => false]); + $user2 = User::factory()->create(); + $comment1 = Comment::factory()->create([ + 'ArticleID' => $user->ID, + 'ArticleType' => 3, + 'user_id' => $user2->ID, + 'Payload' => 'This is my first comment.', + ]); + $comment2 = Comment::factory()->create([ + 'ArticleID' => $user->ID, + 'ArticleType' => 3, + 'user_id' => $user2->ID, + 'Payload' => 'This is my second comment.', + ]); + + // Act + $response = $this->get($this->apiUrl('GetComments', ['i' => $user->User])) + ->assertNotFound() + ->assertJson([]); + } +}