From aa1468c596787ae9b23ce7bf5c809c3e9a9c2aba Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Fri, 27 Sep 2024 12:30:05 +0530 Subject: [PATCH 01/24] WIP --- .env.example | 3 ++ app/Console/Commands/SyncArticleImages.php | 39 ++++++++++++++++++++++ config/services.php | 4 +++ 3 files changed, 46 insertions(+) create mode 100644 app/Console/Commands/SyncArticleImages.php diff --git a/.env.example b/.env.example index 0581c42f0..b9bedb501 100644 --- a/.env.example +++ b/.env.example @@ -49,6 +49,9 @@ TELEGRAM_CHANNEL= FATHOM_SITE_ID= FATHOM_TOKEN= + +UNSPLASH_ACCESS_KEY= + LOG_STACK=single SESSION_ENCRYPT=false SESSION_PATH=/ diff --git a/app/Console/Commands/SyncArticleImages.php b/app/Console/Commands/SyncArticleImages.php new file mode 100644 index 000000000..d041ab189 --- /dev/null +++ b/app/Console/Commands/SyncArticleImages.php @@ -0,0 +1,39 @@ +accessKey = config('services.unsplash.access_key'); + } + + public function handle(): void + { + if (! $this->accessKey) { + $this->error('Unsplash access key must be configured'); + + return; + } + + Article::published()->chunk(100, function ($articles) { + $articles->each(function ($article) { + if (! $article->hero_image) { + // Update Unsplash image URL + } + }); + }); + } +} diff --git a/config/services.php b/config/services.php index 1105eb382..89c11b623 100644 --- a/config/services.php +++ b/config/services.php @@ -40,4 +40,8 @@ 'token' => env('FATHOM_TOKEN'), ], + 'unsplash' => [ + 'acccess_key' => env('UNSPLASH_ACCESS_KEY'), + ], + ]; From b83f4ffe0544aee37d78ae25e1fabebcdcaf72ea Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Mon, 30 Sep 2024 11:02:22 +0530 Subject: [PATCH 02/24] WIP --- ..._add_hero_image_url_column_to_articles.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 database/migrations/2024_09_27_095949_add_hero_image_url_column_to_articles.php diff --git a/database/migrations/2024_09_27_095949_add_hero_image_url_column_to_articles.php b/database/migrations/2024_09_27_095949_add_hero_image_url_column_to_articles.php new file mode 100644 index 000000000..7d36802e7 --- /dev/null +++ b/database/migrations/2024_09_27_095949_add_hero_image_url_column_to_articles.php @@ -0,0 +1,28 @@ +string('hero_image_url')->nullable()->after('hero_image'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('articles', function (Blueprint $table) { + $table->dropColumn('hero_image_url'); + }); + } +}; From 56e2f918bdaca90c417259113d14735330d1db95 Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Mon, 30 Sep 2024 13:57:17 +0530 Subject: [PATCH 03/24] WIP --- app/Console/Commands/SyncArticleImages.php | 23 ++++++++++++++++++++-- config/services.php | 2 +- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/Console/Commands/SyncArticleImages.php b/app/Console/Commands/SyncArticleImages.php index d041ab189..c08cc87ed 100644 --- a/app/Console/Commands/SyncArticleImages.php +++ b/app/Console/Commands/SyncArticleImages.php @@ -4,6 +4,7 @@ use App\Models\Article; use Illuminate\Console\Command; +use Illuminate\Support\Facades\Http; final class SyncArticleImages extends Command { @@ -30,10 +31,28 @@ public function handle(): void Article::published()->chunk(100, function ($articles) { $articles->each(function ($article) { - if (! $article->hero_image) { - // Update Unsplash image URL + if ($article->hasHeroImage()) { + $article->hero_image_url = $this->getUnsplashImageUrlFromStringId($article->hero_image); + $article->save(); } }); }); } + + protected function getUnsplashImageUrlFromStringId(string $imageId): ?string + { + $response = Http::retry(3, 100, null, false)->withToken($this->accessKey, 'Client-ID') + ->get("https://api.unsplash.com/photos/{$imageId}"); + + if ($response->failed()) { + logger()->error('Failed to get raw image url from unsplash for', [ + 'imageId' => $imageId, + 'response' => $response->json(), + ]); + + return null; + } + + return (string) $response->json('urls.raw'); + } } diff --git a/config/services.php b/config/services.php index 89c11b623..2a575db66 100644 --- a/config/services.php +++ b/config/services.php @@ -41,7 +41,7 @@ ], 'unsplash' => [ - 'acccess_key' => env('UNSPLASH_ACCESS_KEY'), + 'access_key' => env('UNSPLASH_ACCESS_KEY'), ], ]; From 334a3d1291b4614a70f2a80dad18040f95b04bc0 Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Mon, 30 Sep 2024 14:15:07 +0530 Subject: [PATCH 04/24] WIP --- app/Models/Article.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/Article.php b/app/Models/Article.php index 9fa33d422..b59889b2b 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -106,7 +106,7 @@ public function hasHeroImage(): bool public function heroImage($width = 400, $height = 300): string { if ($this->hero_image) { - return "https://source.unsplash.com/{$this->hero_image}/{$width}x{$height}"; + return "{$this->hero_image_url}&fit=clip&w={$width}&h={$height}"; } return asset('images/default-background.svg'); From c2699a405466cf59d6deb3fc02c1e907f23663b2 Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Mon, 30 Sep 2024 15:54:59 +0530 Subject: [PATCH 05/24] CREATE - Tests --- app/Models/Article.php | 3 +- .../Commands/SyncArticleImagesTest.php | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/Integration/Commands/SyncArticleImagesTest.php diff --git a/app/Models/Article.php b/app/Models/Article.php index b59889b2b..281619c5d 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -45,6 +45,7 @@ final class Article extends Model implements Feedable 'original_url', 'slug', 'hero_image', + 'hero_image_url', 'is_pinned', 'view_count', 'tweet_id', @@ -105,7 +106,7 @@ public function hasHeroImage(): bool public function heroImage($width = 400, $height = 300): string { - if ($this->hero_image) { + if ($this->hero_image_url) { return "{$this->hero_image_url}&fit=clip&w={$width}&h={$height}"; } diff --git a/tests/Integration/Commands/SyncArticleImagesTest.php b/tests/Integration/Commands/SyncArticleImagesTest.php new file mode 100644 index 000000000..d59173fef --- /dev/null +++ b/tests/Integration/Commands/SyncArticleImagesTest.php @@ -0,0 +1,40 @@ +create([ + 'hero_image' => 'sxiSod0tyYQ', + 'submitted_at' => now(), + 'approved_at' => now(), + ]); + + (new SyncArticleImages)->handle(); + + $article->refresh(); + + expect($article->heroImage())->toBeUrl(); + expect($article->heroImage())->toContain('https://images.unsplash.com/photo-1584824486509-112e4181ff6b'); +}); + +test('hero image url is not updated for published articles with no hero image', function () { + + $article = Article::factory()->create([ + 'submitted_at' => now(), + 'approved_at' => now(), + ]); + + (new SyncArticleImages)->handle(); + + $article->refresh(); + + expect($article->hero_image_url)->toBe(null); +})->only(); From 3fc833035929ad3cb3dcb4ba9f9c1f74851359e7 Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Mon, 30 Sep 2024 15:55:40 +0530 Subject: [PATCH 06/24] WIP --- tests/Integration/Commands/SyncArticleImagesTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Integration/Commands/SyncArticleImagesTest.php b/tests/Integration/Commands/SyncArticleImagesTest.php index d59173fef..9238fb3fe 100644 --- a/tests/Integration/Commands/SyncArticleImagesTest.php +++ b/tests/Integration/Commands/SyncArticleImagesTest.php @@ -37,4 +37,4 @@ $article->refresh(); expect($article->hero_image_url)->toBe(null); -})->only(); +}); From 77927a1c485fdc631a40a74c94ef2268845da8fe Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Mon, 30 Sep 2024 15:55:57 +0530 Subject: [PATCH 07/24] WIP --- tests/Integration/Commands/SyncArticleImagesTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Integration/Commands/SyncArticleImagesTest.php b/tests/Integration/Commands/SyncArticleImagesTest.php index 9238fb3fe..5dbaa6cda 100644 --- a/tests/Integration/Commands/SyncArticleImagesTest.php +++ b/tests/Integration/Commands/SyncArticleImagesTest.php @@ -10,7 +10,6 @@ test('hero image url is updated for published articles with hero image', function () { - $article = Article::factory()->create([ 'hero_image' => 'sxiSod0tyYQ', 'submitted_at' => now(), @@ -26,7 +25,6 @@ }); test('hero image url is not updated for published articles with no hero image', function () { - $article = Article::factory()->create([ 'submitted_at' => now(), 'approved_at' => now(), From c4957ca22c86ff4090bce0c7f647075ab381323b Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Mon, 30 Sep 2024 17:28:19 +0530 Subject: [PATCH 08/24] WIP --- app/Console/Commands/SyncArticleImages.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/SyncArticleImages.php b/app/Console/Commands/SyncArticleImages.php index c08cc87ed..1c9346f68 100644 --- a/app/Console/Commands/SyncArticleImages.php +++ b/app/Console/Commands/SyncArticleImages.php @@ -32,14 +32,14 @@ public function handle(): void Article::published()->chunk(100, function ($articles) { $articles->each(function ($article) { if ($article->hasHeroImage()) { - $article->hero_image_url = $this->getUnsplashImageUrlFromStringId($article->hero_image); + $article->hero_image_url = $this->generateUnsplashImageUrlFromStringId($article->hero_image); $article->save(); } }); }); } - protected function getUnsplashImageUrlFromStringId(string $imageId): ?string + protected function generateUnsplashImageUrlFromStringId(string $imageId): ?string { $response = Http::retry(3, 100, null, false)->withToken($this->accessKey, 'Client-ID') ->get("https://api.unsplash.com/photos/{$imageId}"); From b2df385d9fde9a1eb832424142658ed594df8244 Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Mon, 30 Sep 2024 18:13:49 +0530 Subject: [PATCH 09/24] FIX - Test to fake api reponse for unsplash --- phpunit.xml | 1 + tests/Integration/Commands/SyncArticleImagesTest.php | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/phpunit.xml b/phpunit.xml index 8502c633e..5c508fe25 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -32,5 +32,6 @@ + diff --git a/tests/Integration/Commands/SyncArticleImagesTest.php b/tests/Integration/Commands/SyncArticleImagesTest.php index 5dbaa6cda..0a79d7b41 100644 --- a/tests/Integration/Commands/SyncArticleImagesTest.php +++ b/tests/Integration/Commands/SyncArticleImagesTest.php @@ -3,6 +3,7 @@ use App\Console\Commands\SyncArticleImages; use App\Models\Article; use Illuminate\Foundation\Testing\DatabaseMigrations; +use Illuminate\Support\Facades\Http; use Tests\TestCase; uses(TestCase::class); @@ -10,6 +11,14 @@ test('hero image url is updated for published articles with hero image', function () { + Http::fake(function () { + return [ + 'urls' => [ + 'raw' => 'https://images.unsplash.com/photo-1584824486509-112e4181ff6b?ixid=M3w2NTgwOTl8MHwxfGFsbHx8fHx8fHx8fDE3Mjc2ODMzMzZ8&ixlib=rb-4.0.3' + ] + ]; + }); + $article = Article::factory()->create([ 'hero_image' => 'sxiSod0tyYQ', 'submitted_at' => now(), From 1ebb625a53214ba1d75a411facbb5535f8769d03 Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Mon, 30 Sep 2024 18:14:54 +0530 Subject: [PATCH 10/24] WIP --- app/Console/Commands/SyncArticleImages.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/SyncArticleImages.php b/app/Console/Commands/SyncArticleImages.php index 1c9346f68..58fc15808 100644 --- a/app/Console/Commands/SyncArticleImages.php +++ b/app/Console/Commands/SyncArticleImages.php @@ -32,14 +32,14 @@ public function handle(): void Article::published()->chunk(100, function ($articles) { $articles->each(function ($article) { if ($article->hasHeroImage()) { - $article->hero_image_url = $this->generateUnsplashImageUrlFromStringId($article->hero_image); + $article->hero_image_url = $this->generateUnsplashImageUrlFromId($article->hero_image); $article->save(); } }); }); } - protected function generateUnsplashImageUrlFromStringId(string $imageId): ?string + protected function generateUnsplashImageUrlFromId(string $imageId): ?string { $response = Http::retry(3, 100, null, false)->withToken($this->accessKey, 'Client-ID') ->get("https://api.unsplash.com/photos/{$imageId}"); From dca7d6ddeaf12e5fec527b359c099b4e0b6a3616 Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Wed, 2 Oct 2024 23:18:10 +0530 Subject: [PATCH 11/24] WIP --- app/Console/Commands/SyncArticleImages.php | 20 +++++++++++++++---- app/Models/Article.php | 2 ++ ..._image_additional_columns_to_articles.php} | 12 +++++++++-- resources/views/articles/show.blade.php | 3 ++- 4 files changed, 30 insertions(+), 7 deletions(-) rename database/migrations/{2024_09_27_095949_add_hero_image_url_column_to_articles.php => 2024_09_27_095949_add_hero_image_additional_columns_to_articles.php} (52%) diff --git a/app/Console/Commands/SyncArticleImages.php b/app/Console/Commands/SyncArticleImages.php index 58fc15808..1b208c875 100644 --- a/app/Console/Commands/SyncArticleImages.php +++ b/app/Console/Commands/SyncArticleImages.php @@ -32,14 +32,20 @@ public function handle(): void Article::published()->chunk(100, function ($articles) { $articles->each(function ($article) { if ($article->hasHeroImage()) { - $article->hero_image_url = $this->generateUnsplashImageUrlFromId($article->hero_image); - $article->save(); + $imageData = $this->fetchUnsplashImageDataFromId($article->hero_image); + + if (!is_null($imageData)) { + $article->hero_image_url = $imageData['image_url']; + $article->hero_image_author_name = $imageData['author_name']; + $article->hero_image_author_url = $imageData['author_url']; + $article->save(); + } } }); }); } - protected function generateUnsplashImageUrlFromId(string $imageId): ?string + protected function fetchUnsplashImageDataFromId(string $imageId): ?array { $response = Http::retry(3, 100, null, false)->withToken($this->accessKey, 'Client-ID') ->get("https://api.unsplash.com/photos/{$imageId}"); @@ -53,6 +59,12 @@ protected function generateUnsplashImageUrlFromId(string $imageId): ?string return null; } - return (string) $response->json('urls.raw'); + $response = $response->json(); + + return [ + 'image_url' => $response['urls']['raw'], + 'author_name' => $response['user']['name'], + 'author_url' => $response['user']['links']['html'] + ]; } } diff --git a/app/Models/Article.php b/app/Models/Article.php index 281619c5d..ca002b5a6 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -46,6 +46,8 @@ final class Article extends Model implements Feedable 'slug', 'hero_image', 'hero_image_url', + 'hero_image_author_name', + 'hero_image_author_url', 'is_pinned', 'view_count', 'tweet_id', diff --git a/database/migrations/2024_09_27_095949_add_hero_image_url_column_to_articles.php b/database/migrations/2024_09_27_095949_add_hero_image_additional_columns_to_articles.php similarity index 52% rename from database/migrations/2024_09_27_095949_add_hero_image_url_column_to_articles.php rename to database/migrations/2024_09_27_095949_add_hero_image_additional_columns_to_articles.php index 7d36802e7..7fcc5a0cd 100644 --- a/database/migrations/2024_09_27_095949_add_hero_image_url_column_to_articles.php +++ b/database/migrations/2024_09_27_095949_add_hero_image_additional_columns_to_articles.php @@ -12,7 +12,11 @@ public function up(): void { Schema::table('articles', function (Blueprint $table) { - $table->string('hero_image_url')->nullable()->after('hero_image'); + $table->after('hero_image', function () use ($table) { + $table->string('hero_image_url')->nullable(); + $table->string('hero_image_author_name')->nullable(); + $table->string('hero_image_author_url')->nullable(); + }); }); } @@ -22,7 +26,11 @@ public function up(): void public function down(): void { Schema::table('articles', function (Blueprint $table) { - $table->dropColumn('hero_image_url'); + $table->dropColumn([ + 'hero_image_url', + 'hero_image_author_name', + 'hero_image_author_url', + ]); }); } }; diff --git a/resources/views/articles/show.blade.php b/resources/views/articles/show.blade.php index 26daea2ca..66582a9a6 100644 --- a/resources/views/articles/show.blade.php +++ b/resources/views/articles/show.blade.php @@ -85,6 +85,7 @@ class="w-full bg-center {{ $article->hasHeroImage() ? 'bg-cover' : '' }} bg-gray +

Photo by {{ $article->hero_image_author_name }} on Unsplash

@@ -250,4 +251,4 @@ class="prose prose-lg text-gray-800 prose-lio" @endif @endcan -@endsection +@endsection \ No newline at end of file From 2752e044a3346b12317dda39629e807bf2e22422 Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Thu, 3 Oct 2024 11:19:13 +0530 Subject: [PATCH 12/24] WIP --- app/Console/Commands/SyncArticleImages.php | 11 ++++++++++- app/Models/Article.php | 8 +++++++- resources/views/articles/show.blade.php | 7 ++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/app/Console/Commands/SyncArticleImages.php b/app/Console/Commands/SyncArticleImages.php index 1b208c875..a323075a9 100644 --- a/app/Console/Commands/SyncArticleImages.php +++ b/app/Console/Commands/SyncArticleImages.php @@ -31,7 +31,7 @@ public function handle(): void Article::published()->chunk(100, function ($articles) { $articles->each(function ($article) { - if ($article->hasHeroImage()) { + if ($this->shouldBeSynced($article)) { $imageData = $this->fetchUnsplashImageDataFromId($article->hero_image); if (!is_null($imageData)) { @@ -45,6 +45,15 @@ public function handle(): void }); } + protected function shouldBeSynced(Article $article): bool + { + if ($article->hero_image && ! $article->hasHeroImage()) { + return false; + } + + return true; + } + protected function fetchUnsplashImageDataFromId(string $imageId): ?array { $response = Http::retry(3, 100, null, false)->withToken($this->accessKey, 'Client-ID') diff --git a/app/Models/Article.php b/app/Models/Article.php index ca002b5a6..86245b12a 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -103,7 +103,13 @@ public function excerpt(int $limit = 100): string public function hasHeroImage(): bool { - return $this->hero_image !== null; + return $this->hero_image_url !== null; + } + + public function hasHeroImageAuthor(): bool + { + return $this->hero_image_author_name !== null && + $this->hero_image_author_url !== null; } public function heroImage($width = 400, $height = 300): string diff --git a/resources/views/articles/show.blade.php b/resources/views/articles/show.blade.php index 66582a9a6..0b52f18e4 100644 --- a/resources/views/articles/show.blade.php +++ b/resources/views/articles/show.blade.php @@ -85,7 +85,12 @@ class="w-full bg-center {{ $article->hasHeroImage() ? 'bg-cover' : '' }} bg-gray -

Photo by {{ $article->hero_image_author_name }} on Unsplash

+ + @if($article->hasHeroImageAuthor()) +

+ Photo by {{ $article->hero_image_author_name }} on Unsplash +

+ @endif From 89532b0afb7ecd8267671e58c21403054997798e Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Thu, 3 Oct 2024 11:31:57 +0530 Subject: [PATCH 13/24] WIP --- app/Console/Commands/SyncArticleImages.php | 6 +++++- .../Commands/SyncArticleImagesTest.php | 19 ++++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/app/Console/Commands/SyncArticleImages.php b/app/Console/Commands/SyncArticleImages.php index a323075a9..9b98ebb16 100644 --- a/app/Console/Commands/SyncArticleImages.php +++ b/app/Console/Commands/SyncArticleImages.php @@ -47,7 +47,11 @@ public function handle(): void protected function shouldBeSynced(Article $article): bool { - if ($article->hero_image && ! $article->hasHeroImage()) { + if (!$article->hero_image) { + return false; + } + + if ($article->hero_image && $article->hasHeroImage()) { return false; } diff --git a/tests/Integration/Commands/SyncArticleImagesTest.php b/tests/Integration/Commands/SyncArticleImagesTest.php index 0a79d7b41..8d2ef09b1 100644 --- a/tests/Integration/Commands/SyncArticleImagesTest.php +++ b/tests/Integration/Commands/SyncArticleImagesTest.php @@ -10,12 +10,18 @@ uses(DatabaseMigrations::class); -test('hero image url is updated for published articles with hero image', function () { +test('hero image url and author information is updated for published articles with hero image', function () { Http::fake(function () { return [ 'urls' => [ 'raw' => 'https://images.unsplash.com/photo-1584824486509-112e4181ff6b?ixid=M3w2NTgwOTl8MHwxfGFsbHx8fHx8fHx8fDE3Mjc2ODMzMzZ8&ixlib=rb-4.0.3' - ] + ], + 'user' => [ + 'name' => 'Erik Mclean', + 'links' => [ + 'html' => 'https://unsplash.com/@introspectivedsgn', + ] + ], ]; }); @@ -29,11 +35,12 @@ $article->refresh(); - expect($article->heroImage())->toBeUrl(); expect($article->heroImage())->toContain('https://images.unsplash.com/photo-1584824486509-112e4181ff6b'); + expect($article->hero_image_author_name)->toBe('Erik Mclean'); + expect($article->hero_image_author_url)->toBe('https://unsplash.com/@introspectivedsgn'); }); -test('hero image url is not updated for published articles with no hero image', function () { +test('hero image url and author information is not updated for published articles with no hero image', function () { $article = Article::factory()->create([ 'submitted_at' => now(), 'approved_at' => now(), @@ -44,4 +51,6 @@ $article->refresh(); expect($article->hero_image_url)->toBe(null); -}); + expect($article->hero_image_author_name)->toBe(null); + expect($article->hero_image_author_url)->toBe(null); +})->only(); From af2c054db4196c3c12218948f489a8837c2353b7 Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Thu, 3 Oct 2024 11:32:05 +0530 Subject: [PATCH 14/24] WIP --- tests/Integration/Commands/SyncArticleImagesTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Integration/Commands/SyncArticleImagesTest.php b/tests/Integration/Commands/SyncArticleImagesTest.php index 8d2ef09b1..75fac11f8 100644 --- a/tests/Integration/Commands/SyncArticleImagesTest.php +++ b/tests/Integration/Commands/SyncArticleImagesTest.php @@ -53,4 +53,4 @@ expect($article->hero_image_url)->toBe(null); expect($article->hero_image_author_name)->toBe(null); expect($article->hero_image_author_url)->toBe(null); -})->only(); +}); From 9928dc3b0b175740b1dcace891325a64fcd23f7d Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Thu, 3 Oct 2024 12:20:23 +0530 Subject: [PATCH 15/24] WIP --- app/Console/Commands/SyncArticleImages.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/Console/Commands/SyncArticleImages.php b/app/Console/Commands/SyncArticleImages.php index 9b98ebb16..f6650c02e 100644 --- a/app/Console/Commands/SyncArticleImages.php +++ b/app/Console/Commands/SyncArticleImages.php @@ -47,11 +47,7 @@ public function handle(): void protected function shouldBeSynced(Article $article): bool { - if (!$article->hero_image) { - return false; - } - - if ($article->hero_image && $article->hasHeroImage()) { + if (!$article->hero_image || $article->hasHeroImage()) { return false; } From 1c32da429b989225f738ad882a0739eaad8c2bb8 Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Thu, 3 Oct 2024 12:21:41 +0530 Subject: [PATCH 16/24] WIP --- app/Console/Commands/SyncArticleImages.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/SyncArticleImages.php b/app/Console/Commands/SyncArticleImages.php index f6650c02e..79fd3431b 100644 --- a/app/Console/Commands/SyncArticleImages.php +++ b/app/Console/Commands/SyncArticleImages.php @@ -31,7 +31,7 @@ public function handle(): void Article::published()->chunk(100, function ($articles) { $articles->each(function ($article) { - if ($this->shouldBeSynced($article)) { + if ($this->checkShouldArticleBeSynced($article)) { $imageData = $this->fetchUnsplashImageDataFromId($article->hero_image); if (!is_null($imageData)) { @@ -45,7 +45,7 @@ public function handle(): void }); } - protected function shouldBeSynced(Article $article): bool + protected function checkShouldArticleBeSynced(Article $article): bool { if (!$article->hero_image || $article->hasHeroImage()) { return false; From a40519926d7eae81b85742de3241afb249598044 Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Thu, 3 Oct 2024 12:28:05 +0530 Subject: [PATCH 17/24] WIP --- app/Console/Commands/SyncArticleImages.php | 4 +-- app/Models/Article.php | 2 +- ...ero_image_to_hero_image_id_in_articles.php | 28 +++++++++++++++++++ .../Commands/SyncArticleImagesTest.php | 2 +- 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 database/migrations/2024_10_03_065334_rename_hero_image_to_hero_image_id_in_articles.php diff --git a/app/Console/Commands/SyncArticleImages.php b/app/Console/Commands/SyncArticleImages.php index 79fd3431b..3dfa780ef 100644 --- a/app/Console/Commands/SyncArticleImages.php +++ b/app/Console/Commands/SyncArticleImages.php @@ -32,7 +32,7 @@ public function handle(): void Article::published()->chunk(100, function ($articles) { $articles->each(function ($article) { if ($this->checkShouldArticleBeSynced($article)) { - $imageData = $this->fetchUnsplashImageDataFromId($article->hero_image); + $imageData = $this->fetchUnsplashImageDataFromId($article->hero_image_id); if (!is_null($imageData)) { $article->hero_image_url = $imageData['image_url']; @@ -47,7 +47,7 @@ public function handle(): void protected function checkShouldArticleBeSynced(Article $article): bool { - if (!$article->hero_image || $article->hasHeroImage()) { + if (!$article->hero_image_id || $article->hasHeroImage()) { return false; } diff --git a/app/Models/Article.php b/app/Models/Article.php index 86245b12a..b79ec7152 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -44,7 +44,7 @@ final class Article extends Model implements Feedable 'body', 'original_url', 'slug', - 'hero_image', + 'hero_image_id', 'hero_image_url', 'hero_image_author_name', 'hero_image_author_url', diff --git a/database/migrations/2024_10_03_065334_rename_hero_image_to_hero_image_id_in_articles.php b/database/migrations/2024_10_03_065334_rename_hero_image_to_hero_image_id_in_articles.php new file mode 100644 index 000000000..9c72d9aa1 --- /dev/null +++ b/database/migrations/2024_10_03_065334_rename_hero_image_to_hero_image_id_in_articles.php @@ -0,0 +1,28 @@ +renameColumn('hero_image', 'hero_image_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('articles', function (Blueprint $table) { + $table->renameColumn('hero_image_id', 'hero_image'); + }); + } +}; diff --git a/tests/Integration/Commands/SyncArticleImagesTest.php b/tests/Integration/Commands/SyncArticleImagesTest.php index 75fac11f8..001bdc7a4 100644 --- a/tests/Integration/Commands/SyncArticleImagesTest.php +++ b/tests/Integration/Commands/SyncArticleImagesTest.php @@ -26,7 +26,7 @@ }); $article = Article::factory()->create([ - 'hero_image' => 'sxiSod0tyYQ', + 'hero_image_id' => 'sxiSod0tyYQ', 'submitted_at' => now(), 'approved_at' => now(), ]); From cc33a3949299b0ff4224f4170c229d2556ede221 Mon Sep 17 00:00:00 2001 From: Tauseef Shah Date: Thu, 3 Oct 2024 12:35:14 +0530 Subject: [PATCH 18/24] WIP --- app/Models/Article.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/Article.php b/app/Models/Article.php index b79ec7152..82595776b 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -114,7 +114,7 @@ public function hasHeroImageAuthor(): bool public function heroImage($width = 400, $height = 300): string { - if ($this->hero_image_url) { + if ($this->hasHeroImage()) { return "{$this->hero_image_url}&fit=clip&w={$width}&h={$height}"; } From 93d2e315e964889ad22ae1ccae3c61b5dfa934df Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 3 Oct 2024 21:09:41 +0200 Subject: [PATCH 19/24] wip --- app/Console/Commands/SyncArticleImages.php | 41 +++++-------------- app/Models/Article.php | 6 +++ ...o_image_additional_columns_to_articles.php | 12 +----- ...ero_image_to_hero_image_id_in_articles.php | 28 ------------- database/seeders/UserSeeder.php | 2 +- resources/views/articles/show.blade.php | 4 +- 6 files changed, 21 insertions(+), 72 deletions(-) delete mode 100644 database/migrations/2024_10_03_065334_rename_hero_image_to_hero_image_id_in_articles.php diff --git a/app/Console/Commands/SyncArticleImages.php b/app/Console/Commands/SyncArticleImages.php index 3dfa780ef..4636c0fad 100644 --- a/app/Console/Commands/SyncArticleImages.php +++ b/app/Console/Commands/SyncArticleImages.php @@ -10,53 +10,34 @@ final class SyncArticleImages extends Command { protected $signature = 'lio:sync-article-images'; - protected $description = 'Updates the Unsplash image for all articles'; - - protected $accessKey; - - public function __construct() - { - parent::__construct(); - - $this->accessKey = config('services.unsplash.access_key'); - } + protected $description = 'Updates the Unsplash image for all unsynced articles'; public function handle(): void { - if (! $this->accessKey) { + if (! config('services.unsplash.access_key')) { $this->error('Unsplash access key must be configured'); return; } - Article::published()->chunk(100, function ($articles) { + Article::unsyncedImages()->chunk(100, function ($articles) { $articles->each(function ($article) { - if ($this->checkShouldArticleBeSynced($article)) { - $imageData = $this->fetchUnsplashImageDataFromId($article->hero_image_id); + $imageData = $this->fetchUnsplashImageDataFromId($article->hero_image_id); - if (!is_null($imageData)) { - $article->hero_image_url = $imageData['image_url']; - $article->hero_image_author_name = $imageData['author_name']; - $article->hero_image_author_url = $imageData['author_url']; - $article->save(); - } + if (! is_null($imageData)) { + $article->hero_image_url = $imageData['image_url']; + $article->hero_image_author_name = $imageData['author_name']; + $article->hero_image_author_url = $imageData['author_url']; + $article->save(); } }); }); } - protected function checkShouldArticleBeSynced(Article $article): bool - { - if (!$article->hero_image_id || $article->hasHeroImage()) { - return false; - } - - return true; - } - protected function fetchUnsplashImageDataFromId(string $imageId): ?array { - $response = Http::retry(3, 100, null, false)->withToken($this->accessKey, 'Client-ID') + $response = Http::retry(3, 100, throw: false) + ->withToken(config('services.unsplash.access_key'), 'Client-ID') ->get("https://api.unsplash.com/photos/{$imageId}"); if ($response->failed()) { diff --git a/app/Models/Article.php b/app/Models/Article.php index 82595776b..80f3e345a 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -318,6 +318,12 @@ public function scopeTrending(Builder $query): Builder ->orderBy('submitted_at', 'desc'); } + public function scopeUnsyncedImages(Builder $query): Builder + { + return $query->whereNotNull('hero_image_id') + ->whereNull('hero_image_url'); + } + public function shouldBeSearchable() { return $this->isPublished(); diff --git a/database/migrations/2024_09_27_095949_add_hero_image_additional_columns_to_articles.php b/database/migrations/2024_09_27_095949_add_hero_image_additional_columns_to_articles.php index 7fcc5a0cd..fb7f1174e 100644 --- a/database/migrations/2024_09_27_095949_add_hero_image_additional_columns_to_articles.php +++ b/database/migrations/2024_09_27_095949_add_hero_image_additional_columns_to_articles.php @@ -18,19 +18,9 @@ public function up(): void $table->string('hero_image_author_url')->nullable(); }); }); - } - /** - * Reverse the migrations. - */ - public function down(): void - { Schema::table('articles', function (Blueprint $table) { - $table->dropColumn([ - 'hero_image_url', - 'hero_image_author_name', - 'hero_image_author_url', - ]); + $table->renameColumn('hero_image', 'hero_image_id'); }); } }; diff --git a/database/migrations/2024_10_03_065334_rename_hero_image_to_hero_image_id_in_articles.php b/database/migrations/2024_10_03_065334_rename_hero_image_to_hero_image_id_in_articles.php deleted file mode 100644 index 9c72d9aa1..000000000 --- a/database/migrations/2024_10_03_065334_rename_hero_image_to_hero_image_id_in_articles.php +++ /dev/null @@ -1,28 +0,0 @@ -renameColumn('hero_image', 'hero_image_id'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::table('articles', function (Blueprint $table) { - $table->renameColumn('hero_image_id', 'hero_image'); - }); - } -}; diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php index 48e5d4632..5c3fe18a4 100644 --- a/database/seeders/UserSeeder.php +++ b/database/seeders/UserSeeder.php @@ -44,7 +44,7 @@ public function run(): void [ 'submitted_at' => now(), 'approved_at' => now(), - 'hero_image' => 'sxiSod0tyYQ', + 'hero_image_id' => 'sxiSod0tyYQ', ], ['submitted_at' => now(), 'approved_at' => now()], ['submitted_at' => now()], diff --git a/resources/views/articles/show.blade.php b/resources/views/articles/show.blade.php index 0b52f18e4..86ac30ced 100644 --- a/resources/views/articles/show.blade.php +++ b/resources/views/articles/show.blade.php @@ -86,7 +86,7 @@ class="w-full bg-center {{ $article->hasHeroImage() ? 'bg-cover' : '' }} bg-gray - @if($article->hasHeroImageAuthor()) + @if ($article->hasHeroImageAuthor())

Photo by {{ $article->hero_image_author_name }} on Unsplash

@@ -256,4 +256,4 @@ class="prose prose-lg text-gray-800 prose-lio" @endif @endcan -@endsection \ No newline at end of file +@endsection From fc3a6f0b7079449383e6449c47710b28a5059e0a Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 3 Oct 2024 21:17:44 +0200 Subject: [PATCH 20/24] wip --- phpunit.xml | 2 +- tests/Integration/Commands/SyncArticleImagesTest.php | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 5c508fe25..3873884ea 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -32,6 +32,6 @@ - + diff --git a/tests/Integration/Commands/SyncArticleImagesTest.php b/tests/Integration/Commands/SyncArticleImagesTest.php index 001bdc7a4..8b6298575 100644 --- a/tests/Integration/Commands/SyncArticleImagesTest.php +++ b/tests/Integration/Commands/SyncArticleImagesTest.php @@ -3,6 +3,7 @@ use App\Console\Commands\SyncArticleImages; use App\Models\Article; use Illuminate\Foundation\Testing\DatabaseMigrations; +use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Http; use Tests\TestCase; @@ -11,6 +12,8 @@ test('hero image url and author information is updated for published articles with hero image', function () { + Config::set('services.unsplash.access_key', 'test'); + Http::fake(function () { return [ 'urls' => [ @@ -41,6 +44,8 @@ }); test('hero image url and author information is not updated for published articles with no hero image', function () { + Config::set('services.unsplash.access_key', 'test'); + $article = Article::factory()->create([ 'submitted_at' => now(), 'approved_at' => now(), From f0d028dfdd48cb2b8b0571868ba66340069630ae Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 8 Oct 2024 22:18:34 +0200 Subject: [PATCH 21/24] wip --- app/Models/Article.php | 2 +- resources/views/articles/show.blade.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Models/Article.php b/app/Models/Article.php index 80f3e345a..485274134 100644 --- a/app/Models/Article.php +++ b/app/Models/Article.php @@ -115,7 +115,7 @@ public function hasHeroImageAuthor(): bool public function heroImage($width = 400, $height = 300): string { if ($this->hasHeroImage()) { - return "{$this->hero_image_url}&fit=clip&w={$width}&h={$height}"; + return "{$this->hero_image_url}&fit=clip&w={$width}&h={$height}&utm_source=Laravel.io&utm_medium=referral"; } return asset('images/default-background.svg'); diff --git a/resources/views/articles/show.blade.php b/resources/views/articles/show.blade.php index 86ac30ced..b473b2936 100644 --- a/resources/views/articles/show.blade.php +++ b/resources/views/articles/show.blade.php @@ -88,7 +88,7 @@ class="w-full bg-center {{ $article->hasHeroImage() ? 'bg-cover' : '' }} bg-gray @if ($article->hasHeroImageAuthor())

- Photo by {{ $article->hero_image_author_name }} on Unsplash + Photo by {{ $article->hero_image_author_name }} on Unsplash

@endif From 039927e5c62801cf420e08bc04ab87fabb95b399 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 24 Oct 2024 20:39:13 +0200 Subject: [PATCH 22/24] wip --- app/Console/Commands/SyncArticleImages.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/Console/Commands/SyncArticleImages.php b/app/Console/Commands/SyncArticleImages.php index 4636c0fad..8d57ad150 100644 --- a/app/Console/Commands/SyncArticleImages.php +++ b/app/Console/Commands/SyncArticleImages.php @@ -51,6 +51,11 @@ protected function fetchUnsplashImageDataFromId(string $imageId): ?array $response = $response->json(); + // Trigger as download... + Http::retry(3, 100, throw: false) + ->withToken(config('services.unsplash.access_key'), 'Client-ID') + ->get($response['links']['download_location']); + return [ 'image_url' => $response['urls']['raw'], 'author_name' => $response['user']['name'], From 51c1d62e747ad9db964bed179b3645541a72e955 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Fri, 1 Nov 2024 11:33:12 +0100 Subject: [PATCH 23/24] wip --- tests/Integration/Commands/SyncArticleImagesTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Integration/Commands/SyncArticleImagesTest.php b/tests/Integration/Commands/SyncArticleImagesTest.php index 8b6298575..a545d8d00 100644 --- a/tests/Integration/Commands/SyncArticleImagesTest.php +++ b/tests/Integration/Commands/SyncArticleImagesTest.php @@ -10,7 +10,6 @@ uses(TestCase::class); uses(DatabaseMigrations::class); - test('hero image url and author information is updated for published articles with hero image', function () { Config::set('services.unsplash.access_key', 'test'); From 37eb0111d7bad54b4d5ebd36d8df3082d0b60c3f Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Fri, 1 Nov 2024 11:38:14 +0100 Subject: [PATCH 24/24] wip --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 75f4d77f7..ce2d15213 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,20 @@ FATHOM_SITE_ID= FATHOM_TOKEN= ``` +### Unsplash (optional) + +To make sure article and user header images get synced into the database we'll need to setup an access key from [Unsplash](https://unsplash.com/developers). Please note that your Unsplash app requires production access. + +``` +UNSPLASH_ACCESS_KEY= +``` + +After that you can add an Unsplash photo ID to any article row in the `hero_image_id` column and run the sync command to fetch the image url and author data: + +```bash +php artisan lio:sync-article-images +``` + ## Commands Command | Description