diff --git a/app/Http/Controllers/BookmarkController.php b/app/Http/Controllers/BookmarkController.php new file mode 100644 index 0000000..3a48624 --- /dev/null +++ b/app/Http/Controllers/BookmarkController.php @@ -0,0 +1,57 @@ +authorize('viewAny', Bookmark::class); + + return new BookmarkCollection(request()->user()->bookmarks); + } + + public function store(BookmarkRequest $request) + { + $this->authorize('create', Bookmark::class); + + $bookmark = request()->user()->bookmarks()->create($request->all()); + + return (new BookmarkResource($bookmark)) + ->response() + ->setStatusCode(201); + } + + public function show(Bookmark $bookmark) + { + $this->authorize('view', $bookmark); + + return new BookmarkResource($bookmark); + } + + public function update(BookmarkRequest $request, Bookmark $bookmark) + { + $this->authorize('update', $bookmark); + + $bookmark->update($request->all()); + + return (new BookmarkResource($bookmark)) + ->response() + ->setStatusCode(200); + } + + public function destroy(Bookmark $bookmark) + { + $this->authorize('delete', $bookmark); + + $bookmark->delete(); + + return response([], 204); + } +} diff --git a/app/Http/Requests/BookmarkRequest.php b/app/Http/Requests/BookmarkRequest.php new file mode 100644 index 0000000..d008ae4 --- /dev/null +++ b/app/Http/Requests/BookmarkRequest.php @@ -0,0 +1,31 @@ + 'max:65', + 'url' => ['required', 'url', 'max:255'], + 'favicon' => ['url', 'max:255'], + ]; + } + + public function attributes() + { + return [ + 'name' => 'Nom du marque-page', + ]; + } + + public function messages() + { + return [ + 'url.required' => 'Une url est nécessaire', + ]; + } +} diff --git a/app/Http/Resources/Bookmark.php b/app/Http/Resources/Bookmark.php new file mode 100644 index 0000000..fdb8ee2 --- /dev/null +++ b/app/Http/Resources/Bookmark.php @@ -0,0 +1,33 @@ + [ + 'type' => 'bookmark', + 'bookmark_id' => $this->id, + 'attributes' => [ + 'data' => [ + 'name' => $this->name, + 'url' => $this->url, + 'favicon' => $this->favicon, + 'created_at' => $this->created_at->diffForHumans(), + 'last_updated' => $this->updated_at->diffForHumans(), + ] + ], + ], + ]; + } +} diff --git a/app/Http/Resources/BookmarkCollection.php b/app/Http/Resources/BookmarkCollection.php new file mode 100644 index 0000000..b6418e0 --- /dev/null +++ b/app/Http/Resources/BookmarkCollection.php @@ -0,0 +1,25 @@ + $this->collection, + 'bookmarks_count' => $this->count(), + 'links' => [ + 'self' => url('/bookmarks'), + ] + ]; + } +} diff --git a/app/Models/Bookmark.php b/app/Models/Bookmark.php new file mode 100644 index 0000000..7e52a12 --- /dev/null +++ b/app/Models/Bookmark.php @@ -0,0 +1,10 @@ +hasMany(ToDoList::class); } + public function bookmarks() : HasMany + { + return $this->hasMany(Bookmark::class); + } + public function images(): MorphMany { return $this->morphMany(Image::class, 'imageable'); diff --git a/app/Policies/BookmarkPolicy.php b/app/Policies/BookmarkPolicy.php new file mode 100644 index 0000000..23b30b2 --- /dev/null +++ b/app/Policies/BookmarkPolicy.php @@ -0,0 +1,94 @@ +id == $bookmark->user_id; + } + + /** + * Determine whether the user can create bookmarks. + * + * @param \App\User $user + * @return mixed + */ + public function create(User $user) + { + return true; + } + + /** + * Determine whether the user can update the bookmark. + * + * @param \App\User $user + * @param \App\Models\Bookmark $bookmark + * @return mixed + */ + public function update(User $user, Bookmark $bookmark) + { + return $user->id == $bookmark->user_id; + } + + /** + * Determine whether the user can delete the bookmark. + * + * @param \App\User $user + * @param \App\Models\Bookmark $bookmark + * @return mixed + */ + public function delete(User $user, Bookmark $bookmark) + { + return $user->id == $bookmark->user_id; + } + + /** + * Determine whether the user can restore the bookmark. + * + * @param \App\User $user + * @param \App\Models\Bookmark $bookmark + * @return mixed + */ + public function restore(User $user, Bookmark $bookmark) + { + return false; + } + + /** + * Determine whether the user can permanently delete the bookmark. + * + * @param \App\User $user + * @param \App\Models\Bookmark $bookmark + * @return mixed + */ + public function forceDelete(User $user, Bookmark $bookmark) + { + return false; + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index a1f63e0..5012a59 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -18,6 +18,7 @@ class AuthServiceProvider extends ServiceProvider 'App\User' => 'App\Policies\UserPolicy', 'App\Models\Memo' => 'App\Policies\MemoPolicy', 'App\Models\ToDoList' => 'App\Policies\ToDoListPolicy', + 'App\Models\Bookmark' => 'App\Policies\BookmarkPolicy', ]; /** diff --git a/database/factories/BookmarkFactory.php b/database/factories/BookmarkFactory.php new file mode 100644 index 0000000..bedc0cd --- /dev/null +++ b/database/factories/BookmarkFactory.php @@ -0,0 +1,15 @@ +define(Bookmark::class, function (Faker $faker) { + return [ + 'user_id' => factory(\App\User::class), + 'name' => $faker->words(3, [false]), + 'url' => $faker->url, + 'favicon' => $faker->imageUrl(), + ]; +}); diff --git a/database/migrations/2020_05_09_123927_create_bookmarks_table.php b/database/migrations/2020_05_09_123927_create_bookmarks_table.php new file mode 100644 index 0000000..a8beefb --- /dev/null +++ b/database/migrations/2020_05_09_123927_create_bookmarks_table.php @@ -0,0 +1,35 @@ +id(); + $table->unsignedBigInteger('user_id'); + $table->string('name')->nullable(); + $table->string('url'); + $table->string('favicon')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('bookmarks'); + } +} diff --git a/routes/api.php b/routes/api.php index 47453da..9e38f83 100644 --- a/routes/api.php +++ b/routes/api.php @@ -26,6 +26,7 @@ Route::middleware('auth:api')->group(function () { '/meteo' => 'MeteoController', '/to-do-lists' => 'ToDoListController', '/to-do-lists/{toDoList}/to-do' => 'ToDoController', + '/bookmarks' => 'BookmarkController', // '/users/{user}/posts' => 'UserPostController', // '/friend-request' => 'FriendRequestController', ]); diff --git a/tests/Feature/BookmarkTest.php b/tests/Feature/BookmarkTest.php new file mode 100644 index 0000000..b0d5e32 --- /dev/null +++ b/tests/Feature/BookmarkTest.php @@ -0,0 +1,212 @@ +withoutExceptionHandling(); + + $this->actingAs($user = factory(\App\User::class)->create(), 'api'); + + $response = $this->post('/api/bookmarks', $this->data()); + + $bookmark = Bookmark::first(); + + $this->assertCount(1, Bookmark::all()); + $this->assertEquals('Test Name', $bookmark->name); + $this->assertEquals('https://portal.bricooli.fr', $bookmark->url); + $this->assertEquals('https://portal.bricooli.fr/img/logo.svg', $bookmark->favicon); + + $response->assertStatus(201); + $response->assertJson([ + 'data' => [ + 'type' => 'bookmark', + 'bookmark_id' => $bookmark->id, + 'attributes' => [ + 'data' => [ + 'name' => $bookmark->name, + 'url' => $bookmark->url, + 'favicon' => $bookmark->favicon, + ] + ], + ], + ]); + } + + /** @test */ + public function bookmark_url_are_required() + { + $this->actingAs($user = factory(\App\User::class)->create(), 'api'); + $response = $this->post('/api/bookmarks', array_merge($this->data(), ['url' => ''])); + + $response->assertSessionHasErrors('url'); + $this->assertCount(0, Bookmark::all()); + } + + /** @test */ + public function a_bookmark_can_be_retrieved() + { + $this->actingAs($user = factory(User::class)->create(), 'api'); + $bookmark = factory(Bookmark::class)->create(['user_id' => $user->id]); + + $response = $this->get('/api/bookmarks/' . $bookmark->id ); + + $response->assertJson([ + 'data' => [ + 'bookmark_id' => $bookmark->id, + 'attributes' => [ + 'data' => [ + 'name' => $bookmark->name, + 'url' => $bookmark->url, + 'favicon' => $bookmark->favicon, + 'last_updated' => $bookmark->updated_at->diffForHumans(), + ] + ], + ], + ]); + } + + /** @test */ + public function a_user_can_retrueved_all_this_to_do_lists() + { + $this->actingAs($user = factory(User::class)->create(), 'api'); + $bookmarkOne = factory(Bookmark::class)->create(['user_id' => $user->id]); + $bookmarkTwo = factory(Bookmark::class)->create(['user_id' => $user->id]); + + $response = $this->get('/api/bookmarks'); + + $response->assertJson([ + 'data' => [ + [ + 'data' => [ + 'bookmark_id' => $bookmarkOne->id, + 'attributes' => [ + 'data' => [ + 'name' => $bookmarkOne->name, + 'last_updated' => $bookmarkOne->updated_at->diffForHumans(), + ] + ], + ] + ], + [ + 'data' => [ + 'bookmark_id' => $bookmarkTwo->id, + 'attributes' => [ + 'data' => [ + 'name' => $bookmarkTwo->name, + 'last_updated' => $bookmarkTwo->updated_at->diffForHumans(), + ] + ], + ], + ] + ], + 'links' => [ + 'self' => url('/bookmarks'), + ] + ]); + } + + /** @test */ + public function only_owner_bookmark_can_retrieved_it() + { + $user = factory(User::class)->create(); + $bookmark = factory(Bookmark::class)->create(['user_id' => $user->id]); + + $this->actingAs($userAnother = factory(User::class)->create(), 'api'); + + $response = $this->get('/api/bookmarks/' . $bookmark->id ); + + $response->assertStatus(403); + } + + /** @test */ + public function a_to_bookmark_can_be_patch() + { + $this->withoutExceptionHandling(); + $this->actingAs($user = factory(User::class)->create(), 'api'); + $bookmark = factory(Bookmark::class)->create(['user_id' => $user->id]); + + $response = $this->patch('/api/bookmarks/' . $bookmark->id, [ + 'name' => 'Bookmark Update', + 'url' => 'https://portal.bricooli.fr', + ]); + + $bookmark = $bookmark->fresh(); + + $this->assertEquals('Bookmark Update', $bookmark->name); + $this->assertEquals('https://portal.bricooli.fr', $bookmark->url); + + $response->assertStatus(200); + $response->assertJson([ + 'data' => [ + 'bookmark_id' => $bookmark->id, + 'attributes' => [ + 'data' => [ + 'name' => 'Bookmark Update' + ] + ] + ] + ]); + } + + /** @test */ + public function only_the_owner_can_patch_the_bookmark() + { + $user = factory(User::class)->create(); + $bookmark = factory(Bookmark::class)->create(['id' => 123, 'user_id' => $user->id]); + + $this->actingAs($anotherUser = factory(User::class)->create(), 'api'); + + $this->patch('/api/bookmarks/'. $bookmark->id, ['url' => 'https://portal.bricooli.fr']) + ->assertStatus(403); + } + + /** @test */ + public function a_bookmark_can_be_delete() + { + $this->actingAs($user = factory(User::class)->create(), 'api'); + + $bookmark = factory(Bookmark::class)->create(['user_id' => $user->id]); + + $response = $this->delete('/api/bookmarks/' . $bookmark->id); + + $toDoList = $bookmark->fresh(); + + $this->assertCount(0, Bookmark::all()); + + $response->assertStatus(204); + } + + /** @test */ + public function only_the_owner_can_delete_the_bookmark() + { + $user = factory(User::class)->create(); + $bookmark = factory(Bookmark::class)->create(); + + $this->actingAs($anotherUser = factory(User::class)->create(), 'api'); + + $response = $this->delete('/api/bookmarks/' . $bookmark->id); + + $response->assertStatus(403); + } + + private function data() + { + return [ + 'name' => 'Test Name', + 'url' => 'https://portal.bricooli.fr', + 'favicon' => 'https://portal.bricooli.fr/img/logo.svg', + ]; + } +}