From 1c7bff508f36b558072fb0fe2648da58fc8ecc28 Mon Sep 17 00:00:00 2001 From: Jonny Barnes Date: Tue, 19 Sep 2017 16:07:32 +0100 Subject: [PATCH] Squashed commit of the following: commit ebbaf83a331395d86754f231ebf3852c31ee13e7 Author: Jonny Barnes Date: Tue Sep 19 16:07:16 2017 +0100 Show just a name if no known author url commit 7c3fc38a5101635efbb1659d7dc0e4e87f28977a Author: Jonny Barnes Date: Tue Sep 19 15:55:07 2017 +0100 Update changelog commit e05876d604b2655fdd1b03fe5390c3333cd5e064 Author: Jonny Barnes Date: Tue Sep 19 15:54:10 2017 +0100 Add a trait for testing tokens commit 1288769757e6c69fccf849a73ef53e6497953d74 Author: Jonny Barnes Date: Tue Sep 19 15:53:54 2017 +0100 Add a test for the process like job commit d85a7109d51c979846b2b15d92e2b4c3978c6dc7 Author: Jonny Barnes Date: Tue Sep 19 15:53:25 2017 +0100 fix typo, and allow for array of author info, or just a name commit 1fc63c6fb6c5648e31759502a011b2be0525af54 Author: Jonny Barnes Date: Mon Sep 18 15:38:16 2017 +0100 Add another test for creating likes commit 487723ac41fa00a8182f5bf3665ab7b5f8fece52 Author: Jonny Barnes Date: Mon Sep 18 15:38:03 2017 +0100 fix unexpected end of file error commit a24eef82ae7a2a3e1d3943a6cfed85757c713434 Author: Jonny Barnes Date: Mon Sep 18 15:37:31 2017 +0100 Better response when creating likes commit fa49df98613b136167dc093a97745eeb90a4a7a6 Author: Jonny Barnes Date: Mon Sep 18 14:43:39 2017 +0100 Make the author fields nullable commit 5a2f9273c18cf31a54eb54f40732024159c3dc2d Author: Jonny Barnes Date: Mon Sep 18 14:43:20 2017 +0100 Delegate to the LikeService for creating likes commit 801d6567ec3456cbcdfa6260339dd9ed2fdfa5b0 Author: Jonny Barnes Date: Mon Sep 18 14:42:54 2017 +0100 Create the Job that gets the content of the like and the author info commit df563473606b43a330c4e977b230d4b7b2a85268 Author: Jonny Barnes Date: Mon Sep 18 14:42:28 2017 +0100 Create the service the mpub controller delegates to commit ab6ebee71ffdeb584bbef0454874d3fc1c6499f4 Author: Jonny Barnes Date: Mon Sep 18 14:42:08 2017 +0100 Allow Like::create to work for just the url commit 6d70c43f11056597a493f863c3a1ac681ed06b71 Author: Jonny Barnes Date: Mon Sep 18 14:10:20 2017 +0100 Add some initial tests commit 4049342b061594656dbf7183d7428f95ba6b3598 Author: Jonny Barnes Date: Mon Sep 18 14:10:06 2017 +0100 Add database migration/seed/factory commit 5b3aa20fa14202e84af310477b97044723201ea7 Author: Jonny Barnes Date: Mon Sep 18 14:09:21 2017 +0100 Add domain logic for likes commit 7ef5392a1833df6cee77ecb1166af4fc0abc0eb5 Author: Jonny Barnes Date: Mon Sep 18 14:08:47 2017 +0100 Add routes for likes --- app/Http/Controllers/LikesController.php | 20 +++++ app/Http/Controllers/MicropubController.php | 11 ++- app/Jobs/ProcessLike.php | 59 +++++++++++++ app/Like.php | 44 ++++++++++ app/Services/LikeService.php | 37 ++++++++ changelog.md | 3 + database/factories/LikeFactory.php | 12 +++ .../2017_09_16_191741_create_likes_table.php | 35 ++++++++ database/seeds/DatabaseSeeder.php | 1 + database/seeds/LikesTableSeeder.php | 16 ++++ database/seeds/NotesTableSeeder.php | 6 ++ resources/views/likes/index.blade.php | 27 ++++++ resources/views/likes/show.blade.php | 23 +++++ routes/web.php | 6 ++ tests/Feature/LikesTest.php | 88 +++++++++++++++++++ tests/TestToken.php | 37 ++++++++ 16 files changed, 424 insertions(+), 1 deletion(-) create mode 100644 app/Http/Controllers/LikesController.php create mode 100644 app/Jobs/ProcessLike.php create mode 100644 app/Like.php create mode 100644 app/Services/LikeService.php create mode 100644 database/factories/LikeFactory.php create mode 100644 database/migrations/2017_09_16_191741_create_likes_table.php create mode 100644 database/seeds/LikesTableSeeder.php create mode 100644 resources/views/likes/index.blade.php create mode 100644 resources/views/likes/show.blade.php create mode 100644 tests/Feature/LikesTest.php create mode 100644 tests/TestToken.php diff --git a/app/Http/Controllers/LikesController.php b/app/Http/Controllers/LikesController.php new file mode 100644 index 00000000..02922e86 --- /dev/null +++ b/app/Http/Controllers/LikesController.php @@ -0,0 +1,20 @@ +paginate(20); + + return view('likes.index', compact('likes')); + } + + public function show(Like $like) + { + return view('likes.show', compact('like')); + } +} diff --git a/app/Http/Controllers/MicropubController.php b/app/Http/Controllers/MicropubController.php index c6498a69..36cba4ab 100644 --- a/app/Http/Controllers/MicropubController.php +++ b/app/Http/Controllers/MicropubController.php @@ -6,8 +6,9 @@ use Storage; use Monolog\Logger; use Ramsey\Uuid\Uuid; use App\Jobs\ProcessImage; -use App\{Media, Note, Place}; +use App\Services\LikeService; use Monolog\Handler\StreamHandler; +use App\{Like, Media, Note, Place}; use Intervention\Image\ImageManager; use Illuminate\Http\{Request, Response}; use App\Exceptions\InvalidTokenException; @@ -73,6 +74,14 @@ class MicropubController extends Controller if (stristr($tokenData->getClaim('scope'), 'create') === false) { return $this->returnInsufficientScopeResponse(); } + if ($request->has('properties.like-of') || $request->has('like-of')) { + $like = (new LikeService())->createLike($request); + + return response()->json([ + 'response' => 'created', + 'location' => config('app.url') . "/likes/$like->id", + ], 201)->header('Location', config('app.url') . "/likes/$like->id"); + } $data = []; $data['client-id'] = $tokenData->getClaim('client_id'); if ($request->header('Content-Type') == 'application/json') { diff --git a/app/Jobs/ProcessLike.php b/app/Jobs/ProcessLike.php new file mode 100644 index 00000000..84fffa7d --- /dev/null +++ b/app/Jobs/ProcessLike.php @@ -0,0 +1,59 @@ +like = $like; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle(Client $client, Authorship $authorship) + { + $response = $client->request('GET', $this->like->url); + $mf2 = \Mf2\parse((string) $response->getBody(), $this->like->url); + if (array_has($mf2, 'items.0.properties.content')) { + $this->like->content = $mf2['items'][0]['properties']['content'][0]['html']; + } + + try { + $author = $authorship->findAuthor($mf2); + if (is_array($author)) { + $this->like->author_name = $author['name']; + $this->like->author_url = $author['url']; + } + if (is_string($author) && $author !== '') { + $this->like->author_name = $author; + } + } catch (AuthorshipParserException $exception) { + return; + } + + $this->like->save(); + } +} diff --git a/app/Like.php b/app/Like.php new file mode 100644 index 00000000..aae728e6 --- /dev/null +++ b/app/Like.php @@ -0,0 +1,44 @@ +attributes['url'] = normalize_url($value); + } + + public function setAuthorUrlAttribute($value) + { + $this->attributes['author_url'] = normalize_url($value); + } + + public function getContentAttribute($value) + { + if ($value === null) { + return $this->url; + } + + $mf2 = Mf2\parse($value, $this->url); + + return $this->filterHTML($mf2['items'][0]['properties']['content'][0]['html']); + } + + public function filterHTML($html) + { + $config = HTMLPurifier_Config::createDefault(); + $config->set('Cache.SerializerPath', storage_path() . '/HTMLPurifier'); + $config->set('HTML.TargetBlank', true); + $purifier = new HTMLPurifier($config); + + return $purifier->purify($html); + } +} diff --git a/app/Services/LikeService.php b/app/Services/LikeService.php new file mode 100644 index 00000000..d4ff6f76 --- /dev/null +++ b/app/Services/LikeService.php @@ -0,0 +1,37 @@ +header('Content-Type') == 'application/json') { + //micropub request + $url = normalize_url($request->input('properties.like-of.0')); + } + if ( + ($request->header('Content-Type') == 'x-www-url-formencoded') + || + ($request->header('Content-Type') == 'multipart/form-data') + ) { + $url = normalize_url($request->input('like-of')); + } + + $like = Like::create(['url' => $url]); + ProcessLike::dispatch($like); + + return $like; + } +} diff --git a/changelog.md b/changelog.md index f5b38ea3..854deb3c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,8 @@ # Changelog +## Version {next} + - Add support for `likes` (issue#69) + ## Version 0.8.1 (2017-09-16) - Order notes by latest (issue#70) - AcitivtyStream support is now indicated with HTTP Link headers diff --git a/database/factories/LikeFactory.php b/database/factories/LikeFactory.php new file mode 100644 index 00000000..ad3e9551 --- /dev/null +++ b/database/factories/LikeFactory.php @@ -0,0 +1,12 @@ +define(App\Like::class, function (Faker $faker) { + return [ + 'url' => $faker->url, + 'author_name' => $faker->name, + 'author_url' => $faker->url, + 'content' => '
' . $faker->realtext() . '
', + ]; +}); diff --git a/database/migrations/2017_09_16_191741_create_likes_table.php b/database/migrations/2017_09_16_191741_create_likes_table.php new file mode 100644 index 00000000..5f413c82 --- /dev/null +++ b/database/migrations/2017_09_16_191741_create_likes_table.php @@ -0,0 +1,35 @@ +increments('id'); + $table->string('url'); + $table->string('author_name')->nullable(); + $table->string('author_url')->nullable(); + $table->text('content')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('likes'); + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 8e906692..bb2d4f85 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -18,5 +18,6 @@ class DatabaseSeeder extends Seeder $this->call(NotesTableSeeder::class); $this->call(WebMentionsTableSeeder::class); $this->call(IndieWebUserTableSeeder::class); + $this->call(LikesTableSeeder::class); } } diff --git a/database/seeds/LikesTableSeeder.php b/database/seeds/LikesTableSeeder.php new file mode 100644 index 00000000..59c5af07 --- /dev/null +++ b/database/seeds/LikesTableSeeder.php @@ -0,0 +1,16 @@ +create(); + } +} diff --git a/database/seeds/NotesTableSeeder.php b/database/seeds/NotesTableSeeder.php index 334ba2df..3509ae8a 100644 --- a/database/seeds/NotesTableSeeder.php +++ b/database/seeds/NotesTableSeeder.php @@ -12,6 +12,7 @@ class NotesTableSeeder extends Seeder public function run() { factory(App\Note::class, 10)->create(); + sleep(1); $noteWithPlace = App\Note::create([ 'note' => 'Having a #beer at the local. 🍺', 'tweet_id' => '123456789', @@ -19,17 +20,21 @@ class NotesTableSeeder extends Seeder $place = App\Place::find(1); $noteWithPlace->place()->associate($place); $noteWithPlace->save(); + sleep(1); $noteWithContact = App\Note::create([ 'note' => 'Hi @tantek' ]); + sleep(1); $noteWithContactPlusPic = App\Note::create([ 'note' => 'Hi @aaron', 'client_id' => 'https://jbl5.dev/notes/new' ]); + sleep(1); $noteWithoutContact = App\Note::create([ 'note' => 'Hi @bob', 'client_id' => 'https://quill.p3k.io' ]); + sleep(1); //copy aaron’s profile pic in place $spl = new SplFileInfo(public_path() . '/assets/profile-images/aaronparecki.com'); if ($spl->isDir() === false) { @@ -40,6 +45,7 @@ class NotesTableSeeder extends Seeder 'note' => 'Note from somehwere', 'location' => '53.499,-2.379' ]); + sleep(1); $noteSyndicated = App\Note::create([ 'note' => 'This note has all the syndication targets', 'tweet_id' => '123456', diff --git a/resources/views/likes/index.blade.php b/resources/views/likes/index.blade.php new file mode 100644 index 00000000..8c114d98 --- /dev/null +++ b/resources/views/likes/index.blade.php @@ -0,0 +1,27 @@ +@extends('master') + +@section('title') +Likes « +@stop + +@section('content') +
+@foreach($likes as $like) +
+
+ Liked a post by + + @isset($like->author_url) + {{ $like->author_name }} + @else + {{ $like->author_name }} + @endisset + : +
+ {!! $like->content !!} +
+
+
+@endforeach +
+@stop diff --git a/resources/views/likes/show.blade.php b/resources/views/likes/show.blade.php new file mode 100644 index 00000000..ca06c018 --- /dev/null +++ b/resources/views/likes/show.blade.php @@ -0,0 +1,23 @@ +@extends('master') + +@section('title') +Like « +@stop + +@section('content') +
+
+ Liked a post by + + @isset($like->author_url) + {{ $like->author_name }} + @else + {{ $like->author_name }} + @endisset + : +
+ {!! $like->content !!} +
+
+
+@stop diff --git a/routes/web.php b/routes/web.php index ebb209a0..ae1713bb 100644 --- a/routes/web.php +++ b/routes/web.php @@ -108,6 +108,12 @@ Route::group(['domain' => config('url.longurl')], function () { }); Route::get('note/{id}', 'NotesController@redirect'); // for legacy note URLs + // Likes + Route::group(['prefix' => 'likes'], function () { + Route::get('/', 'LikesController@index'); + Route::get('/{like}', 'LikesController@show'); + }); + // Micropub Client Route::group(['prefix' => 'micropub'], function () { Route::get('/create', 'MicropubClientController@create')->name('micropub-client'); diff --git a/tests/Feature/LikesTest.php b/tests/Feature/LikesTest.php new file mode 100644 index 00000000..e33f3d12 --- /dev/null +++ b/tests/Feature/LikesTest.php @@ -0,0 +1,88 @@ +get('/likes'); + $response->assertViewIs('likes.index'); + } + + public function test_single_like_page() + { + $response = $this->get('/likes'); + $response->assertViewIs('likes.index'); + } + + public function test_like_micropub_request() + { + Queue::fake(); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer ' . $this->getToken(), + ])->json('POST', '/api/post', [ + 'type' => ['h-entry'], + 'properties' => [ + 'like-of' => ['https://example.org/blog-post'], + ], + ]); + + $response->assertJson(['response' => 'created']); + + Queue::assertPushed(ProcessLike::class); + $this->assertDatabaseHas('likes', ['url' => 'https://example.org/blog-post']); + } + + public function test_process_like_job() + { + $like = new Like(); + $like->url = 'http://example.org/note/id'; + $like->save(); + $id = $like->id; + + $job = new ProcessLike($like); + + $content = << + +
+
+ A post that I like. +
+ by Fred Bloggs +
+ + +END; + $mock = new MockHandler([ + new Response(200, [], $content), + new Response(200, [], $content), + ]); + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + $this->app->bind(Client::class, $client); + $authorship = new Authorship(); + + $job->handle($client, $authorship); + + $this->assertEquals('Fred Bloggs', Like::find($id)->author_name); + } +} diff --git a/tests/TestToken.php b/tests/TestToken.php new file mode 100644 index 00000000..4cce0892 --- /dev/null +++ b/tests/TestToken.php @@ -0,0 +1,37 @@ +set('client_id', 'https://quill.p3k.io') + ->set('me', 'https://jonnybarnes.localhost') + ->set('scope', 'create update') + ->set('issued_at', time()) + ->sign($signer, env('APP_KEY')) + ->getToken(); + + return $token; + } + + public function getInvalidToken() + { + $signer = new Sha256(); + $token = (new Builder()) + ->set('client_id', 'https://quill.p3k.io') + ->set('me', 'https://jonnybarnes.localhost') + ->set('scope', 'view') //error here + ->set('issued_at', time()) + ->sign($signer, env('APP_KEY')) + ->getToken(); + + return $token; + } +}