Skip to content
This repository was archived by the owner on Jun 29, 2021. It is now read-only.
Merged
47 changes: 47 additions & 0 deletions domains/Discussions/Controllers/AnswersIndexController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace Domains\Discussions\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Domains\Discussions\Models\Question;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Contracts\Auth\Factory as Auth;
use Domains\Discussions\Resources\AnswerResource;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;

class AnswersIndexController extends Controller
{
private Question $question;

public function __construct(Auth $auth, Question $question)
{
if ($auth->guest()) {
$this->middleware('throttle:30,1');
}

$this->question = $question;
}

public function __invoke(Request $request, int $questionId): AnonymousResourceCollection
{
$this->validate($request, [
'author' => ['sometimes', 'integer', 'exists:users,id'],
'created' => ['sometimes', 'array', 'size:2'],
'created.from' => ['required_with:created', 'date'],
'created.to' => ['required_with:created', 'date', 'afterOrEqual:created.from']
]);

$answers = $this->question
->findOrFail($questionId)
->answers()
->when($authorId = $request->input('author'),
static fn(Builder $answers, int $authorId) => $answers->whereAuthorId($authorId))
->when($created = $request->input('created'),
static fn(Builder $answers, array $created) => $answers->whereBetween('created_at', [$created['from'], $created['to']]))
->latest()
->simplePaginate(15);

return AnswerResource::collection($answers);
}
}
6 changes: 6 additions & 0 deletions domains/Discussions/Models/Question.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;

class Question extends Model
Expand All @@ -22,6 +23,11 @@ public function author(): BelongsTo
->withTrashed();
}

public function answers(): HasMany
{
return $this->hasMany(Answer::class);
}

public function scopeFindByAuthorId(Builder $query, int $term): Builder
{
return $query->where('author_id', $term);
Expand Down
22 changes: 22 additions & 0 deletions domains/Discussions/Resources/AnswerResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Domains\Discussions\Resources;

use Domains\Accounts\Resources\UserResource;
use Illuminate\Http\Resources\Json\JsonResource;

class AnswerResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'content' => $this->content,
'question' => QuestionResource::make($this->question),
'author' => UserResource::make($this->author),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'deleted_at' => $this->deleted_at,
];
}
}
174 changes: 174 additions & 0 deletions domains/Discussions/Tests/Feature/AnswersIndexControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
<?php

namespace Domains\Discussions\Tests\Feature;

use Carbon\Carbon;
use Illuminate\Http\Response;
use Tests\TestCase;
use Domains\Accounts\Models\User;
use Domains\Discussions\Models\Answer;
use Domains\Discussions\Models\Question;
use Laravel\Lumen\Testing\DatabaseMigrations;
use Domains\Accounts\Database\Factories\UserFactory;
use Domains\Discussions\Database\Factories\AnswerFactory;
use Domains\Discussions\Database\Factories\QuestionFactory;

