Skip to content
This repository was archived by the owner on Jun 29, 2021. It is now read-only.

Commit a62c58a

Browse files
tiagofialexreisJosé Postiga
authored
Resolves #27 - Update question (#41)
* init * Adds Question create feature resolves #25 Removes unnecessary code in AccountsStoreController. * Added '/discussions' prefix As per #25 (comment) * Minor fixes as per review #40 (review) * Cleanup * Added question update * Added tests * Resolves #28, #29: "Update timestamps columns on links and tags tables" (#38) * feat(Tags): add support to timestamps w/timezones - drop regular timestamps columns - create new ones with timestampsTz * feat(Links): add support to timestamps w/timezones added doctrine/dbal dependency * fix: add method return type * fix(Links): add missing timestamp (approved_at) * chore: converted timestampTz to softDeletesTz * feat(links): added approved_at datetime cast * fix: replaced dropColumn with change method * chore(Changelog): add latest updates timestamps columns (on links and tags) changed to datetime w/timezone * Resolves #25 - An authenticated user can post a question (#40) * init * Adds Question create feature resolves #25 Removes unnecessary code in AccountsStoreController. * Added '/discussions' prefix As per #25 (comment) * Minor fixes as per review #40 (review) * Cleanup * Resolve conflicts + test tweaks * Code cleanup + minor improvements * minor tweaks * final tweaks/cleanup * style(Discussions): minor code style fixes * ci(PHPUnit): add blade files to the code coverage exclusion list Co-authored-by: Alexandre Reis <[email protected]> Co-authored-by: José Postiga <[email protected]>
1 parent 7cf9eb1 commit a62c58a

File tree

8 files changed

+186
-10
lines changed

8 files changed

+186
-10
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ All notable changes to `laravel-portugal/api` will be documented in this file
1010
- A guest should be able to login and logout (#37)
1111
- Add account types and permissions (#42)
1212
- An authenticated user can post an answer to a question (#31)
13+
- An authenticated user can update a question (#27)
1314

1415
### Changed
1516

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Domains\Discussions\Controllers;
4+
5+
use App\Http\Controllers\Controller;
6+
use Domains\Discussions\Models\Question;
7+
use Illuminate\Http\Request;
8+
use Illuminate\Http\Response;
9+
10+
class QuestionsUpdateController extends Controller
11+
{
12+
private Question $questions;
13+
14+
public function __construct(Question $questions)
15+
{
16+
$this->questions = $questions;
17+
}
18+
19+
public function __invoke(int $questionId, Request $request): Response
20+
{
21+
$question = $this->questions->findOrFail($questionId);
22+
23+
$this->authorize('update', $question);
24+
25+
$this->validate($request, [
26+
'title' => ['required', 'string', 'max:255'],
27+
'description' => ['nullable', 'string'],
28+
]);
29+
30+
$question->update([
31+
'title' => $request->input('title'),
32+
'description' => $request->input('description', $question->description),
33+
]);
34+
35+
return new Response('', Response::HTTP_NO_CONTENT);
36+
}
37+
}

domains/Discussions/DiscussionsServiceProvider.php

+8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use Domains\Discussions\Models\Question;
66
use Domains\Discussions\Observers\QuestionObserver;
7+
use Domains\Discussions\Policies\QuestionPolicy;
8+
use Illuminate\Support\Facades\Gate;
79
use Illuminate\Support\Facades\Route;
810
use Illuminate\Support\ServiceProvider;
911

@@ -14,6 +16,7 @@ public function boot(): void
1416
$this->loadMigrationsFrom(__DIR__ . '/Database/Migrations');
1517
$this->bootRoutes();
1618
$this->bootObservers();
19+
$this->bootPolicies();
1720
}
1821

1922
private function bootRoutes(): void
@@ -31,4 +34,9 @@ private function bootObservers(): void
3134
{
3235
Question::observe(QuestionObserver::class);
3336
}
37+
38+
private function bootPolicies(): void
39+
{
40+
Gate::policy(Question::class, QuestionPolicy::class);
41+
}
3442
}

domains/Discussions/Models/Question.php

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ class Question extends Model
1111
{
1212
use SoftDeletes;
1313

14+
protected $fillable = ['title', 'description'];
15+
1416
public function author(): BelongsTo
1517
{
1618
return $this->belongsTo(User::class)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Domains\Discussions\Policies;
4+
5+
use Domains\Accounts\Models\User;
6+
use Domains\Discussions\Models\Question;
7+
use Illuminate\Auth\Access\HandlesAuthorization;
8+
9+
class QuestionPolicy
10+
{
11+
use HandlesAuthorization;
12+
13+
public function update(User $user, Question $question)
14+
{
15+
return $question->author->is($user);
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
namespace Domains\Discussions\Tests\Feature;
4+
5+
use Domains\Accounts\Database\Factories\UserFactory;
6+
use Domains\Accounts\Models\User;
7+
use Domains\Discussions\Database\Factories\QuestionFactory;
8+
use Domains\Discussions\Models\Question;
9+
use Faker\Factory;
10+
use Faker\Generator;
11+
use Illuminate\Http\Response;
12+
use Illuminate\Support\Carbon;
13+
use Illuminate\Support\Str;
14+
use Laravel\Lumen\Testing\DatabaseMigrations;
15+
use Tests\TestCase;
16+
17+
class QuestionsUpdateTest extends TestCase
18+
{
19+
use DatabaseMigrations;
20+
21+
private Generator $faker;
22+
private User $user;
23+
private Question $question;
24+
25+
protected function setUp(): void
26+
{
27+
parent::setUp();
28+
29+
$this->faker = Factory::create();
30+
$this->user = UserFactory::new()->create();
31+
$this->question = QuestionFactory::new(['author_id' => $this->user->id])->create();
32+
}
33+
34+
/** @test */
35+
public function it_updates_questions(): void
36+
{
37+
Carbon::setTestNow();
38+
39+
$payload = [
40+
'title' => $this->faker->title,
41+
'description' => $this->faker->paragraph,
42+
];
43+
44+
$response = $this->actingAs($this->user)
45+
->call(
46+
'PATCH',
47+
route('discussions.questions.update', ['questionId' => $this->question->id]),
48+
$payload
49+
);
50+
51+
$this->assertResponseStatus(Response::HTTP_NO_CONTENT);
52+
53+
self::assertTrue($response->isEmpty());
54+
55+
$this->seeInDatabase('questions', [
56+
'id' => $this->question->id,
57+
'author_id' => $this->user->id,
58+
'title' => $payload['title'],
59+
'slug' => Str::slug($payload['title']),
60+
'description' => $payload['description'],
61+
'updated_at' => Carbon::now(),
62+
]);
63+
}
64+
65+
/** @test */
66+
public function it_fails_to_update_if_title_is_missing(): void
67+
{
68+
$this->actingAs($this->user)
69+
->patch(route('discussions.questions.update', ['questionId' => $this->question->id]))
70+
->seeJsonStructure([
71+
'title',
72+
]);
73+
}
74+
75+
/** @test */
76+
public function it_keeps_previous_description_if_none_is_sent(): void
77+
{
78+
$response = $this->actingAs($this->user)
79+
->call(
80+
'PATCH',
81+
route('discussions.questions.update', ['questionId' => $this->question->id]),
82+
[
83+
'title' => $this->faker->title,
84+
]
85+
);
86+
87+
self::assertTrue($response->isEmpty());
88+
self::assertEquals($this->question->description, $this->question->refresh()->description);
89+
}
90+
91+
/** @test */
92+
public function it_forbids_non_owner_to_update_questions(): void
93+
{
94+
$this->actingAs(UserFactory::new()->make())
95+
->patch(
96+
route('discussions.questions.update', ['questionId' => $this->question->id]),
97+
[
98+
'title' => $this->faker->title,
99+
]
100+
)
101+
->assertResponseStatus(Response::HTTP_FORBIDDEN);
102+
}
103+
}

domains/Discussions/routes.php

+17-10
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,23 @@
22

33
use Domains\Discussions\Controllers\AnswersStoreController;
44
use Domains\Discussions\Controllers\QuestionsStoreController;
5+
use Domains\Discussions\Controllers\QuestionsUpdateController;
56
use Illuminate\Support\Facades\Route;
67

7-
Route::post('/questions', [
8-
'as' => 'questions.store',
9-
'middleware' => 'auth',
10-
'uses' => QuestionsStoreController::class,
11-
]);
8+
Route::group(['middleware' => 'auth'], function () {
9+
Route::post('/questions', [
10+
'as' => 'questions.store',
11+
'uses' => QuestionsStoreController::class,
12+
]);
13+
14+
Route::patch('/questions/{questionId}', [
15+
'as' => 'questions.update',
16+
'uses' => QuestionsUpdateController::class,
17+
]);
18+
19+
Route::post('/questions/{questionId}/answers', [
20+
'as' => 'questions.answers',
21+
'uses' => AnswersStoreController::class
22+
]);
23+
});
1224

13-
Route::post('/questions/{questionId}/answers', [
14-
'as' => 'questions.answers',
15-
'middleware' => 'auth',
16-
'uses' => AnswersStoreController::class
17-
]);

phpunit.xml

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<directory suffix=".php">./domains/*/Database</directory>
1414
<directory suffix=".php">./domains/*/Routes</directory>
1515
<directory suffix=".php">./domains/*/Tests</directory>
16+
<directory suffix="blade.php">./domains/*/Resources</directory>
1617
<directory suffix=".php">./tests</directory>
1718
</exclude>
1819
</coverage>

0 commit comments

Comments
 (0)