Skip to content

Commit 1aeaddd

Browse files
zigzagdevclaude
andcommitted
feat: store and serve YouTube thumbnails for heritage images
- Migration: add main_video_url column to world_heritage_sites - Command: world-heritage:sync-video-urls fetches main_video_url from data.unesco.org API and populates DB (1248 records synced) - Model: add main_video_url to WorldHeritage fillable - Controller: look up main_video_url from DB instead of calling UNESCO API on every request, then proxy YouTube hqdefault thumbnail Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent db13051 commit 1aeaddd

4 files changed

Lines changed: 122 additions & 13 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
namespace App\Console\Commands;
4+
5+
use Illuminate\Console\Command;
6+
use Illuminate\Support\Facades\DB;
7+
use Illuminate\Support\Facades\Http;
8+
9+
class SyncWorldHeritageVideoUrls extends Command
10+
{
11+
protected $signature = 'world-heritage:sync-video-urls
12+
{--limit=100 : Records per API request}
13+
{--dry-run : Show counts without updating DB}';
14+
15+
protected $description = 'Fetch main_video_url from data.unesco.org API and update world_heritage_sites';
16+
17+
private const API_URL = 'https://data.unesco.org/api/explore/v2.1/catalog/datasets/whc001/records';
18+
19+
public function handle(): int
20+
{
21+
$limit = max(1, (int) $this->option('limit'));
22+
$dryRun = (bool) $this->option('dry-run');
23+
24+
$first = $this->fetch(1, 0);
25+
if ($first === null) {
26+
return self::FAILURE;
27+
}
28+
29+
$total = (int) ($first['total_count'] ?? 0);
30+
$offset = 0;
31+
$updated = 0;
32+
$skipped = 0;
33+
34+
$this->info("Total UNESCO records: {$total}");
35+
36+
while ($offset < $total) {
37+
$response = $this->fetch($limit, $offset);
38+
if ($response === null) {
39+
return self::FAILURE;
40+
}
41+
42+
$results = $response['results'] ?? [];
43+
if ($results === []) {
44+
break;
45+
}
46+
47+
foreach ($results as $row) {
48+
$idNo = (int) ($row['id_no'] ?? 0);
49+
$videoUrl = $row['main_video_url'] ?? null;
50+
51+
if ($idNo === 0) {
52+
$skipped++;
53+
continue;
54+
}
55+
56+
if (!$dryRun) {
57+
DB::table('world_heritage_sites')
58+
->where('id', $idNo)
59+
->update(['main_video_url' => $videoUrl]);
60+
}
61+
62+
$updated++;
63+
}
64+
65+
$offset += count($results);
66+
$this->line("Processed {$offset}/{$total}...");
67+
}
68+
69+
if ($dryRun) {
70+
$this->info("Dry-run: would update {$updated} records, skipped {$skipped}.");
71+
} else {
72+
$this->info("Done. Updated {$updated} records, skipped {$skipped}.");
73+
}
74+
75+
return self::SUCCESS;
76+
}
77+
78+
private function fetch(int $limit, int $offset): ?array
79+
{
80+
$response = Http::acceptJson()
81+
->retry(3, 500)
82+
->get(self::API_URL, [
83+
'select' => 'id_no,main_video_url',
84+
'limit' => $limit,
85+
'offset' => $offset,
86+
'order_by' => 'id_no asc',
87+
]);
88+
89+
if ($response->failed()) {
90+
$this->error("UNESCO API error: HTTP {$response->status()} offset={$offset}");
91+
return null;
92+
}
93+
94+
return $response->json();
95+
}
96+
}

src/app/Models/WorldHeritage.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class WorldHeritage extends Model
3636
'short_description',
3737
'unesco_site_url',
3838
'main_image_url',
39+
'main_video_url',
3940
];
4041

4142
protected $hidden = [

src/app/Packages/Features/Controller/HeritageImageController.php

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use App\Http\Controllers\Controller;
66
use App\Models\Image;
7+
use App\Models\WorldHeritage;
78
use Illuminate\Http\Request;
89
use Illuminate\Http\Response;
910
use Illuminate\Http\JsonResponse;
@@ -37,20 +38,9 @@ public function proxyImage(Request $request, int $id): Response|JsonResponse
3738

3839
private function fetchVideoUrlFromUnesco(int $idNo): ?string
3940
{
40-
$response = Http::acceptJson()
41-
->get('https://data.unesco.org/api/explore/v2.1/catalog/datasets/whc001/records', [
42-
'where' => "id_no={$idNo}",
43-
'limit' => 1,
44-
'select' => 'main_video_url',
45-
]);
46-
47-
if ($response->failed()) {
48-
return null;
49-
}
50-
51-
$results = $response->json('results');
41+
$site = WorldHeritage::find($idNo);
5242

53-
return $results[0]['main_video_url'] ?? null;
43+
return $site?->main_video_url;
5444
}
5545

5646
private function buildYoutubeThumbnailUrl(string $videoUrl): ?string
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
public function up(): void
10+
{
11+
Schema::table('world_heritage_sites', function (Blueprint $table) {
12+
$table->string('main_video_url')->nullable()->after('main_image_url');
13+
});
14+
}
15+
16+
public function down(): void
17+
{
18+
Schema::table('world_heritage_sites', function (Blueprint $table) {
19+
$table->dropColumn('main_video_url');
20+
});
21+
}
22+
};

0 commit comments

Comments
 (0)