Skip to content

Commit

Permalink
Merge branch 'FEAT/laravel-portugal#32/GetAnswersFromAQuestion' of ht…
Browse files Browse the repository at this point in the history
  • Loading branch information
ana-lisboa committed Nov 1, 2020
2 parents 95e010f + c4ca3cb commit 7f9ffb8
Show file tree
Hide file tree
Showing 15 changed files with 887 additions and 737 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ All notable changes to `laravel-portugal/api` will be documented in this file

### Added

- An authenticated user can delete a question (#30)
- A guest or an authenticated user can see details of a question (#48)
- A guest or an authenticated user can list questions (#26)

### Changed

Expand Down
1,165 changes: 436 additions & 729 deletions composer.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion domains/Discussions/Controllers/AnswersStoreController.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public function __construct(Answer $answer, Question $question)
$this->question = $question;
}

public function __invoke(int $questionId, Request $request): Response
public function __invoke(Request $request, int $questionId): Response
{
$this->validate($request, [
'content' => ['required', 'string'],
Expand Down
29 changes: 29 additions & 0 deletions domains/Discussions/Controllers/QuestionsDeleteController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Domains\Discussions\Controllers;

use App\Http\Controllers\Controller;
use Domains\Discussions\Models\Question;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class QuestionsDeleteController extends Controller
{
private Question $questions;

public function __construct(Question $questions)
{
$this->questions = $questions;
}

public function __invoke(Request $request, int $questionId): Response
{
$question = $this->questions->findOrFail($questionId);

$this->authorize('delete', $question);

$question->delete();

return new Response('', Response::HTTP_NO_CONTENT);
}
}
53 changes: 53 additions & 0 deletions domains/Discussions/Controllers/QuestionsIndexController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Domains\Discussions\Controllers;

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

class QuestionsIndexController extends Controller
{
protected Question $question;

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

$this->question = $question;
}

public function __invoke(Request $request): AnonymousResourceCollection
{
$this->validate($request, [
'author' => ['sometimes', 'integer'],
'title' => ['sometimes', 'string'],
'created' => ['sometimes', 'array', 'size:2'],
'created.from' => ['required_with:created', 'date'],
'created.to' => ['required_with:created', 'date', 'afterOrEqual:created.from'],
'resolved' => ['sometimes', 'boolean'],
]);

$question = $this->question
->when($request->input('author'),
fn(Builder $query, int $authorId) => $query->findByAuthorId($authorId))
->when($request->input('title'),
fn(Builder $query, string $title) => $query->findByTitle($title))
->when($request->input('created'),
fn(Builder $query, array $created) => $query->findByCreatedDate([$created['from'], $created['to']]))
->when($request->boolean('resolved'),
fn(Builder $query) => $query->resolved())
->when(!$request->boolean('resolved') && $request->input('resolved') != null,
fn(Builder $query) => $query->nonResolved())
->latest()
->simplePaginate(15);

return QuestionResource::collection($question);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public function __construct(Question $questions)
$this->questions = $questions;
}

public function __invoke(int $questionId, Request $request): Response
public function __invoke(Request $request, int $questionId): Response
{
$question = $this->questions->findOrFail($questionId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public function up(): void
{
Schema::create('questions', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(User::class, 'author_id');
$table->foreignIdFor(User::class, 'author_id')->constrained();
$table->string('title')->index();
$table->string('slug');
$table->text('description');
Expand Down
28 changes: 28 additions & 0 deletions domains/Discussions/Models/Question.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Domains\Discussions\Models;

use Domains\Accounts\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
Expand All @@ -14,6 +15,8 @@ class Question extends Model

protected $fillable = ['title', 'description'];

protected $dates = ['resolved_at'];

public function author(): BelongsTo
{
return $this->belongsTo(User::class)
Expand All @@ -24,4 +27,29 @@ public function answers(): HasMany
{
return $this->hasMany(Answer::class);
}

public function scopeFindByAuthorId(Builder $query, int $term): Builder
{
return $query->where('author_id', $term);
}

public function scopeFindByTitle(Builder $query, string $term): Builder
{
return $query->where('title', 'like', '%'.strtoupper($term).'%');
}

public function scopeFindByCreatedDate(Builder $query, array $term): Builder
{
return $query->whereBetween('created_at', [$term[0], $term[1]]);
}

public function scopeResolved(Builder $query): Builder
{
return $query->whereNotNull('resolved_at');
}

public function scopeNonResolved(Builder $query): Builder
{
return $query->whereNull('resolved_at');
}
}
8 changes: 7 additions & 1 deletion domains/Discussions/Policies/QuestionPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Domains\Discussions\Policies;

use Domains\Accounts\Enums\AccountTypeEnum;
use Domains\Accounts\Models\User;
use Domains\Discussions\Models\Question;
use Illuminate\Auth\Access\HandlesAuthorization;
Expand All @@ -10,8 +11,13 @@ class QuestionPolicy
{
use HandlesAuthorization;

public function update(User $user, Question $question)
public function update(User $user, Question $question): bool
{
return $question->author->is($user);
}

public function delete(User $user, Question $question): bool
{
return $question->author->is($user) || $user->hasRole(AccountTypeEnum::ADMIN);
}
}
1 change: 1 addition & 0 deletions domains/Discussions/Resources/QuestionResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public function toArray($request): array
'author' => UserResource::make($this->author),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'resolved_at' => $this->resolved_at,
'deleted_at' => $this->deleted_at,
];
}
Expand Down
79 changes: 79 additions & 0 deletions domains/Discussions/Tests/Feature/QuestionsDeleteTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

namespace Domains\Discussions\Tests\Feature;

use Domains\Accounts\Database\Factories\UserFactory;
use Domains\Accounts\Enums\AccountTypeEnum;
use Domains\Accounts\Models\User;
use Domains\Discussions\Database\Factories\QuestionFactory;
use Domains\Discussions\Models\Question;
use Faker\Factory;
use Faker\Generator;
use Illuminate\Http\Response;
use Illuminate\Support\Carbon;
use Laravel\Lumen\Testing\DatabaseMigrations;
use Tests\TestCase;

class QuestionsDeleteTest extends TestCase
{
use DatabaseMigrations;

private Generator $faker;
private User $user;
private Question $question;

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

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

Carbon::setTestNow();
}

/** @test */
public function it_soft_deletes_a_question_i_own(): void
{
$response = $this->actingAs($this->user)
->delete(route('discussions.questions.delete', ['questionId' => $this->question->id]));

$this->assertResponseStatus(Response::HTTP_NO_CONTENT);
self::assertTrue($response->response->isEmpty());
$this->seeInDatabase('questions', [
'id' => $this->question->id,
'updated_at' => Carbon::now(),
'deleted_at' => Carbon::now(),
]);
}

/** @test */
public function it_allows_admin_to_soft_delete_another_users_question(): void
{
$response = $this->actingAs(UserFactory::new()->withRole(AccountTypeEnum::ADMIN)->make())
->delete(route('discussions.questions.update', ['questionId' => $this->question->id]));

$this->assertResponseStatus(Response::HTTP_NO_CONTENT);
self::assertTrue($response->response->isEmpty());
$this->seeInDatabase('questions', [
'id' => $this->question->id,
'updated_at' => Carbon::now(),
'deleted_at' => Carbon::now(),
]);
}

/** @test */
public function it_forbids_a_non_admin_to_soft_delete_a_question_he_doesnt_own(): void
{
$this->actingAs(UserFactory::new()->make())
->delete(route('discussions.questions.update', ['questionId' => $this->question->id]));

$this->assertResponseStatus(Response::HTTP_FORBIDDEN);
$this->seeInDatabase('questions', [
'id' => $this->question->id,
'updated_at' => $this->question->updated_at,
'deleted_at' => null,
]);
}
}
Loading

0 comments on commit 7f9ffb8

Please sign in to comment.