From ca784fe26a5cc5cb773eb484bcd383e6426434bf Mon Sep 17 00:00:00 2001 From: soeren Date: Sun, 7 Dec 2025 19:25:01 +0100 Subject: [PATCH] added a sparkbooth section to gallery form, connection details reside there now. --- .phpunit.result.cache | 2 +- app/Filament/Pages/SparkboothConnections.php | 21 ++- .../Resources/Galleries/GalleryResource.php | 22 +++ .../Galleries/Schemas/GalleryForm.php | 158 ++++++++++++------ app/Models/Gallery.php | 11 ++ database/seeders/GallerySeeder.php | 4 +- database/seeders/RoleSeeder.php | 9 +- database/seeders/UserSeeder.php | 21 ++- .../pages/partials/sparkbooth-token.blade.php | 59 ++++--- .../filament/pages/sparkbooth-setup.blade.php | 6 +- .../Filament/SparkboothConnectionsTest.php | 53 ++++++ 11 files changed, 263 insertions(+), 103 deletions(-) create mode 100644 tests/Feature/Filament/SparkboothConnectionsTest.php diff --git a/.phpunit.result.cache b/.phpunit.result.cache index 6ed710f..adf7248 100644 --- a/.phpunit.result.cache +++ b/.phpunit.result.cache @@ -1 +1 @@ -{"version":2,"defects":{"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_authenticate_using_the_login_screen":8,"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_not_authenticate_with_invalid_password":8,"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_logout":8,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_verification_screen_can_be_rendered":8,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_can_be_verified":8,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_is_not_verified_with_invalid_hash":8,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_confirm_password_screen_can_be_rendered":8,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_password_can_be_confirmed":8,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_password_is_not_confirmed_with_invalid_password":8,"Tests\\Feature\\Auth\\PasswordResetTest::test_reset_password_link_can_be_requested":8,"Tests\\Feature\\Auth\\PasswordResetTest::test_reset_password_screen_can_be_rendered":8,"Tests\\Feature\\Auth\\PasswordResetTest::test_password_can_be_reset_with_valid_token":8,"Tests\\Feature\\Auth\\PasswordUpdateTest::test_password_can_be_updated":8,"Tests\\Feature\\Auth\\PasswordUpdateTest::test_correct_password_must_be_provided_to_update_password":8,"Tests\\Feature\\Auth\\RegistrationTest::test_new_users_can_register":7,"Tests\\Feature\\ProfileTest::test_profile_page_is_displayed":8,"Tests\\Feature\\ProfileTest::test_profile_information_can_be_updated":8,"Tests\\Feature\\ProfileTest::test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged":8,"Tests\\Feature\\ProfileTest::test_user_can_delete_their_account":8,"Tests\\Feature\\ProfileTest::test_correct_password_must_be_provided_to_delete_account":8,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":7,"Tests\\Feature\\Filament\\ResourcePagesTest::test_admin_resource_index_pages_render":7,"Tests\\Feature\\Filament\\ResourcePagesTest::test_admin_resource_create_pages_render":7,"Tests\\Feature\\Filament\\ResourcePagesTest::test_admin_resource_edit_pages_render":7,"Tests\\Feature\\AiStatusTest::test_ai_status_endpoint_returns_correct_structure":8,"Tests\\Feature\\AiStatusTest::test_ai_status_update_disables_unavailable_providers":7,"Tests\\Feature\\AiStatusTest::test_comfyui_check_availability_method":8,"Tests\\Feature\\AiStatusTest::test_runwareai_check_availability_method":8,"Tests\\Feature\\GalleryAccessTest::test_allows_access_after_correct_password":8,"Tests\\Feature\\GalleryAccessTest::test_redirects_to_access_page_when_password_is_required":7,"Tests\\Feature\\GalleryAccessTest::test_denies_access_when_gallery_expired":8},"times":{"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.001,"Tests\\Feature\\Auth\\AuthenticationTest::test_login_screen_can_be_rendered":2.652,"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_authenticate_using_the_login_screen":0.844,"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_not_authenticate_with_invalid_password":0.003,"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_logout":0.003,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_verification_screen_can_be_rendered":0.004,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_can_be_verified":0.003,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_is_not_verified_with_invalid_hash":0.004,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_confirm_password_screen_can_be_rendered":0.003,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_password_can_be_confirmed":0.004,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_password_is_not_confirmed_with_invalid_password":0.011,"Tests\\Feature\\Auth\\PasswordResetTest::test_reset_password_link_screen_can_be_rendered":0.685,"Tests\\Feature\\Auth\\PasswordResetTest::test_reset_password_link_can_be_requested":0.079,"Tests\\Feature\\Auth\\PasswordResetTest::test_reset_password_screen_can_be_rendered":0.004,"Tests\\Feature\\Auth\\PasswordResetTest::test_password_can_be_reset_with_valid_token":0.003,"Tests\\Feature\\Auth\\PasswordUpdateTest::test_password_can_be_updated":0.003,"Tests\\Feature\\Auth\\PasswordUpdateTest::test_correct_password_must_be_provided_to_update_password":0.003,"Tests\\Feature\\Auth\\RegistrationTest::test_registration_screen_can_be_rendered":0.653,"Tests\\Feature\\Auth\\RegistrationTest::test_new_users_can_register":2.449,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":3.546,"Tests\\Feature\\ProfileTest::test_profile_page_is_displayed":0.003,"Tests\\Feature\\ProfileTest::test_profile_information_can_be_updated":0.008,"Tests\\Feature\\ProfileTest::test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged":0.005,"Tests\\Feature\\ProfileTest::test_user_can_delete_their_account":0.004,"Tests\\Feature\\ProfileTest::test_correct_password_must_be_provided_to_delete_account":0.004,"Tests\\Feature\\Filament\\ResourcePagesTest::test_admin_resource_index_pages_render":6.542,"Tests\\Feature\\Filament\\ResourcePagesTest::test_admin_resource_create_pages_render":3.055,"Tests\\Feature\\Filament\\ResourcePagesTest::test_admin_resource_edit_pages_render":3.132,"Tests\\Feature\\AiStatusTest::test_ai_status_endpoint_returns_correct_structure":0.637,"Tests\\Feature\\AiStatusTest::test_ai_status_update_disables_unavailable_providers":1.569,"Tests\\Feature\\AiStatusTest::test_comfyui_check_availability_method":0.228,"Tests\\Feature\\AiStatusTest::test_runwareai_check_availability_method":0.233,"Tests\\Feature\\GalleryAccessTest::test_redirects_to_access_page_when_password_is_required":9.523,"Tests\\Feature\\GalleryAccessTest::test_allows_access_after_correct_password":3.424,"Tests\\Feature\\GalleryAccessTest::test_denies_access_when_gallery_expired":3.086}} \ No newline at end of file +{"version":2,"defects":{"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_authenticate_using_the_login_screen":8,"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_not_authenticate_with_invalid_password":8,"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_logout":8,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_verification_screen_can_be_rendered":8,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_can_be_verified":8,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_is_not_verified_with_invalid_hash":8,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_confirm_password_screen_can_be_rendered":8,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_password_can_be_confirmed":8,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_password_is_not_confirmed_with_invalid_password":8,"Tests\\Feature\\Auth\\PasswordResetTest::test_reset_password_link_can_be_requested":8,"Tests\\Feature\\Auth\\PasswordResetTest::test_reset_password_screen_can_be_rendered":8,"Tests\\Feature\\Auth\\PasswordResetTest::test_password_can_be_reset_with_valid_token":8,"Tests\\Feature\\Auth\\PasswordUpdateTest::test_password_can_be_updated":8,"Tests\\Feature\\Auth\\PasswordUpdateTest::test_correct_password_must_be_provided_to_update_password":8,"Tests\\Feature\\Auth\\RegistrationTest::test_new_users_can_register":7,"Tests\\Feature\\ProfileTest::test_profile_page_is_displayed":8,"Tests\\Feature\\ProfileTest::test_profile_information_can_be_updated":8,"Tests\\Feature\\ProfileTest::test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged":8,"Tests\\Feature\\ProfileTest::test_user_can_delete_their_account":8,"Tests\\Feature\\ProfileTest::test_correct_password_must_be_provided_to_delete_account":8,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":7,"Tests\\Feature\\Filament\\ResourcePagesTest::test_admin_resource_index_pages_render":7,"Tests\\Feature\\Filament\\ResourcePagesTest::test_admin_resource_create_pages_render":7,"Tests\\Feature\\Filament\\ResourcePagesTest::test_admin_resource_edit_pages_render":7,"Tests\\Feature\\AiStatusTest::test_ai_status_endpoint_returns_correct_structure":8,"Tests\\Feature\\AiStatusTest::test_ai_status_update_disables_unavailable_providers":7,"Tests\\Feature\\AiStatusTest::test_comfyui_check_availability_method":8,"Tests\\Feature\\AiStatusTest::test_runwareai_check_availability_method":8,"Tests\\Feature\\GalleryAccessTest::test_allows_access_after_correct_password":8,"Tests\\Feature\\GalleryAccessTest::test_redirects_to_access_page_when_password_is_required":7,"Tests\\Feature\\GalleryAccessTest::test_denies_access_when_gallery_expired":8},"times":{"Tests\\Unit\\ExampleTest::test_that_true_is_true":0.001,"Tests\\Feature\\Auth\\AuthenticationTest::test_login_screen_can_be_rendered":2.652,"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_authenticate_using_the_login_screen":0.844,"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_not_authenticate_with_invalid_password":0.003,"Tests\\Feature\\Auth\\AuthenticationTest::test_users_can_logout":0.003,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_verification_screen_can_be_rendered":0.004,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_can_be_verified":0.003,"Tests\\Feature\\Auth\\EmailVerificationTest::test_email_is_not_verified_with_invalid_hash":0.004,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_confirm_password_screen_can_be_rendered":0.003,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_password_can_be_confirmed":0.004,"Tests\\Feature\\Auth\\PasswordConfirmationTest::test_password_is_not_confirmed_with_invalid_password":0.011,"Tests\\Feature\\Auth\\PasswordResetTest::test_reset_password_link_screen_can_be_rendered":0.685,"Tests\\Feature\\Auth\\PasswordResetTest::test_reset_password_link_can_be_requested":0.079,"Tests\\Feature\\Auth\\PasswordResetTest::test_reset_password_screen_can_be_rendered":0.004,"Tests\\Feature\\Auth\\PasswordResetTest::test_password_can_be_reset_with_valid_token":0.003,"Tests\\Feature\\Auth\\PasswordUpdateTest::test_password_can_be_updated":0.003,"Tests\\Feature\\Auth\\PasswordUpdateTest::test_correct_password_must_be_provided_to_update_password":0.003,"Tests\\Feature\\Auth\\RegistrationTest::test_registration_screen_can_be_rendered":0.653,"Tests\\Feature\\Auth\\RegistrationTest::test_new_users_can_register":2.449,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":3.546,"Tests\\Feature\\ProfileTest::test_profile_page_is_displayed":0.003,"Tests\\Feature\\ProfileTest::test_profile_information_can_be_updated":0.008,"Tests\\Feature\\ProfileTest::test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged":0.005,"Tests\\Feature\\ProfileTest::test_user_can_delete_their_account":0.004,"Tests\\Feature\\ProfileTest::test_correct_password_must_be_provided_to_delete_account":0.004,"Tests\\Feature\\Filament\\ResourcePagesTest::test_admin_resource_index_pages_render":6.542,"Tests\\Feature\\Filament\\ResourcePagesTest::test_admin_resource_create_pages_render":3.055,"Tests\\Feature\\Filament\\ResourcePagesTest::test_admin_resource_edit_pages_render":3.132,"Tests\\Feature\\AiStatusTest::test_ai_status_endpoint_returns_correct_structure":0.637,"Tests\\Feature\\AiStatusTest::test_ai_status_update_disables_unavailable_providers":1.569,"Tests\\Feature\\AiStatusTest::test_comfyui_check_availability_method":0.228,"Tests\\Feature\\AiStatusTest::test_runwareai_check_availability_method":0.233,"Tests\\Feature\\GalleryAccessTest::test_redirects_to_access_page_when_password_is_required":9.523,"Tests\\Feature\\GalleryAccessTest::test_allows_access_after_correct_password":3.424,"Tests\\Feature\\GalleryAccessTest::test_denies_access_when_gallery_expired":3.086,"Tests\\Feature\\Filament\\SparkboothConnectionsTest::test_admin_can_delete_connection_without_deleting_gallery":4.826}} \ No newline at end of file diff --git a/app/Filament/Pages/SparkboothConnections.php b/app/Filament/Pages/SparkboothConnections.php index 058b082..2917340 100644 --- a/app/Filament/Pages/SparkboothConnections.php +++ b/app/Filament/Pages/SparkboothConnections.php @@ -4,9 +4,9 @@ namespace App\Filament\Pages; use App\Models\Gallery; use BackedEnum; +use Filament\Actions\Action; use Filament\Notifications\Notification; use Filament\Pages\Page; -use Filament\Actions\Action; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Concerns\InteractsWithTable; use Filament\Tables\Contracts\HasTable; @@ -88,11 +88,24 @@ class SparkboothConnections extends Page implements HasTable return view('filament.pages.partials.sparkbooth-token', $data); }), + Action::make('deleteConnection') + ->label('Verbindung loeschen') + ->icon('heroicon-o-trash') + ->color('danger') + ->requiresConfirmation() + ->modalHeading('Sparkbooth-Verbindung entfernen') + ->modalDescription('Die Galerie bleibt erhalten, aber Upload-Token und Zugangsdaten werden geloescht.') + ->action(function (Gallery $record): void { + $record->clearSparkboothConnection(); + + Notification::make() + ->title('Sparkbooth-Verbindung entfernt.') + ->body('Der Upload-Token und die Zugangsdaten wurden geloescht.') + ->success() + ->send(); + }), ]) ->emptyStateHeading('Keine Sparkbooth-Verbindungen') ->emptyStateDescription('Lege eine neue Verbindung an oder aktiviere Uploads fuer eine Galerie.'); } } - - - diff --git a/app/Filament/Resources/Galleries/GalleryResource.php b/app/Filament/Resources/Galleries/GalleryResource.php index 94fc310..892eb24 100644 --- a/app/Filament/Resources/Galleries/GalleryResource.php +++ b/app/Filament/Resources/Galleries/GalleryResource.php @@ -59,6 +59,7 @@ class GalleryResource extends Resource public static function mutateFormDataBeforeCreate(array $data): array { $data = self::mutatePassword($data); + $data = self::mutateSparkbooth($data, true); $data['slug'] = $data['slug'] ?: Str::uuid()->toString(); return $data; @@ -67,6 +68,7 @@ class GalleryResource extends Resource public static function mutateFormDataBeforeSave(array $data): array { $data = self::mutatePassword($data); + $data = self::mutateSparkbooth($data); $data['slug'] = $data['slug'] ?: Str::uuid()->toString(); return $data; @@ -83,4 +85,24 @@ class GalleryResource extends Resource return $data; } + + private static function mutateSparkbooth(array $data, bool $isCreate = false): array + { + if (array_key_exists('sparkbooth_username', $data)) { + $data['sparkbooth_username'] = $data['sparkbooth_username'] + ? Str::of($data['sparkbooth_username'])->lower()->trim()->value() + : null; + } + + $password = $data['sparkbooth_password'] ?? null; + unset($data['sparkbooth_password']); + + if (! empty($password)) { + $data['sparkbooth_password'] = $password; + } elseif ($isCreate && empty($password)) { + $data['sparkbooth_password'] = Str::random(24); + } + + return $data; + } } diff --git a/app/Filament/Resources/Galleries/Schemas/GalleryForm.php b/app/Filament/Resources/Galleries/Schemas/GalleryForm.php index 1ffa6e6..988d495 100644 --- a/app/Filament/Resources/Galleries/Schemas/GalleryForm.php +++ b/app/Filament/Resources/Galleries/Schemas/GalleryForm.php @@ -3,71 +3,125 @@ namespace App\Filament\Resources\Galleries\Schemas; use Filament\Forms\Components\DateTimePicker; +use Filament\Forms\Components\Select; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\Toggle; use Filament\Schemas\Components\Section; +use Filament\Schemas\Components\Tabs; +use Filament\Schemas\Components\Tabs\Tab; use Filament\Schemas\Components\View; use Filament\Schemas\Schema; use Illuminate\Support\Facades\URL; +use Illuminate\Support\Str; class GalleryForm { public static function configure(Schema $schema): Schema { return $schema + ->columns(1) ->components([ - Section::make('Details') - ->columns(2) - ->schema([ - TextInput::make('name') - ->label('Name') - ->required(), - TextInput::make('slug') - ->label('Slug') - ->disabled() - ->dehydrated(true) - ->helperText('Wird automatisch erzeugt'), - TextInput::make('title') - ->label('Titel') - ->required(), - TextInput::make('images_path') - ->label('Bilder-Pfad') - ->helperText('Relativer Pfad unter public/storage') - ->required(), - Toggle::make('is_public') - ->label('Öffentlich') - ->default(true), - Toggle::make('allow_ai_styles') - ->label('AI-Stile erlauben') - ->default(true), - Toggle::make('allow_print') - ->label('Drucken erlauben') - ->default(true), - Toggle::make('require_password') - ->label('Passwortschutz aktiv') - ->default(false), - TextInput::make('password') - ->label('Neues Passwort') - ->password() - ->revealable() - ->dehydrated(false) - ->helperText('Leer lassen, um das bestehende Passwort zu behalten.'), - DateTimePicker::make('expires_at') - ->label('Ablaufdatum') - ->native(false) - ->seconds(false), - TextInput::make('access_duration_minutes') - ->label('Zugriffsdauer (Minuten)') - ->numeric() - ->minValue(1) - ->nullable() - ->helperText('Optional: Zeitfenster nach dem ersten Unlock.'), - ]), - View::make('filament.components.gallery-link') - ->columnSpanFull() - ->visible(fn (?object $record) => (bool) $record?->id) - ->viewData(fn (?object $record) => [ - 'url' => $record ? URL::route('gallery.show', $record) : null, + Tabs::make('gallery_tabs') + ->tabs([ + Tab::make('Galerie') + ->schema([ + Section::make('Details') + ->columns(2) + ->schema([ + TextInput::make('name') + ->label('Name') + ->required(), + TextInput::make('slug') + ->label('Slug') + ->disabled() + ->dehydrated(true) + ->helperText('Wird automatisch erzeugt'), + TextInput::make('title') + ->label('Titel') + ->required(), + TextInput::make('images_path') + ->label('Bilder-Pfad') + ->helperText('Relativer Pfad unter public/storage') + ->required(), + Toggle::make('is_public') + ->label('Öffentlich') + ->default(true), + Toggle::make('allow_ai_styles') + ->label('AI-Stile erlauben') + ->default(true), + Toggle::make('allow_print') + ->label('Drucken erlauben') + ->default(true), + Toggle::make('require_password') + ->label('Passwortschutz aktiv') + ->default(false), + TextInput::make('password') + ->label('Neues Passwort') + ->password() + ->revealable() + ->dehydrated(false) + ->helperText('Leer lassen, um das bestehende Passwort zu behalten.'), + DateTimePicker::make('expires_at') + ->label('Ablaufdatum') + ->native(false) + ->seconds(false), + TextInput::make('access_duration_minutes') + ->label('Zugriffsdauer (Minuten)') + ->numeric() + ->minValue(1) + ->nullable() + ->helperText('Optional: Zeitfenster nach dem ersten Unlock.'), + ]), + View::make('filament.components.gallery-link') + ->columnSpanFull() + ->visible(fn (?object $record) => (bool) $record?->id) + ->viewData(fn (?object $record) => [ + 'url' => $record ? URL::route('gallery.show', $record) : null, + ]), + ]), + Tab::make('Sparkbooth') + ->schema([ + Section::make('Sparkbooth Upload') + ->columns(2) + ->schema([ + Toggle::make('upload_enabled') + ->label('Uploads aktivieren') + ->helperText('Steuert, ob Sparkbooth-Uploads erlaubt sind.') + ->default(false), + Select::make('sparkbooth_response_format') + ->label('Standard-Antwortformat') + ->options([ + 'json' => 'JSON', + 'xml' => 'XML', + ]) + ->default('json'), + TextInput::make('sparkbooth_username') + ->label('Sparkbooth Benutzername') + ->helperText('Wird in Sparkbooth unter „Username“ eingetragen. Erlaubt sind Buchstaben, Zahlen sowie ._-') + ->maxLength(64) + ->rule('regex:/^[A-Za-z0-9._-]+$/') + ->unique(table: \App\Models\Gallery::class, column: 'sparkbooth_username', ignoreRecord: true) + ->dehydrateStateUsing(fn (?string $state): ?string => $state ? Str::of($state)->lower()->trim()->value() : null), + TextInput::make('sparkbooth_password') + ->label('Sparkbooth Passwort (neu)') + ->password() + ->revealable() + ->dehydrated(false) + ->afterStateHydrated(fn (callable $set) => $set('sparkbooth_password', null)) + ->helperText('Leer lassen, um das bestehende Passwort zu behalten.'), + ]), + View::make('filament.pages.partials.sparkbooth-token') + ->columnSpanFull() + ->visible(fn (?object $record) => (bool) $record?->id) + ->viewData(fn (?object $record) => [ + 'upload_url' => URL::route('api.sparkbooth.upload'), + 'gallery_url' => $record ? URL::route('gallery.show', $record) : null, + 'sparkbooth_username' => $record?->sparkbooth_username, + 'sparkbooth_password' => $record?->sparkbooth_password, + 'response_format' => $record?->sparkbooth_response_format, + 'gallery' => $record ? $record->only(['slug', 'images_path']) : null, + ]), + ]), ]), ]); } diff --git a/app/Models/Gallery.php b/app/Models/Gallery.php index d15a793..7c73988 100644 --- a/app/Models/Gallery.php +++ b/app/Models/Gallery.php @@ -74,4 +74,15 @@ class Gallery extends Model return $password; } + + public function clearSparkboothConnection(): void + { + $this->forceFill([ + 'upload_token_hash' => null, + 'upload_token_expires_at' => null, + 'sparkbooth_username' => null, + 'sparkbooth_password' => null, + 'upload_enabled' => false, + ])->save(); + } } diff --git a/database/seeders/GallerySeeder.php b/database/seeders/GallerySeeder.php index cc5533c..ef61698 100644 --- a/database/seeders/GallerySeeder.php +++ b/database/seeders/GallerySeeder.php @@ -18,11 +18,11 @@ class GallerySeeder extends Seeder $settings = app(GeneralSettings::class); Gallery::firstOrCreate( - ['slug' => Str::uuid()->toString()], + ['images_path' => 'uploads'], [ + 'slug' => Str::uuid()->toString(), 'name' => 'Default Gallery', 'title' => $settings->gallery_heading ?? 'Style Gallery', - 'images_path' => 'uploads', 'is_public' => true, 'allow_ai_styles' => true, 'allow_print' => true, diff --git a/database/seeders/RoleSeeder.php b/database/seeders/RoleSeeder.php index c801ab3..f43b99c 100644 --- a/database/seeders/RoleSeeder.php +++ b/database/seeders/RoleSeeder.php @@ -2,9 +2,8 @@ namespace Database\Seeders; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; -use Illuminate\Database\Seeder; use App\Models\Role; +use Illuminate\Database\Seeder; class RoleSeeder extends Seeder { @@ -13,7 +12,7 @@ class RoleSeeder extends Seeder */ public function run(): void { - Role::create(['name' => 'admin']); - Role::create(['name' => 'user']); + Role::firstOrCreate(['name' => 'admin']); + Role::firstOrCreate(['name' => 'user']); } -} \ No newline at end of file +} diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php index abbdee5..c2875cd 100644 --- a/database/seeders/UserSeeder.php +++ b/database/seeders/UserSeeder.php @@ -2,9 +2,8 @@ namespace Database\Seeders; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; -use Illuminate\Database\Seeder; use App\Models\User; +use Illuminate\Database\Seeder; use Illuminate\Support\Facades\Hash; class UserSeeder extends Seeder @@ -14,11 +13,15 @@ class UserSeeder extends Seeder */ public function run(): void { - User::create([ - 'name' => 'admin', - 'email' => 'admin@admin.com', - 'password' => Hash::make('admin'), - 'role_id' => 1 - ]); + $role = \App\Models\Role::firstOrCreate(['name' => 'admin']); + + User::updateOrCreate( + ['email' => 'admin@admin.com'], + [ + 'name' => 'admin', + 'password' => Hash::make('admin'), + 'role_id' => $role->id, + ] + ); } -} \ No newline at end of file +} diff --git a/resources/views/filament/pages/partials/sparkbooth-token.blade.php b/resources/views/filament/pages/partials/sparkbooth-token.blade.php index 8601532..590b76a 100644 --- a/resources/views/filament/pages/partials/sparkbooth-token.blade.php +++ b/resources/views/filament/pages/partials/sparkbooth-token.blade.php @@ -1,62 +1,69 @@ +@php + $uploadUrl = $upload_url ?? null; + $galleryUrl = $gallery_url ?? null; + $username = $sparkbooth_username ?? '—'; + $password = $sparkbooth_password ?? '—'; + $format = strtoupper($response_format ?? 'JSON'); + $imagesPath = $gallery['images_path'] ?? 'uploads'; +@endphp +

Upload Endpoint

-

{{ $upload_url }}

+

{{ $uploadUrl }}

+

Methode: POST (multipart/form-data) mit Username/Password.

Sparkbooth Benutzername

-

{{ $sparkbooth_username ?? '—' }}

+

{{ $username }}

Eintragen im Sparkbooth Custom Upload Dialog unter „Username“.

Sparkbooth Passwort

-

{{ $sparkbooth_password ?? '—' }}

+

{{ $password }}

Eintragen unter „Password“.

-
-

Galerie-Link

-

{{ $gallery_url }}

-

Slug: {{ $gallery['slug'] }}, Pfad: storage/{{ $gallery['images_path'] }}

-

Antwortformat

-

{{ strtoupper($response_format ?? 'JSON') }}

-

Muss mit „JSON Response“ oder „XML Response“ in Sparkbooth übereinstimmen.

-
-
- -
-
-

Legacy Upload Token

-

{{ $upload_token }}

-

Optionales Secret (Feld „token“) für ältere Integrationen.

+

{{ $format }}

+

In Sparkbooth „JSON Response“ oder „XML Response“ passend auswählen.

Sparkbooth Hinweise

  • Uploader „Custom Upload“ wählen.
  • Username & Password wie oben eintragen.
  • -
  • Falls JSON Response aktiviert ist, hier ebenfalls JSON wählen (gleiches gilt für XML).
  • +
  • Optional: Name/Email/Message Felder in Sparkbooth setzen.
+
+

Beispiel (curl)

+
curl -X POST {{ $uploadUrl }} \
+  -F "media=@/pfad/zum/foto.jpg" \
+  -F "username={{ $username }}" \
+  -F "password={{ $password }}" \
+  -F "response_format={{ strtolower($format) }}" \
+  -F "name=Guest" \
+  -F "message=Hallo von der Fotobox"
+
+
+
+

Galerie-Link

+

{{ $galleryUrl }}

+

Slug: {{ $gallery['slug'] ?? '—' }}, Pfad: storage/{{ $imagesPath }}

+

QR Code (Galerie)

- {!! \SimpleSoftwareIO\QrCode\Facades\QrCode::size(200)->margin(1)->generate($gallery_url) !!} -
-
-
-

QR Code (Upload)

-
- {!! \SimpleSoftwareIO\QrCode\Facades\QrCode::size(200)->margin(1)->generate($upload_url.'?token='.$upload_token) !!} + {!! \SimpleSoftwareIO\QrCode\Facades\QrCode::size(200)->margin(1)->generate($galleryUrl) !!}
diff --git a/resources/views/filament/pages/sparkbooth-setup.blade.php b/resources/views/filament/pages/sparkbooth-setup.blade.php index da66da0..01db741 100644 --- a/resources/views/filament/pages/sparkbooth-setup.blade.php +++ b/resources/views/filament/pages/sparkbooth-setup.blade.php @@ -63,10 +63,8 @@
-

QR Code (Upload)

-
- {!! \SimpleSoftwareIO\QrCode\Facades\QrCode::size(200)->margin(1)->generate($result['upload_url'].'?token='.$result['upload_token']) !!} -
+

Legacy Upload (Token)

+

Nur falls eine alte Integration noch Token statt Username/Password nutzt.

diff --git a/tests/Feature/Filament/SparkboothConnectionsTest.php b/tests/Feature/Filament/SparkboothConnectionsTest.php new file mode 100644 index 0000000..42020f8 --- /dev/null +++ b/tests/Feature/Filament/SparkboothConnectionsTest.php @@ -0,0 +1,53 @@ + 'Admin']); + Role::create(['name' => 'User']); + + $admin = User::factory()->create([ + 'role_id' => $adminRole->id, + ]); + + $this->actingAs($admin); + Filament::setCurrentPanel('admin'); + + $gallery = Gallery::factory()->create([ + 'upload_enabled' => true, + 'sparkbooth_username' => 'spark-test', + 'sparkbooth_password' => 'secret-123', + ]); + + $gallery->setUploadToken('tokentest'); + $gallery->save(); + + Livewire::test(SparkboothConnections::class) + ->callTableAction('deleteConnection', $gallery); + + $updated = $gallery->fresh(); + + $this->assertDatabaseHas('galleries', ['id' => $gallery->id]); + $this->assertNotNull($updated); + $this->assertNull($updated->upload_token_hash); + $this->assertNull($updated->upload_token_expires_at); + $this->assertNull($updated->sparkbooth_username); + $this->assertNull($updated->sparkbooth_password); + $this->assertFalse($updated->upload_enabled); + $this->assertSame(0, Gallery::query()->whereNotNull('upload_token_hash')->count()); + } +}