diff --git a/app/Console/Commands/MigrateMedia.php b/app/Console/Commands/MigrateMedia.php deleted file mode 100644 index 987da9af..00000000 --- a/app/Console/Commands/MigrateMedia.php +++ /dev/null @@ -1,92 +0,0 @@ -hasTable('media_note')) { - $this->error('The table "media_note" does not exist.'); - - exit(1); - } - - // Load all media already saved in `media_endpoint` table - $this->line('Updating existing local media'); - $mediaEndpointMedia = Media::all(); - // Save relationship in new `media_note` table based on `media_endpoint.note_id` - $this->withProgressBar($mediaEndpointMedia, function (Media $mediaEndpointMediaItem) { - $note = Note::find($mediaEndpointMediaItem->note_id); - if ($note) { - $note->media()->attach($mediaEndpointMediaItem->id); - } - }); - - // Load all media records from `media` table - $this->line(''); - $this->line('Migrating old media from S3'); - $oldMedia = DB::table('media')->get(); - foreach ($oldMedia as $oldMediaItem) { - // We only want to process the S3 media - if ($oldMediaItem->disk !== 's3') { - $this->warn('Original media item never stored in S3'); - - continue; - } - - // Check media exists in S3 - if (! Storage::disk('s3')->exists($oldMediaItem->id.DIRECTORY_SEPARATOR.$oldMediaItem->file_name)) { - $this->warn('Original media item not found in S3'); - - continue; - } - // We want to just copy the file, check it does not already exist locally - if (Storage::disk('public')->exists('media'.DIRECTORY_SEPARATOR.$oldMediaItem->file_name)) { - $this->warn('File already exists locally with filename of original media item'); - - continue; - } - // Copy the file - Storage::disk('public')->writeStream('media'.DIRECTORY_SEPARATOR.$oldMediaItem->file_name, Storage::disk('s3')->readStream($oldMediaItem->id.DIRECTORY_SEPARATOR.$oldMediaItem->file_name)); - - // Save relationship based on `media.model_id` - // I have already checked they are all notes - $note = $oldMediaItem->model_id; - $newMediaItem = Media::create([ - 'path' => 'media'.DIRECTORY_SEPARATOR.$oldMediaItem->file_name, - 'type' => 'image', - ]); - $note->media()->attach($newMediaItem->id); - - $this->info('Media item migrated from S3'); - } - - $this->line(''); - $this->line('Migration finished'); - } -} diff --git a/app/Models/Media.php b/app/Models/Media.php index b25e889a..3d923bed 100644 --- a/app/Models/Media.php +++ b/app/Models/Media.php @@ -7,7 +7,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Support\Str; class Media extends Model @@ -20,11 +20,9 @@ class Media extends Model /** @var array */ protected $fillable = ['token', 'path', 'type', 'image_widths']; - public function notes(): BelongsToMany + public function note(): BelongsTo { - return $this->belongsToMany(Note::class) - ->withPivot('alt_text', 'order') - ->withTimestamps(); + return $this->belongsTo(Note::class); } protected function url(): Attribute diff --git a/app/Models/Note.php b/app/Models/Note.php index 91f4b8a1..74533443 100644 --- a/app/Models/Note.php +++ b/app/Models/Note.php @@ -14,6 +14,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Facades\Cache; @@ -91,12 +92,9 @@ class Note extends Model return $this->belongsTo(Place::class); } - public function media(): BelongsToMany + public function media(): HasMany { - return $this->BelongsToMany(Media::class) - ->withPivot('alt_text', 'order') - ->withTimestamps() - ->orderBy('order'); + return $this->hasMany(Media::class); } /** diff --git a/app/Services/NoteService.php b/app/Services/NoteService.php index ff77ef7b..c80a450c 100644 --- a/app/Services/NoteService.php +++ b/app/Services/NoteService.php @@ -51,11 +51,8 @@ class NoteService // $note->instagram_url = $this->getInstagramUrl($request); - foreach ($this->getMedia($data) as $index => $media) { - $note->media()->attach($media['value']->id, [ - 'alt_text' => $media['alt'], - 'order' => $index, - ]); + foreach ($this->getMedia($data) as $media) { + $note->media()->save($media); } $note->save(); @@ -198,37 +195,16 @@ class NoteService if (isset($photos)) { foreach ((array) $photos as $photo) { - // $photo can be a string of the URL opf the photo - // or it can be an object with a `value` and `alt` - $photoUrl = null; - $photoAlt = null; - if (is_string($photo)) { - $photoUrl = $photo; - } elseif (is_array($photo)) { - $photoUrl = $photo['value']; - $photoAlt = $photo['alt']; - } - - if (empty($photoUrl)) { - continue; - } - // check the media was uploaded to my endpoint, and use path - if (Str::startsWith($photoUrl, config('filesystems.disks.public.url'))) { - $path = substr($photoUrl, strlen(config('filesystems.disks.public.url'))); - $media[] = [ - 'value' => Media::where('path', ltrim($path, '/'))->firstOrFail(), - 'alt' => $photoAlt, - ]; + if (Str::startsWith($photo, config('filesystems.disks.public.url'))) { + $path = substr($photo, strlen(config('filesystems.disks.public.url'))); + $media[] = Media::where('path', ltrim($path, '/'))->firstOrFail(); } else { - $newMedia = Media::firstOrNew(['path' => $photoUrl]); + $newMedia = Media::firstOrNew(['path' => $photo]); // currently assuming this is a photo from Swarm or OwnYourGram $newMedia->type = 'image'; $newMedia->save(); - $media[] = [ - 'value' => $newMedia, - 'alt' => $photoAlt, - ]; + $media[] = $newMedia; } } } diff --git a/database/migrations/2025_12_31_110617_create_media_note_pivot_table.php b/database/migrations/2025_12_31_110617_create_media_note_pivot_table.php deleted file mode 100644 index 4017aefb..00000000 --- a/database/migrations/2025_12_31_110617_create_media_note_pivot_table.php +++ /dev/null @@ -1,33 +0,0 @@ -id(); - $table->foreignId('note_id')->constrained()->onDelete('cascade'); - $table->foreignId('media_id')->constrained('media_endpoint')->onDelete('cascade'); - $table->text('alt_text')->nullable(); - $table->integer('order')->default(0); - $table->timestamps(); - - $table->unique(['note_id', 'media_id']); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('media_note'); - } -}; diff --git a/database/seeders/NotesTableSeeder.php b/database/seeders/NotesTableSeeder.php index ea00c7d5..630cfda6 100644 --- a/database/seeders/NotesTableSeeder.php +++ b/database/seeders/NotesTableSeeder.php @@ -84,9 +84,7 @@ class NotesTableSeeder extends Seeder // copy aaron’s profile pic in place $spl = new SplFileInfo(public_path() . '/assets/profile-images/aaronparecki.com'); if ($spl->isDir() === false) { - if (! mkdir($concurrentDirectory = public_path() . '/assets/profile-images/aaronparecki.com', 0755) && ! is_dir($concurrentDirectory)) { - throw new \RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory)); - } + mkdir(public_path() . '/assets/profile-images/aaronparecki.com', 0755); copy(base_path() . '/tests/aaron.png', public_path() . '/assets/profile-images/aaronparecki.com/image'); } @@ -173,9 +171,7 @@ class NotesTableSeeder extends Seeder $noteWithOnlyImage->setCreatedAt($now); $noteWithOnlyImage->setUpdatedAt($now); $noteWithOnlyImage->save(); - $noteWithOnlyImage->media()->attach($media->id, [ - 'alt_text' => 'Test alt text', - ]); + $noteWithOnlyImage->media()->save($media); DB::table('notes') ->where('id', $noteWithOnlyImage->id) ->update(['updated_at' => $now->toDateTimeString()]); diff --git a/resources/views/templates/note.blade.php b/resources/views/templates/note.blade.php index e8418184..68c9d2f6 100644 --- a/resources/views/templates/note.blade.php +++ b/resources/views/templates/note.blade.php @@ -12,7 +12,7 @@ @foreach($note->media as $media) @if($media->type === 'image') - {{ $media->pivot->alt_text }}image_widths !== null) srcset="{{ $media->url }} {{ $media->image_widths }}w, {{ $media->mediumurl }} 1000w, {{ $media->smallurl }} 500w" sizes="min(80ch,80vw)" @else src="{{ $media->url }}" @endif> + image_widths !== null) srcset="{{ $media->url }} {{ $media->image_widths }}w, {{ $media->mediumurl }} 1000w, {{ $media->smallurl }} 500w" sizes="80vh"@endif> @endif @if($media->type === 'audio') diff --git a/tests/Feature/MicropubControllerTest.php b/tests/Feature/MicropubControllerTest.php index 8d10fc63..3d28f399 100644 --- a/tests/Feature/MicropubControllerTest.php +++ b/tests/Feature/MicropubControllerTest.php @@ -236,11 +236,11 @@ class MicropubControllerTest extends TestCase * Test a valid micropub requests using JSON syntax creates a new note. */ #[Test] - public function micropub_client_api_request_creates_new_note_with_photo(): void + public function micropub_client_api_request_creates_new_note(): void { Queue::fake(); Media::create([ - 'path' => 'media/test-photo.jpg', + 'path' => 'test-photo.jpg', 'type' => 'image', ]); SyndicationTarget::factory()->create([ @@ -265,13 +265,12 @@ class MicropubControllerTest extends TestCase 'https://mastodon.social/@jonnybarnes', 'https://bsky.app/profile/jonnybarnes.uk', ], - 'photo' => [config('filesystems.disks.public.url') . '/media/test-photo.jpg'], + 'photo' => [config('filesystems.disks.s3.url') . '/test-photo.jpg'], ], ], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()] ); $response - ->dump() ->assertStatus(201) ->assertJson(['response' => 'created']); Queue::assertPushed(SendWebMentions::class); diff --git a/tests/Unit/Jobs/ProcessMediaJobTest.php b/tests/Unit/Jobs/ProcessMediaJobTest.php index 29eb780c..a405fde8 100644 --- a/tests/Unit/Jobs/ProcessMediaJobTest.php +++ b/tests/Unit/Jobs/ProcessMediaJobTest.php @@ -43,6 +43,7 @@ class ProcessMediaJobTest extends TestCase { $manager = app()->make(ImageManager::class); Storage::disk('local')->put('media/test-image.jpg', file_get_contents(__DIR__.'/../../test-image.jpg')); + ray(Storage::disk('local')->get('media/test-image.jpg')); $job = new ProcessMedia('media/test-image.jpg'); $job->handle($manager); diff --git a/tests/Unit/MediaTest.php b/tests/Unit/MediaTest.php index 675f761d..642d797f 100644 --- a/tests/Unit/MediaTest.php +++ b/tests/Unit/MediaTest.php @@ -15,18 +15,11 @@ class MediaTest extends TestCase use RefreshDatabase; #[Test] - public function media_can_belong_to_multiple_notes(): void + public function get_the_note_that_media_instance_belongs_to(): void { - $media = Media::factory()->create(); - $note1 = Note::factory()->create(); - $note2 = Note::factory()->create(); + $media = Media::factory()->for(Note::factory())->create(); - $note1->media()->attach($media->id, ['alt_text' => 'Alt text for note 1']); - $note2->media()->attach($media->id, ['alt_text' => 'Alt text for note 2']); - - $this->assertCount(2, $media->notes); - $this->assertTrue($media->notes->contains($note1)); - $this->assertTrue($media->notes->contains($note2)); + $this->assertInstanceOf(Note::class, $media->note); } #[Test] diff --git a/tests/Unit/NotesTest.php b/tests/Unit/NotesTest.php index 330d93fe..e71f513a 100644 --- a/tests/Unit/NotesTest.php +++ b/tests/Unit/NotesTest.php @@ -434,21 +434,4 @@ class NotesTest extends TestCase $this->assertSame($expected, $note->note); } - - #[Test] - public function note_can_have_media_with_alt_text(): void - { - $note = Note::factory()->create(); - $media = Media::factory()->create(); - - $note->media()->attach($media->id, [ - 'alt_text' => 'Test alt text', - 'order' => 0, - ]); - - $note->refresh(); - - $this->assertCount(1, $note->media); - $this->assertEquals('Test alt text', $note->media->first()->pivot->alt_text); - } }