From c6b94aa1f11cadfdd12fc887bb5858ed1092b6f4 Mon Sep 17 00:00:00 2001 From: Romulus21 Date: Sun, 19 Apr 2020 22:46:28 +0200 Subject: [PATCH] first work on To Do Lists --- app/Http/Controllers/ToDoController.php | 41 +++++ app/Http/Controllers/ToDoListController.php | 55 ++++++ app/Http/Resources/ToDo.php | 35 ++++ app/Http/Resources/ToDoCollection.php | 22 +++ app/Http/Resources/ToDoList.php | 38 ++++ app/Models/ToDo.php | 18 ++ app/Models/ToDoList.php | 28 +++ app/Models/User.php | 6 + app/Policies/ToDoListPolicy.php | 94 ++++++++++ app/Providers/AuthServiceProvider.php | 1 + database/factories/ToDoListFactory.php | 13 ++ ..._04_19_144900_create_to_do_lists_table.php | 33 ++++ .../2020_04_19_190733_create_to_dos_table.php | 35 ++++ routes/api.php | 2 + tests/Feature/MemosTest.php | 4 +- tests/Feature/ToDoItemsTest.php | 124 +++++++++++++ tests/Feature/ToDoListsTest.php | 163 ++++++++++++++++++ 17 files changed, 710 insertions(+), 2 deletions(-) create mode 100644 app/Http/Controllers/ToDoController.php create mode 100644 app/Http/Controllers/ToDoListController.php create mode 100644 app/Http/Resources/ToDo.php create mode 100644 app/Http/Resources/ToDoCollection.php create mode 100644 app/Http/Resources/ToDoList.php create mode 100644 app/Models/ToDo.php create mode 100644 app/Models/ToDoList.php create mode 100644 app/Policies/ToDoListPolicy.php create mode 100644 database/factories/ToDoListFactory.php create mode 100644 database/migrations/2020_04_19_144900_create_to_do_lists_table.php create mode 100644 database/migrations/2020_04_19_190733_create_to_dos_table.php create mode 100644 tests/Feature/ToDoItemsTest.php create mode 100644 tests/Feature/ToDoListsTest.php diff --git a/app/Http/Controllers/ToDoController.php b/app/Http/Controllers/ToDoController.php new file mode 100644 index 0000000..9b3f6a6 --- /dev/null +++ b/app/Http/Controllers/ToDoController.php @@ -0,0 +1,41 @@ +authorize('create', ToDoList::class); + + $toDo = $toDoList->toDos()->create($this->validateData()); + + return (new ToDoResource($toDo)) + ->response() + ->setStatusCode(201); + } + + public function update(ToDoList $toDoList, ToDo $toDo) + { + $this->authorize('update', $toDoList); + + $toDo->update($this->validateData()); + + return (new ToDoResource($toDo)) + ->response() + ->setStatusCode(200); + } + + private function validateData() + { + return request()->validate([ + 'name' => 'required', + ]); + } +} diff --git a/app/Http/Controllers/ToDoListController.php b/app/Http/Controllers/ToDoListController.php new file mode 100644 index 0000000..98fa2a3 --- /dev/null +++ b/app/Http/Controllers/ToDoListController.php @@ -0,0 +1,55 @@ +authorize('create', ToDoList::class); + + $toDoList = request()->user()->toDoLists()->create($this->validateData()); + + return (new ToDoListResource($toDoList)) + ->response() + ->setStatusCode(201); + } + + public function show(ToDoList $toDoList) + { + $this->authorize('view', $toDoList); + + return new ToDoListResource($toDoList); + } + + public function update(ToDoList $toDoList) + { + $this->authorize('update', $toDoList); + + $toDoList->update($this->validateData()); + + return (new ToDoListResource($toDoList)) + ->response() + ->setStatusCode(200); + } + + public function destroy(ToDoList $toDoList) + { + $this->authorize('delete', $toDoList); + + $toDoList->delete(); + + return response([], 204); + } + + private function validateData() + { + return request()->validate([ + 'name' => 'required', + ]); + } +} diff --git a/app/Http/Resources/ToDo.php b/app/Http/Resources/ToDo.php new file mode 100644 index 0000000..6341a48 --- /dev/null +++ b/app/Http/Resources/ToDo.php @@ -0,0 +1,35 @@ + [ + 'type' => 'to-dos', + 'to_do_id' => $this->id, + 'attributes' => [ + 'data' => [ + 'name' => $this->name, + 'order' => $this->order, + 'checked_at' => optional($this->checked_at)->diffForHumans(), + 'last_updated' => $this->updated_at->diffForHumans(), + ] + ] + ], + 'links' => [ + 'self' => url('/to-do-lists/'.$this->to_do_list_id), + ] + ]; + } +} diff --git a/app/Http/Resources/ToDoCollection.php b/app/Http/Resources/ToDoCollection.php new file mode 100644 index 0000000..2d53a16 --- /dev/null +++ b/app/Http/Resources/ToDoCollection.php @@ -0,0 +1,22 @@ + $this->collection, + 'to_dos_count' => $this->count(), + ]; + } +} diff --git a/app/Http/Resources/ToDoList.php b/app/Http/Resources/ToDoList.php new file mode 100644 index 0000000..534445f --- /dev/null +++ b/app/Http/Resources/ToDoList.php @@ -0,0 +1,38 @@ + [ + 'type' => 'to-do-lists', + 'to_do_list_id' => $this->id, + 'attributes' => [ + 'data' => [ + 'name' => $this->name, + 'to_dos' => new ToDoCollection($this->toDos), + 'posted_by' => new UserResource($this->user), + 'last_updated' => $this->updated_at->diffForHumans(), +// 'cover_image' => new ImageResource($this->coverImage), + ] + ] + //'tags' => TagResource::collection($this->tags), + ], + 'links' => [ + 'self' => $this->path(), + ] + ]; + } +} diff --git a/app/Models/ToDo.php b/app/Models/ToDo.php new file mode 100644 index 0000000..e9e975d --- /dev/null +++ b/app/Models/ToDo.php @@ -0,0 +1,18 @@ +belongsTo(ToDoList::class); + } +} diff --git a/app/Models/ToDoList.php b/app/Models/ToDoList.php new file mode 100644 index 0000000..c258cbc --- /dev/null +++ b/app/Models/ToDoList.php @@ -0,0 +1,28 @@ +id; + } + + public function users(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function toDos(): HasMany + { + return $this->hasMany(ToDo::class); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 5cd1eab..3d8cfcd 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,6 +4,7 @@ namespace App; use App\Models\Image; use App\Models\Memo; +use App\Models\ToDoList; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphMany; @@ -82,4 +83,9 @@ class User extends Authenticatable $userImage->path = 'images/default-cover.jpg'; }); } + + public function toDoLists(): HasMany + { + return $this->hasMany(ToDoList::class); + } } diff --git a/app/Policies/ToDoListPolicy.php b/app/Policies/ToDoListPolicy.php new file mode 100644 index 0000000..94289af --- /dev/null +++ b/app/Policies/ToDoListPolicy.php @@ -0,0 +1,94 @@ +id == $toDoList->user_id; + } + + /** + * Determine whether the user can create to do lists. + * + * @param \App\User $user + * @return mixed + */ + public function create(User $user) + { + return true; + } + + /** + * Determine whether the user can update the to do list. + * + * @param \App\User $user + * @param \App\Models\ToDoList $toDoList + * @return mixed + */ + public function update(User $user, ToDoList $toDoList) + { + return $user->id == $toDoList->user_id; + } + + /** + * Determine whether the user can delete the to do list. + * + * @param \App\User $user + * @param \App\Models\ToDoList $toDoList + * @return mixed + */ + public function delete(User $user, ToDoList $toDoList) + { + return $user->id == $toDoList->user_id; + } + + /** + * Determine whether the user can restore the to do list. + * + * @param \App\User $user + * @param \App\Models\ToDoList $toDoList + * @return mixed + */ + public function restore(User $user, ToDoList $toDoList) + { + return false; + } + + /** + * Determine whether the user can permanently delete the to do list. + * + * @param \App\User $user + * @param \App\Models\ToDoList $toDoList + * @return mixed + */ + public function forceDelete(User $user, ToDoList $toDoList) + { + return false; + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index 955985f..a1f63e0 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -17,6 +17,7 @@ class AuthServiceProvider extends ServiceProvider // 'App\Model' => 'App\Policies\ModelPolicy', 'App\User' => 'App\Policies\UserPolicy', 'App\Models\Memo' => 'App\Policies\MemoPolicy', + 'App\Models\ToDoList' => 'App\Policies\ToDoListPolicy', ]; /** diff --git a/database/factories/ToDoListFactory.php b/database/factories/ToDoListFactory.php new file mode 100644 index 0000000..6287b75 --- /dev/null +++ b/database/factories/ToDoListFactory.php @@ -0,0 +1,13 @@ +define(ToDoList::class, function (Faker $faker) { + return [ + 'user_id' => factory(\App\User::class), + 'name' => $faker->words(3, [false]), + ]; +}); diff --git a/database/migrations/2020_04_19_144900_create_to_do_lists_table.php b/database/migrations/2020_04_19_144900_create_to_do_lists_table.php new file mode 100644 index 0000000..dc8bd15 --- /dev/null +++ b/database/migrations/2020_04_19_144900_create_to_do_lists_table.php @@ -0,0 +1,33 @@ +id(); + $table->unsignedBigInteger('user_id'); + $table->string('name'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('to_do_lists'); + } +} diff --git a/database/migrations/2020_04_19_190733_create_to_dos_table.php b/database/migrations/2020_04_19_190733_create_to_dos_table.php new file mode 100644 index 0000000..75061c7 --- /dev/null +++ b/database/migrations/2020_04_19_190733_create_to_dos_table.php @@ -0,0 +1,35 @@ +id(); + $table->unsignedBigInteger('to_do_list_id'); + $table->string('name'); + $table->integer('order')->default(0); + $table->timestamp('checked_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('to_dos'); + } +} diff --git a/routes/api.php b/routes/api.php index 3f27d2e..cb7b5bd 100644 --- a/routes/api.php +++ b/routes/api.php @@ -23,6 +23,8 @@ Route::middleware('auth:api')->group(function () { '/users' => 'UserController', '/memos' => 'MemosController', '/meteo' => 'MeteoController', + '/to-do-lists' => 'ToDoListController', + '/to-do-lists/{toDoList}/to-do' => 'ToDoController', // '/users/{user}/posts' => 'UserPostController', // '/friend-request' => 'FriendRequestController', ]); diff --git a/tests/Feature/MemosTest.php b/tests/Feature/MemosTest.php index 830ec64..8a3c1fa 100644 --- a/tests/Feature/MemosTest.php +++ b/tests/Feature/MemosTest.php @@ -122,7 +122,7 @@ class MemosTest extends TestCase $response = $this->patch('/api/memos/' . $memo->id, array_merge($this->data(), ['api_token' => $anotherUser->api_token])); - $response->assertStatus(Response::HTTP_FORBIDDEN); + $response->assertStatus(403); } /** @test */ @@ -138,7 +138,7 @@ class MemosTest extends TestCase $this->assertCount(0, Memo::all()); - $response->assertStatus(Response::HTTP_NO_CONTENT); + $response->assertStatus(204); } /** @test */ diff --git a/tests/Feature/ToDoItemsTest.php b/tests/Feature/ToDoItemsTest.php new file mode 100644 index 0000000..9a1069c --- /dev/null +++ b/tests/Feature/ToDoItemsTest.php @@ -0,0 +1,124 @@ +withoutExceptionHandling(); + $this->actingAs($user = factory(User::class)->create(), 'api'); + $toDoList = factory(ToDoList::class)->create(['id' => 123, 'user_id' => $user->id]); + + $response = $this->post('/api/to-do-lists/'. $toDoList->id .'/to-do', [ + 'name' => 'Test name to do', + ])->assertStatus(201); + + $toDo = ToDo::first(); + + $this->assertEquals('Test name to do', $toDo->name); + $this->assertEquals($toDoList->todos[0], $toDo); + $response->assertJson([ + 'data' => [ + 'to_do_id' => $toDo->id, + 'attributes' => [ + 'data' => [ + 'name' => $toDo->name, + 'order' => 0, + ] + ], + ], + 'links' => [ + 'self' => url('/to-do-lists/'.$toDo->toDoList->id), + ] + ]); + } + + /** @test */ + public function to_do_name_are_required() + { + $this->actingAs($user = factory(User::class)->create(), 'api'); + $toDoList = factory(ToDoList::class)->create(['user_id' => $user->id]); + $response = $this->post('/api/to-do-lists/'. $toDoList->id .'/to-do', ['name' => '']); + + $response->assertSessionHasErrors('name'); + $this->assertCount(0, ToDo::all()); + } + + /** @test */ + public function a_to_do_by_to_do_list_can_be_retrieved() + { + $this->actingAs($user = factory(User::class)->create(), 'api'); + $toDoList = factory(ToDoList::class)->create(['user_id' => $user->id]); + $toDoList->toDos()->create([ + 'name' => 'Test name to do', + ]); + + $response = $this->get('/api/to-do-lists/' . $toDoList->id ); + + $response->assertJson([ + 'data' => [ + 'to_do_list_id' => $toDoList->id, + 'attributes' => [ + 'data' => [ + 'name' => $toDoList->name, + 'last_updated' => $toDoList->updated_at->diffForHumans(), + 'to_dos' => [ + 'data' => [ + [ + 'data' => [ + 'to_do_id' => $toDoList->toDos[0]->id, + ] + ] + ], + 'to_dos_count' => 1, + ] + ] + ], + ] + ]); + } + + /** @test */ + public function a_to_do_can_be_patch() + { + $this->withoutExceptionHandling(); + $this->actingAs($user = factory(User::class)->create(), 'api'); + $toDoList = factory(ToDoList::class)->create(['id' => 123, 'user_id' => $user->id]); + $toDoList->toDos()->create([ + 'name' => 'Test name to do', + 'order' => 10, + ]); + + $toDoList = $toDoList->fresh(); +// $toDoList = ToDoList::first(); + + $response = $this->patch('/api/to-do-lists/123/to-do/'. $toDoList->toDos[0]->id, [ + 'name' => 'To Do update' + ])->assertStatus(200) + ->assertJson([ + 'data' => [ + 'to_do_id' => $toDoList->toDos[0]->id, + 'attributes' => [ + 'data' => [ + 'name' => 'To Do update', + 'order' => 10, + ] + ] + ], + 'links' => [ + 'self' => url('/to-do-lists/'. $toDoList->id), + ] + ]); + } +} diff --git a/tests/Feature/ToDoListsTest.php b/tests/Feature/ToDoListsTest.php new file mode 100644 index 0000000..4bedbcf --- /dev/null +++ b/tests/Feature/ToDoListsTest.php @@ -0,0 +1,163 @@ +withoutExceptionHandling(); + $this->actingAs($user = factory(User::class)->create(), 'api'); + + $response = $this->post('/api/to-do-lists/', [ + 'name' => 'Test name to do list' + ])->assertStatus(201); + + $toDoList = ToDoList::first(); + + $this->assertEquals('Test name to do list', $toDoList->name); + $response->assertJson([ + 'data' => [ + 'type' => 'to-do-lists', + 'to_do_list_id' => $toDoList->id, + 'attributes' => [ + 'data' => [ + 'name' => $toDoList->name, + ] + ], + ], + 'links' => [ + 'self' => $toDoList->path(), + ] + ]); + } + + /** @test */ + public function to_do_list_name_are_required() + { + $this->actingAs($user = factory(\App\User::class)->create(), 'api'); + $response = $this->post('/api/to-do-lists/', ['name' => '']); + + $response->assertSessionHasErrors('name'); + $this->assertCount(0, ToDoList::all()); + } + + /** @test */ + public function a_to_do_list_can_be_retrieved() + { + $this->actingAs($user = factory(User::class)->create(), 'api'); + $toDoList = factory(ToDoList::class)->create(['user_id' => $user->id]); + + $response = $this->get('/api/to-do-lists/' . $toDoList->id ); + + $response->assertJson([ + 'data' => [ + 'to_do_list_id' => $toDoList->id, + 'attributes' => [ + 'data' => [ + 'name' => $toDoList->name, + 'last_updated' => $toDoList->updated_at->diffForHumans(), + ] + ], + ], + 'links' => [ + 'self' => $toDoList->path(), + ] + ]); + } + + /** @test */ + public function only_owner_to_do_list_can_retrieved_it() + { + $user = factory(User::class)->create(); + $toDoList = factory(ToDoList::class)->create(['user_id' => $user->id]); + + $this->actingAs($userAnother = factory(User::class)->create(), 'api'); + + $response = $this->get('/api/to-do-lists/' . $toDoList->id ); + + $response->assertStatus(403); + } + + /** @test */ + public function a_to_do_list_can_be_patch() + { + $this->actingAs($user = factory(User::class)->create(), 'api'); + + $toDoList = factory(ToDoList::class)->create(['user_id' => $user->id]); + + $response = $this->patch('/api/to-do-lists/' . $toDoList->id, [ + 'name' => 'To Do List Update' + ]); + + $toDoList = $toDoList->fresh(); + + $this->assertEquals('To Do List Update', $toDoList->name); + + $response->assertStatus(200); + $response->assertJson([ + 'data' => [ + 'to_do_list_id' => $toDoList->id, + 'attributes' => [ + 'data' => [ + 'name' => 'To Do List Update' + ] + ] + ], + 'links' => [ + 'self' => $toDoList->path(), + ] + ]); + } + + /** @test */ + public function only_the_owner_can_patch_the_to_do_list() + { + $this->actingAs($user = factory(User::class)->create(), 'api'); + $toDoList = factory(ToDoList::class)->create(); + + $anotherUser = factory(User::class)->create(); + + $response = $this->patch('/api/to-do-lists/' . $toDoList->id, ['name' => 'Name changed'], ['api_token' => $anotherUser->api_token]); + + $response->assertStatus(403); + } + + /** @test */ + public function a_to_do_list_can_be_delete() + { + $this->actingAs($user = factory(User::class)->create(), 'api'); + + $toDoList = factory(ToDoList::class)->create(['user_id' => $user->id]); + + $response = $this->delete('/api/to-do-lists/' . $toDoList->id); + + $toDoList = $toDoList->fresh(); + + $this->assertCount(0, ToDoList::all()); + + $response->assertStatus(204); + } + + /** @test */ + public function only_the_owner_can_delete_the_to_do_list() + { + $user = factory(User::class)->create(); + $toDoList = factory(ToDoList::class)->create(); + + $this->actingAs($anotherUser = factory(User::class)->create(), 'api'); + + $response = $this->delete('/api/to-do-lists/' . $toDoList->id); + + $response->assertStatus(403); + } +}