diff --git a/.env.example b/.env.example index c0660ea..d1ef0f7 100644 --- a/.env.example +++ b/.env.example @@ -61,5 +61,7 @@ AWS_SECRET_ACCESS_KEY= AWS_DEFAULT_REGION=us-east-1 AWS_BUCKET= AWS_USE_PATH_STYLE_ENDPOINT=false +# AWS_BUCKET=share-lt-images +# MEDIA_DISK=s3 VITE_APP_NAME="${APP_NAME}" diff --git a/.gitignore b/.gitignore index d2bb60d..ac23ed3 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ yarn-error.log .env.dusk.local log*.txt .envrc +database/backups diff --git a/CHANGELOG.md b/CHANGELOG.md index d1f5186..fd4fe6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # CHANGELOG -## 2026-01-08 +## 2026-01-19 added text widgets diff --git a/app/Console/Commands/RemediateBlogS3Images.php b/app/Console/Commands/RemediateBlogS3Images.php new file mode 100644 index 0000000..2d3f531 --- /dev/null +++ b/app/Console/Commands/RemediateBlogS3Images.php @@ -0,0 +1,61 @@ +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); + } + } + } +} diff --git a/app/Filament/Resources/Entries/Schemas/EntryForm.php b/app/Filament/Resources/Entries/Schemas/EntryForm.php index 7f2be7c..218c20b 100644 --- a/app/Filament/Resources/Entries/Schemas/EntryForm.php +++ b/app/Filament/Resources/Entries/Schemas/EntryForm.php @@ -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') diff --git a/app/Filament/Resources/Media/Schemas/MediaForm.php b/app/Filament/Resources/Media/Schemas/MediaForm.php index 5ac03a9..ebe77ac 100644 --- a/app/Filament/Resources/Media/Schemas/MediaForm.php +++ b/app/Filament/Resources/Media/Schemas/MediaForm.php @@ -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); }), diff --git a/app/Http/Resources/EntryResource.php b/app/Http/Resources/EntryResource.php index a116de1..36377b4 100644 --- a/app/Http/Resources/EntryResource.php +++ b/app/Http/Resources/EntryResource.php @@ -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, ]; } } diff --git a/app/Models/Entry.php b/app/Models/Entry.php index d6d6406..37d0b15 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -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', ]; /** diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 33aa162..f911765 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -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, diff --git a/cmd/backup_raw_data.sh b/cmd/backup_raw_data.sh new file mode 100755 index 0000000..bfc210b --- /dev/null +++ b/cmd/backup_raw_data.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +#SQLITE_DB_PATH="../share-lt/database/database.sqlite" +SQLITE_DB_PATH="../share-lt/database/database.sqlite.backup5.recovery" +BACKUP_DIR="database/backups" +TIMESTAMP=$(date +"%Y%m%d_%H%M%S") +BACKUP_FILE="$BACKUP_DIR/backup_$TIMESTAMP.sql" +TABLES="taggables media tags categories text_widgets entries users" +mkdir -p "$BACKUP_DIR" + +for TABLE in $TABLES; do + echo "Backing up table: $TABLE" + sqlite3 "$SQLITE_DB_PATH" ".dump $TABLE" > "$BACKUP_DIR/${TABLE}_backup_$TIMESTAMP.sql" +done + diff --git a/cmd/restore_raw_data.sh b/cmd/restore_raw_data.sh new file mode 100755 index 0000000..2fe10e5 --- /dev/null +++ b/cmd/restore_raw_data.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# Define the backup directory and the SQLite database file +BACKUP_DIR="database/backups" +DB_FILE="database/database.sqlite" +# DATE="20260124_115644" +DATE="20260124_123340" + +# Ensure the DATE variable is set +if [ -z "$DATE" ]; then + echo "DATE variable is not set. Please set the DATE variable to match the backup file date." + exit 1 +fi + +# Check if the backup directory exists +if [ ! -d "$BACKUP_DIR" ]; then + echo "Backup directory does not exist: $BACKUP_DIR" + exit 1 +fi + +# Check if the SQLite database file exists +if [ ! -f "$DB_FILE" ]; then + echo "SQLite database file does not exist: $DB_FILE" + exit 1 +fi + +# Check if there are any matching backup files +if ! ls "$BACKUP_DIR"/*"$DATE".sql 1> /dev/null 2>&1; then + echo "No backup files found for the date: $DATE" + exit 1 +fi + +echo "Starting restore process from backups dated: $DATE" +echo "Using database file: $DB_FILE" + +# Loop through each file in the backup directory +for file in "$BACKUP_DIR"/*"$DATE".sql; do + if [ -f "$file" ]; then + echo "Restoring data from $file into $DB_FILE..." + + # Import the SQL file into the SQLite database and log output + sqlite3 "$DB_FILE" < "$file" 2>> restore_errors.log + + # Check for errors in the restore process + if [ $? -eq 0 ]; then + echo "Successfully restored $file" + else + echo "Failed to restore $file. Check restore_errors.log for details." + fi + + # Debugging: Print the last 10 lines of the database to verify data + # echo "Last 10 rows of the database after restoring $file:" + # sqlite3 "$DB_FILE" "SELECT * FROM sqlite_master WHERE type='table';" 2>> restore_errors.log + # sqlite3 "$DB_FILE" "SELECT * FROM entries ORDER BY rowid DESC LIMIT 10;" 2>> restore_errors.log + fi +done + +echo "Restore process completed." \ No newline at end of file diff --git a/composer.json b/composer.json index 19e926f..ae35e3d 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "laravel/framework": "^12.0", "laravel/sanctum": "^4.0", "laravel/tinker": "^2.10.1", + "league/flysystem-aws-s3-v3": "^3.0", "livewire/flux": "^2.9.0", "spatie/laravel-medialibrary": "^11.17", "spatie/laravel-tags": "^4.10" diff --git a/composer.lock b/composer.lock index d086086..a121e30 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f4958020fbef658afa0859ba9b23a152", + "content-hash": "07b9fb2e6bdb329e62a1cf4d814812df", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -72,6 +72,157 @@ }, "time": "2025-12-04T13:38:21+00:00" }, + { + "name": "aws/aws-crt-php", + "version": "v1.2.7", + "source": { + "type": "git", + "url": "https://github.com/awslabs/aws-crt-php.git", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" + } + ], + "description": "AWS Common Runtime for PHP", + "homepage": "https://github.com/awslabs/aws-crt-php", + "keywords": [ + "amazon", + "aws", + "crt", + "sdk" + ], + "support": { + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7" + }, + "time": "2024-10-18T22:15:13+00:00" + }, + { + "name": "aws/aws-sdk-php", + "version": "3.369.19", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "32fee3a25290186724ede9ca177d5090f7c5a837" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/32fee3a25290186724ede9ca177d5090f7c5a837", + "reference": "32fee3a25290186724ede9ca177d5090f7c5a837", + "shasum": "" + }, + "require": { + "aws/aws-crt-php": "^1.2.3", + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/promises": "^2.0", + "guzzlehttp/psr7": "^2.4.5", + "mtdowling/jmespath.php": "^2.8.0", + "php": ">=8.1", + "psr/http-message": "^1.0 || ^2.0", + "symfony/filesystem": "^v5.4.45 || ^v6.4.3 || ^v7.1.0 || ^v8.0.0" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "composer/composer": "^2.7.8", + "dms/phpunit-arraysubset-asserts": "^0.4.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-sockets": "*", + "phpunit/phpunit": "^9.6", + "psr/cache": "^2.0 || ^3.0", + "psr/simple-cache": "^2.0 || ^3.0", + "sebastian/comparator": "^1.2.3 || ^4.0 || ^5.0", + "yoast/phpunit-polyfills": "^2.0" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-pcntl": "To use client-side monitoring", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Aws\\": "src/" + }, + "exclude-from-classmap": [ + "src/data/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "support": { + "forum": "https://github.com/aws/aws-sdk-php/discussions", + "issues": "https://github.com/aws/aws-sdk-php/issues", + "source": "https://github.com/aws/aws-sdk-php/tree/3.369.19" + }, + "time": "2026-01-23T19:05:51+00:00" + }, { "name": "bacon/bacon-qr-code", "version": "v3.0.3", @@ -3154,16 +3305,16 @@ }, { "name": "league/flysystem", - "version": "3.30.2", + "version": "3.31.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277" + "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", - "reference": "5966a8ba23e62bdb518dd9e0e665c2dbd4b5b277", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1717e0b3642b0df65ecb0cc89cdd99fa840672ff", + "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff", "shasum": "" }, "require": { @@ -3231,22 +3382,77 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.30.2" + "source": "https://github.com/thephpleague/flysystem/tree/3.31.0" }, - "time": "2025-11-10T17:13:11+00:00" + "time": "2026-01-23T15:38:47+00:00" }, { - "name": "league/flysystem-local", - "version": "3.30.2", + "name": "league/flysystem-aws-s3-v3", + "version": "3.31.0", "source": { "type": "git", - "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d" + "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", + "reference": "e36a2bc60b06332c92e4435047797ded352b446f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/ab4f9d0d672f601b102936aa728801dd1a11968d", - "reference": "ab4f9d0d672f601b102936aa728801dd1a11968d", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/e36a2bc60b06332c92e4435047797ded352b446f", + "reference": "e36a2bc60b06332c92e4435047797ded352b446f", + "shasum": "" + }, + "require": { + "aws/aws-sdk-php": "^3.295.10", + "league/flysystem": "^3.10.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3V3\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "AWS S3 filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "aws", + "file", + "files", + "filesystem", + "s3", + "storage" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.31.0" + }, + "time": "2026-01-23T15:30:45+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.31.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", "shasum": "" }, "require": { @@ -3280,9 +3486,9 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.30.2" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" }, - "time": "2025-11-10T11:23:37+00:00" + "time": "2026-01-23T15:30:45+00:00" }, { "name": "league/mime-type-detection", @@ -3997,6 +4203,72 @@ ], "time": "2026-01-02T08:56:05+00:00" }, + { + "name": "mtdowling/jmespath.php", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a2a865e05d5f420b50cc2f85bb78d565db12a6bc", + "reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" + }, + "require-dev": { + "composer/xdebug-handler": "^3.0.3", + "phpunit/phpunit": "^8.5.33" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "files": [ + "src/JmesPath.php" + ], + "psr-4": { + "JmesPath\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "support": { + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/2.8.0" + }, + "time": "2024-09-04T18:46:31+00:00" + }, { "name": "nesbot/carbon", "version": "3.11.0", @@ -6940,6 +7212,76 @@ ], "time": "2024-09-25T14:21:43+00:00" }, + { + "name": "symfony/filesystem", + "version": "v7.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "d551b38811096d0be9c4691d406991b47c0c630a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a", + "reference": "d551b38811096d0be9c4691d406991b47c0c630a", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-27T13:27:24+00:00" + }, { "name": "symfony/finder", "version": "v7.4.3", diff --git a/config/filesystems.php b/config/filesystems.php index 3d671bd..367dbfe 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -41,7 +41,7 @@ return [ 'public' => [ 'driver' => 'local', 'root' => storage_path('app/public'), - 'url' => env('APP_URL').'/storage', + 'url' => env('APP_URL') . '/storage', 'visibility' => 'public', 'throw' => false, 'report' => false, @@ -53,6 +53,7 @@ return [ 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), + 'root' => env('AWS_DIRECTORY', ''), 'url' => env('AWS_URL'), 'endpoint' => env('AWS_ENDPOINT'), 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), diff --git a/config/services.php b/config/services.php index 6a90eb8..eba8929 100644 --- a/config/services.php +++ b/config/services.php @@ -26,6 +26,7 @@ return [ 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'directory' => env('AWS_DIRECTORY'), ], 'slack' => [ diff --git a/database/migrations/2026_01_20_000000_add_type_to_entries_table.php b/database/migrations/2026_01_20_000000_add_type_to_entries_table.php new file mode 100644 index 0000000..89596ed --- /dev/null +++ b/database/migrations/2026_01_20_000000_add_type_to_entries_table.php @@ -0,0 +1,28 @@ +string('type')->nullable()->default('article')->after('title'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('entries', function (Blueprint $table) { + $table->dropColumn('type'); + }); + } +}; diff --git a/database/migrations/2026_01_20_175349_add_priority_to_entries.php b/database/migrations/2026_01_20_175349_add_priority_to_entries.php new file mode 100644 index 0000000..b89e4a4 --- /dev/null +++ b/database/migrations/2026_01_20_175349_add_priority_to_entries.php @@ -0,0 +1,28 @@ +integer('priority')->default(0); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('entries', function (Blueprint $table) { + $table->dropColumn('priority'); + }); + } +}; diff --git a/package-lock.json b/package-lock.json index a54ebf9..948b7fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "share-lt", + "name": "share-lt-recovery-zone", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/tests/Unit/EntryModelTest.php b/tests/Unit/EntryModelTest.php index 3085db9..939bfe6 100644 --- a/tests/Unit/EntryModelTest.php +++ b/tests/Unit/EntryModelTest.php @@ -5,6 +5,7 @@ use App\Models\Entry; it('has correct fillable attributes', function () { $expected = [ 'title', + 'type', 'slug', 'description', 'is_published', @@ -13,6 +14,8 @@ it('has correct fillable attributes', function () { 'content', 'call_to_action_link', 'call_to_action_text', + 'category_id', + 'priority', ]; $entry = new Entry;