s3 config
This commit is contained in:
parent
d9dca29bc9
commit
093c538752
19 changed files with 751 additions and 40 deletions
61
app/Console/Commands/RemediateBlogS3Images.php
Normal file
61
app/Console/Commands/RemediateBlogS3Images.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Entry;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use function Livewire\str;
|
||||
|
||||
class RemediateBlogS3Images extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:remediate-blog-s3-images';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command description';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$stringToFind = 'src="http://127.0.0.1:8000/storage';
|
||||
$stringToReplace = 'src="https://your-s3-bucket-here/yours-site-static-media-dir-here';
|
||||
|
||||
Log::info('RemediateBlogS3Images command executed.');
|
||||
foreach (Entry::all() as $entry) {
|
||||
|
||||
$this->info('Entry ID: ' . $entry->id);
|
||||
if (str_contains($entry->content, $stringToFind)) {
|
||||
$this->info(' - Found occurrence in entry ID: ' . $entry->id);
|
||||
// Extract all image srcs that match the pattern
|
||||
preg_match_all('/src=\"http:\/\/127.0.0.1:8000\/storage([^\"]*)/', $entry->content, $matches);
|
||||
if (!empty($matches[0])) {
|
||||
foreach ($matches[0] as $i => $foundUrl) {
|
||||
$this->info(' - Found image src: ' . $foundUrl);
|
||||
// Compute the replacement for this specific image
|
||||
$relativePath = $matches[1][$i] ?? '';
|
||||
$newUrl = 'src="https://your-s3-bucket-here/yours-site-static-media-dir-here' . $relativePath;
|
||||
$this->info(' - Will replace with: ' . $newUrl);
|
||||
}
|
||||
}
|
||||
$updatedContent = \str_replace($stringToFind, $stringToReplace, $entry->content);
|
||||
// uncomment the following when your sure about the changes
|
||||
// $entry->content = $updatedContent;
|
||||
// $entry->save();
|
||||
// $this->info(' - Updated entry ID: ' . $entry->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ use Filament\Forms\Components\Textarea;
|
|||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Schemas\Schema;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\MediaLibrary\MediaCollections\Models\Media;
|
||||
|
||||
|
|
@ -22,6 +23,16 @@ class EntryForm
|
|||
{
|
||||
return $schema
|
||||
->components([
|
||||
Select::make('type')
|
||||
->options([
|
||||
'article' => 'Article',
|
||||
'card' => 'Card',
|
||||
'text' => 'Text',
|
||||
'image' => 'Image',
|
||||
])
|
||||
->default('article')
|
||||
->required()
|
||||
->live(),
|
||||
TextInput::make('title')
|
||||
->required()
|
||||
->live(onBlur: true)
|
||||
|
|
@ -30,21 +41,112 @@ class EntryForm
|
|||
}),
|
||||
TextInput::make('slug')
|
||||
->required()
|
||||
->visible(fn($get) => $get('type') === 'article')
|
||||
->dehydrated()
|
||||
->readOnly(),
|
||||
Textarea::make('description')
|
||||
->visible(fn($get) => $get('type') === 'article')
|
||||
->columnSpanFull(),
|
||||
SpatieTagsInput::make('tags')
|
||||
->type('entry-tags')
|
||||
->visible(fn($get) => $get('type') === 'article')
|
||||
->columnSpanFull(),
|
||||
SpatieMediaLibraryFileUpload::make('featured_image')
|
||||
->visible(
|
||||
fn($get) =>
|
||||
$get('type') === 'article' || $get('type') === 'image'
|
||||
)
|
||||
->collection('featured-image')
|
||||
->image()
|
||||
->imageEditor()
|
||||
->disk('public')
|
||||
->disk(config('media-library.disk_name', 'public'))
|
||||
->visibility('public')
|
||||
->columnSpanFull()
|
||||
->dehydrated(false)
|
||||
->saveUploadedFileUsing(function ($file, $record) {
|
||||
$diskName = config('media-library.disk_name', 'public');
|
||||
|
||||
if (config('app.env') === 'local') {
|
||||
Log::info('Featured Image Upload Debug', [
|
||||
'disk' => $diskName,
|
||||
'file_name' => $file->getClientOriginalName(),
|
||||
'file_size' => $file->getSize(),
|
||||
'file_mime' => $file->getMimeType(),
|
||||
'file_path' => $file->getRealPath(),
|
||||
'record_id' => $record?->id,
|
||||
'aws_config' => [
|
||||
'bucket' => config('filesystems.disks.s3.bucket'),
|
||||
'region' => config('filesystems.disks.s3.region'),
|
||||
'key_exists' => !empty(config('filesystems.disks.s3.key')),
|
||||
'secret_exists' => !empty(config('filesystems.disks.s3.secret')),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!$record) {
|
||||
throw new \Exception('Record not found during upload');
|
||||
}
|
||||
|
||||
// Test S3 connection if using S3
|
||||
if ($diskName === 's3') {
|
||||
$disk = \Storage::disk('s3');
|
||||
|
||||
// Test basic S3 connectivity
|
||||
$testFile = 'test-' . time() . '.txt';
|
||||
$disk->put($testFile, 'test content');
|
||||
$disk->delete($testFile);
|
||||
|
||||
if (config('app.env') === 'local') {
|
||||
Log::info('S3 connectivity test passed');
|
||||
}
|
||||
}
|
||||
|
||||
// Use addMedia with the file directly, not addMediaFromRequest
|
||||
// Generate secure filename similar to Livewire temp files
|
||||
$originalName = $file->getClientOriginalName();
|
||||
$extension = pathinfo($originalName, PATHINFO_EXTENSION);
|
||||
$baseName = pathinfo($originalName, PATHINFO_FILENAME);
|
||||
|
||||
// Generate secure filename with encoded original name
|
||||
$encodedName = base64_encode($originalName);
|
||||
$secureFileName = Str::random(32) . '-meta' . $encodedName . '-.' . $extension;
|
||||
|
||||
$media = $record->addMedia($file->getRealPath())
|
||||
->usingName($baseName) // Keep original name for display/search
|
||||
->usingFileName($secureFileName) // Use secure filename for storage
|
||||
->toMediaCollection('featured-image', $diskName);
|
||||
|
||||
if (config('app.env') === 'local') {
|
||||
Log::info('Featured Image Upload Success', [
|
||||
'media_id' => $media->id,
|
||||
'media_url' => $media->getUrl(),
|
||||
'media_path' => $media->getPathRelativeToRoot(),
|
||||
'disk' => $media->disk
|
||||
]);
|
||||
}
|
||||
|
||||
return $media->getUrl();
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Featured Image Upload Failed', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'disk' => $diskName,
|
||||
'file_name' => $file->getClientOriginalName()
|
||||
]);
|
||||
|
||||
|
||||
// Also show error to user
|
||||
\Filament\Notifications\Notification::make()
|
||||
->danger()
|
||||
->title('Upload Failed')
|
||||
->body($e->getMessage())
|
||||
->persistent()
|
||||
->send();
|
||||
|
||||
throw $e;
|
||||
}
|
||||
})
|
||||
->hintAction(
|
||||
Action::make('featured_picker')
|
||||
->label('Featured Image from Gallery')
|
||||
|
|
@ -55,11 +157,10 @@ class EntryForm
|
|||
->label('Select an existing image')
|
||||
->allowHtml()
|
||||
->options(function () {
|
||||
return Media::where('model_type', 'temp')
|
||||
->where('model_id', 0)
|
||||
->where('disk', 'public')
|
||||
$currentDisk = config('media-library.disk_name', 'public');
|
||||
return Media::where('disk', $currentDisk)
|
||||
->latest()
|
||||
->limit(30)
|
||||
->limit(50)
|
||||
->get(['id', 'file_name', 'name', 'disk'])
|
||||
->mapWithKeys(function (Media $item) {
|
||||
try {
|
||||
|
|
@ -86,6 +187,15 @@ class EntryForm
|
|||
])
|
||||
->action(function (array $data, SpatieMediaLibraryFileUpload $component): void {
|
||||
$record = $component->getRecord();
|
||||
$diskName = config('media-library.disk_name', 'public');
|
||||
|
||||
if (config('app.env') === 'local') {
|
||||
Log::info('Featured Image Picker Action Debug', [
|
||||
'disk' => $diskName,
|
||||
'record_id' => $record?->id,
|
||||
'image_id' => $data['image_id'] ?? null
|
||||
]);
|
||||
}
|
||||
|
||||
if (! $record) {
|
||||
\Filament\Notifications\Notification::make()
|
||||
|
|
@ -101,27 +211,53 @@ class EntryForm
|
|||
}
|
||||
|
||||
$sourceMedia = Media::find($data['image_id']);
|
||||
if (! $sourceMedia || ! file_exists($sourceMedia->getPath())) {
|
||||
if (! $sourceMedia) {
|
||||
Log::error('Source media not found', ['image_id' => $data['image_id']]);
|
||||
\Filament\Notifications\Notification::make()
|
||||
->danger()
|
||||
->title('Image file not found')
|
||||
->title('Source image not found in database')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$sourceFile = $sourceMedia->getPath();
|
||||
$tempCopy = sys_get_temp_dir() . '/' . uniqid() . '_' . $sourceMedia->file_name;
|
||||
copy($sourceFile, $tempCopy);
|
||||
|
||||
try {
|
||||
// For S3, we need to handle file copying differently
|
||||
if ($sourceMedia->disk === 's3') {
|
||||
if (config('app.env') === 'local') {
|
||||
Log::info('Copying S3 media to new collection', [
|
||||
'source_disk' => $sourceMedia->disk,
|
||||
'source_path' => $sourceMedia->getPathRelativeToRoot(),
|
||||
'target_disk' => $diskName
|
||||
]);
|
||||
}
|
||||
|
||||
// Copy from S3 to S3 or download temporarily
|
||||
$sourceDisk = \Storage::disk($sourceMedia->disk);
|
||||
$sourceContent = $sourceDisk->get($sourceMedia->getPathRelativeToRoot());
|
||||
|
||||
if (!$sourceContent) {
|
||||
throw new \Exception('Could not read source file from S3');
|
||||
}
|
||||
|
||||
$tempCopy = sys_get_temp_dir() . '/' . uniqid() . '_' . $sourceMedia->file_name;
|
||||
file_put_contents($tempCopy, $sourceContent);
|
||||
} else {
|
||||
// Local file handling
|
||||
$sourceFile = $sourceMedia->getPath();
|
||||
if (! file_exists($sourceFile)) {
|
||||
throw new \Exception('Source file not found on disk');
|
||||
}
|
||||
|
||||
$tempCopy = sys_get_temp_dir() . '/' . uniqid() . '_' . $sourceMedia->file_name;
|
||||
copy($sourceFile, $tempCopy);
|
||||
}
|
||||
|
||||
// Verify record has ID
|
||||
if (! $record->id) {
|
||||
\Filament\Notifications\Notification::make()
|
||||
->danger()
|
||||
->title('Entry must be saved first')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +265,15 @@ class EntryForm
|
|||
$newMedia = $record->addMedia($tempCopy)
|
||||
->usingName($sourceMedia->name ?: pathinfo($sourceMedia->file_name, PATHINFO_FILENAME))
|
||||
->usingFileName($sourceMedia->file_name)
|
||||
->toMediaCollection('featured-image', 'public');
|
||||
->toMediaCollection('featured-image', $diskName);
|
||||
|
||||
if (config('app.env') === 'local') {
|
||||
Log::info('Featured Image Picker Success', [
|
||||
'new_media_id' => $newMedia->id,
|
||||
'new_media_disk' => $newMedia->disk,
|
||||
'new_media_url' => $newMedia->getUrl()
|
||||
]);
|
||||
}
|
||||
|
||||
// Dispatch event for app.js to handle
|
||||
$component->getLivewire()->dispatch('featured-image-added', ['mediaId' => $newMedia->id]);
|
||||
|
|
@ -139,12 +283,20 @@ class EntryForm
|
|||
->title('Image added to featured image')
|
||||
->send();
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Featured Image Picker Failed', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'source_media_id' => $data['image_id'],
|
||||
'disk' => $diskName
|
||||
]);
|
||||
|
||||
\Filament\Notifications\Notification::make()
|
||||
->danger()
|
||||
->title('Error: ' . $e->getMessage())
|
||||
->persistent()
|
||||
->send();
|
||||
} finally {
|
||||
if (file_exists($tempCopy)) {
|
||||
if (isset($tempCopy) && file_exists($tempCopy)) {
|
||||
unlink($tempCopy);
|
||||
}
|
||||
}
|
||||
|
|
@ -154,7 +306,13 @@ class EntryForm
|
|||
->required(),
|
||||
Toggle::make('is_featured')
|
||||
->required(),
|
||||
DatePicker::make('published_at'),
|
||||
TextInput::make('priority')
|
||||
->label('Priority')
|
||||
->numeric()
|
||||
->default(0)
|
||||
->required(),
|
||||
DatePicker::make('published_at')
|
||||
->visible(fn($get) => $get('type') === 'article'),
|
||||
Select::make('category_id')
|
||||
->label('Category')
|
||||
->options(function () {
|
||||
|
|
@ -164,11 +322,14 @@ class EntryForm
|
|||
})
|
||||
->searchable(),
|
||||
TextInput::make('call_to_action_text')
|
||||
->label('Call to Action Text'),
|
||||
->label('Call to Action Text')
|
||||
->visible(fn($get) => $get('type') !== 'article'),
|
||||
TextInput::make('call_to_action_link')
|
||||
->label('Call to Action URL'),
|
||||
->label('Call to Action URL')
|
||||
->visible(fn($get) => $get('type') !== 'article'),
|
||||
|
||||
RichEditor::make('content')
|
||||
->visible(fn($get) => $get('type') !== 'image')
|
||||
->columnSpanFull()
|
||||
->hintAction(
|
||||
Action::make('picker')
|
||||
|
|
|
|||
|
|
@ -32,12 +32,12 @@ class MediaForm
|
|||
'1:1',
|
||||
])
|
||||
->columnSpanFull()
|
||||
->disk('public')
|
||||
->disk('s3')
|
||||
->directory('media')
|
||||
->visibility('public')
|
||||
->acceptedFileTypes(['image/*', 'application/pdf'])
|
||||
->maxSize(10240)
|
||||
->required(fn ($context) => $context === 'create')
|
||||
->required(fn($context) => $context === 'create')
|
||||
->afterStateHydrated(function (FileUpload $component, $state, $record): void {
|
||||
if (! $record) {
|
||||
return;
|
||||
|
|
@ -50,7 +50,7 @@ class MediaForm
|
|||
}
|
||||
|
||||
// Construct the correct path: {media_id}/{filename}
|
||||
$path = $media->id.'/'.$media->file_name;
|
||||
$path = $media->id . '/' . $media->file_name;
|
||||
|
||||
$component->state($path);
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ class EntryResource extends JsonResource
|
|||
'call_to_action_link' => $this->call_to_action_link,
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
'priority' => $this->priority,
|
||||
'type' => $this->type,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class Entry extends Model implements HasMedia, HasRichContent
|
|||
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'type',
|
||||
'slug',
|
||||
'description',
|
||||
'is_published',
|
||||
|
|
@ -33,6 +34,7 @@ class Entry extends Model implements HasMedia, HasRichContent
|
|||
'call_to_action_link',
|
||||
'call_to_action_text',
|
||||
'category_id',
|
||||
'priority',
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -34,7 +34,12 @@ class AdminPanelProvider extends PanelProvider
|
|||
->colors([
|
||||
'primary' => Color::Blue,
|
||||
])
|
||||
->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources')
|
||||
->resources([
|
||||
\App\Filament\Resources\Entries\EntryResource::class,
|
||||
\App\Filament\Resources\TextWidgets\TextWidgetResource::class,
|
||||
\App\Filament\Resources\Media\MediaResource::class,
|
||||
\App\Filament\Resources\Categroys\CategroyResource::class,
|
||||
])
|
||||
->discoverPages(in: app_path('Filament/Pages'), for: 'App\Filament\Pages')
|
||||
->pages([
|
||||
Dashboard::class,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue