diff --git a/app/Http/Controllers/BookmarkController.php b/app/Http/Controllers/BookmarkController.php
new file mode 100644
index 0000000..11b91b0
--- /dev/null
+++ b/app/Http/Controllers/BookmarkController.php
@@ -0,0 +1,89 @@
+authorize('viewAny', Bookmark::class);
+
+ return new BookmarkCollection(request()->user()->bookmarks);
+ }
+
+ public function store(BookmarkRequest $request)
+ {
+ $this->authorize('create', Bookmark::class);
+
+ $metas = $this->getMeta($request->url);
+ $request['name'] = (empty($request->name)) ? $metas['title'] : $request->name;
+ $request['favicon'] = $metas['favicon'];
+
+ $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);
+ }
+
+ private function getMeta($url)
+ {
+ $metas = [];
+ $client = new \GuzzleHttp\Client();
+ $promise = $client->requestAsync('GET', $url);
+ $response = $promise->wait();
+ $page = $response->getBody()->getContents();
+ preg_match("/\
(.*)\<\/title\>/i",$page,$title);
+ $metas['title'] = $title[1];
+
+
+ preg_match('/\/i',$page,$favicon);
+ if(isset($favicon[1])) {
+ preg_match('/href="(.*)/i',$favicon[1],$favicon);
+ $metas['favicon'] = $favicon[1];
+
+ preg_match('/http/', $metas['favicon'], $matches);
+ if(empty($matches)) {
+ $metas['favicon'] = parse_url($url, PHP_URL_SCHEME).'://'.parse_url($url, PHP_URL_HOST).$metas['favicon'];
+ }
+ //dd($metas['favicon'], $matches, !isset($matches[1]), empty($matches));
+ } else {
+ $metas['favicon'] = null;
+ }
+
+ return $metas;
+ }
+}
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..a7c6271
--- /dev/null
+++ b/app/Http/Resources/Bookmark.php
@@ -0,0 +1,34 @@
+ [
+ 'type' => 'bookmark',
+ 'bookmark_id' => $this->id,
+ 'attributes' => [
+ 'data' => [
+ 'name' => $this->name,
+ 'description' => $this->description,
+ '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..bf50dd9
--- /dev/null
+++ b/database/migrations/2020_05_09_123927_create_bookmarks_table.php
@@ -0,0 +1,36 @@
+id();
+ $table->unsignedBigInteger('user_id');
+ $table->string('name')->nullable();
+ $table->string('description')->nullable();
+ $table->string('url');
+ $table->string('favicon')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('bookmarks');
+ }
+}
diff --git a/resources/js/components/Nav.vue b/resources/js/components/Nav.vue
index 475efab..5c252ed 100644
--- a/resources/js/components/Nav.vue
+++ b/resources/js/components/Nav.vue
@@ -13,6 +13,10 @@
ToDo Lists
+
+
+ Bookmarks
+
Jeux
diff --git a/resources/js/router.js b/resources/js/router.js
index 656e1fd..49a1c34 100644
--- a/resources/js/router.js
+++ b/resources/js/router.js
@@ -11,6 +11,7 @@ import MemoShow from './views/Memo/MemoShow'
import MemoEdit from './views/Memo/MemoEdit'
import ToDoListIndex from './views/ToDoLists/ToDoListIndex'
import ToDoListShow from './views/ToDoLists/ToDoListShow'
+import BookmarkIndex from './views/Bookmark/BookmarkIndex'
import GameIndex from './views/Games/GameIndex'
import Hangman from './views/Games/HangMan/Hangman'
import Quizz from './views/Games/Quizz/Quizz'
@@ -65,6 +66,11 @@ export default new VueRouter({
meta: {title: 'Details of List'}
},
+ {
+ path: '/bookmarks', component: BookmarkIndex,
+ meta: {title: 'Bookmark Lists'}
+ },
+
{
path: '/jeux', component: GameIndex,
meta: {title: 'Liste des jeux'}
diff --git a/resources/js/views/Bookmark/Bookmark.vue b/resources/js/views/Bookmark/Bookmark.vue
new file mode 100644
index 0000000..9891fde
--- /dev/null
+++ b/resources/js/views/Bookmark/Bookmark.vue
@@ -0,0 +1,95 @@
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/views/Bookmark/BookmarkIndex.vue b/resources/js/views/Bookmark/BookmarkIndex.vue
new file mode 100644
index 0000000..473900e
--- /dev/null
+++ b/resources/js/views/Bookmark/BookmarkIndex.vue
@@ -0,0 +1,92 @@
+
+
+
+
+
Add a new to-do list ?
+
+
+
+
+
+
+
+
+
+
+
No Bookmark Yet
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/svg/globe.svg b/resources/svg/globe.svg
new file mode 100644
index 0000000..4603b43
--- /dev/null
+++ b/resources/svg/globe.svg
@@ -0,0 +1,127 @@
+
diff --git a/routes/api.php b/routes/api.php
index 2641233..be41cd2 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',
'/events/categories' => 'EventCategoryController',
'/events' => 'EventController',
// '/users/{user}/posts' => 'UserPostController',
diff --git a/tests/Feature/BookmarkTest.php b/tests/Feature/BookmarkTest.php
new file mode 100644
index 0000000..79b0c8f
--- /dev/null
+++ b/tests/Feature/BookmarkTest.php
@@ -0,0 +1,215 @@
+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('Test Description', $bookmark->description);
+ $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,
+ 'description' => $bookmark->description,
+ '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',
+ 'description' => 'Test Description',
+ 'url' => 'https://portal.bricooli.fr',
+ 'favicon' => 'https://portal.bricooli.fr/img/logo.svg',
+ ];
+ }
+}