class AnswersIndexControllerTest extends TestCase
{
use DatabaseMigrations;

private User $user;
private User $secondUser;
private Question $question;
private Answer $answer;
private Answer $secondAnswer;

protected function setUp(): void
{
parent::setUp();

$this->user = UserFactory::new()->create();
$this->secondUser = UserFactory::new()->create();

$this->question = QuestionFactory::new([
'author_id' => $this->user->id
])->create();

$this->answer = AnswerFactory::new([
'question_id' => $this->question->id,
'author_id' => $this->user->id,
'created_at' => Carbon::now()->subWeek()->toDateTimeString()
])->create();

$this->secondAnswer = AnswerFactory::new([
'question_id' => $this->question->id,
'author_id' => $this->secondUser->id,
'created_at' => Carbon::now()->toDateTimeString()
])->create();
}

/** @test */
public function it_gets_paginated_answers_for_a_question(): void
{
$this->get(route('discussions.questions.answersList', ['questionId' => $this->question->id]))
->seeJsonStructure([
"data" => [
0 => [
"id",
"content",
"question" => [
"id",
"title",
"slug",
"description",
"author" => [
"id",
"name",
"email",
"trusted",
"created_at",
"updated_at",
"deleted_at",
],
"created_at",
"updated_at",
"resolved_at",
"deleted_at",
],
"author" => [
"id",
"name",
"email",
"trusted",
"created_at",
"updated_at",
"deleted_at",
],
"created_at",
"updated_at",
"deleted_at",
]
]
])
// answer 1
->seeJsonContains(['id' => $this->answer->id])
->seeJsonContains(['content' => $this->answer->content])
// answer 2
->seeJsonContains(['id' => $this->secondAnswer->id])
->seeJsonContains(['content' => $this->secondAnswer->content]);
}

/** @test */
public function it_gets_paginated_answers_for_a_question_from_a_particular_author(): void
{
$this->get(route('discussions.questions.answersList', ['questionId' => $this->question->id, 'author' => $this->user->id]))
->seeJson(['id' => $this->answer->id])
->dontSeeJson(['id' => $this->secondAnswer->id]);
}

/** @test */
public function it_gets_paginated_answers_for_a_question_from_a_particular_time_frame(): void
{
$aWeekAgo = Carbon::now()->subDays(8);
$yesterday = Carbon::yesterday();

$this->get(route('discussions.questions.answersList', ['questionId' => $this->question->id]) . '?created[from]=' . $aWeekAgo->format('Y-m-d') . '&created[to]=' . $yesterday->format('Y-m-d'))
// answer 1
->seeJsonContains(['id' => $this->answer->id])
->seeJsonContains(['content' => $this->answer->content])
// answer 2
->seeJsonDoesntContains([
"id" => $this->secondAnswer->id
]);
}

/** @test */
public function it_gets_paginated_answers_for_a_question_from_a_particular_time_frame_and_user(): void
{
$thirdAnswer = AnswerFactory::new([
'question_id' => $this->question->id,
'author_id' => $this->user->id,
'created_at' => Carbon::now()->subWeek()->toDateTimeString()
])->create();

$aWeekAgo = Carbon::now()->subDays(8);
$yesterday = Carbon::yesterday();

$this->get(route('discussions.questions.answersList', ['questionId' => $this->question->id]) . '?created[from]=' . $aWeekAgo->format('Y-m-d') . '&created[to]=' . $yesterday->format('Y-m-d') . '&author=1')
// answer 1
->seeJsonContains(['id' => $this->answer->id])
->seeJsonContains(['content' => $this->answer->content])
// answer 3
->seeJsonContains(['id' => $thirdAnswer->id])
->seeJsonContains(['content' => $thirdAnswer->content])
// answer 2
->dontSeeJson([
"id" => $this->secondAnswer->id
]);
}

/** @test */
public function it_blocks_guest_for_many_attempts(): void
{
for ($attempt = 0; $attempt < 30; ++$attempt) {
$this->get(route('discussions.questions.answersList', ['questionId' => $this->question->id]))
->assertResponseStatus(Response::HTTP_OK);
}

$this->get(route('discussions.questions.answersList', ['questionId' => $this->question->id]))
->assertResponseStatus(Response::HTTP_TOO_MANY_REQUESTS);
}

/** @test */
public function it_not_blocks_authenticated_user_for_many_attempts(): void
{
$this->actingAs($this->user);

for ($attempt = 0; $attempt < 30; ++$attempt) {
$this->get(route('discussions.questions.answersList', ['questionId' => $this->question->id]));
}

$this->get(route('discussions.questions.answersList', ['questionId' => $this->question->id]))
->assertResponseStatus(Response::HTTP_OK);
}
}
6 changes: 6 additions & 0 deletions domains/Discussions/routes.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use Domains\Discussions\Controllers\AnswersStoreController;
use Domains\Discussions\Controllers\AnswersIndexController;
use Domains\Discussions\Controllers\QuestionsDeleteController;
use Domains\Discussions\Controllers\QuestionsIndexController;
use Domains\Discussions\Controllers\QuestionsStoreController;
Expand Down Expand Up @@ -39,3 +40,8 @@
'as' => 'questions.view',
'uses' => QuestionsViewController::class,
]);

Route::get('/questions/{questionId}/answers', [
'as' => 'questions.answersList',
'uses' => AnswersIndexController::class
]);