diff --git a/.kilocode/mcp.json b/.kilocode/mcp.json index 618b639..149a607 100644 --- a/.kilocode/mcp.json +++ b/.kilocode/mcp.json @@ -1,16 +1 @@ -{ - "mcpServers": { - "laravel-boost": { - "command": "php", - "args": [ - "artisan", - "boost:mcp" - ], - "transport": "stdio", - "alwaysAllow": [ - "application-info" - ], - "disabled": false - } - } -} \ No newline at end of file +{"mcpServers":{"laravel-boost":{"command":"php","args":["artisan","boost:mcp"],"transport":"stdio","alwaysAllow":["application-info","search-docs"],"disabled":false}}} \ No newline at end of file diff --git a/app/Filament/Pages/SparkboothConnections.php b/app/Filament/Pages/SparkboothConnections.php new file mode 100644 index 0000000..84d819c --- /dev/null +++ b/app/Filament/Pages/SparkboothConnections.php @@ -0,0 +1,88 @@ +heading('Vorhandene Sparkbooth-Verbindungen') + ->query( + Gallery::query() + ->whereNotNull('upload_token_hash') + ->orderByDesc('created_at') + ) + ->columns([ + TextColumn::make('name') + ->label('Name') + ->searchable(), + TextColumn::make('slug') + ->label('Slug') + ->copyable() + ->toggleable(), + TextColumn::make('images_path') + ->label('Upload-Pfad') + ->copyable() + ->toggleable(), + TextColumn::make('created_at') + ->label('Angelegt') + ->since() + ->sortable(), + ]) + ->actions([ + Action::make('show') + ->label('Zugangsdaten anzeigen') + ->icon('heroicon-o-key') + ->color('primary') + ->modalHeading('Upload-Zugangsdaten') + ->modalSubmitAction(false) + ->modalCancelActionLabel('Schließen') + ->modalContent(function (Gallery $record) { + $plainToken = $record->regenerateUploadToken(); + + $data = [ + 'gallery' => $record->only(['id', 'name', 'slug', 'images_path']), + 'upload_token' => $plainToken, + 'upload_url' => route('api.sparkbooth.upload'), + 'gallery_url' => route('gallery.show', $record), + ]; + + Notification::make() + ->title('Upload-Token wurde erneuert.') + ->body('Bitte verwende den neuen Token in Sparkbooth.') + ->success() + ->send(); + + return view('filament.pages.partials.sparkbooth-token', $data); + }), + ]) + ->emptyStateHeading('Keine Sparkbooth-Verbindungen') + ->emptyStateDescription('Lege eine neue Verbindung an oder aktiviere Uploads für eine Galerie.'); + } +} diff --git a/app/Filament/Pages/SparkboothSetup.php b/app/Filament/Pages/SparkboothSetup.php index 5b4a08d..c52713d 100644 --- a/app/Filament/Pages/SparkboothSetup.php +++ b/app/Filament/Pages/SparkboothSetup.php @@ -49,7 +49,10 @@ class SparkboothSetup extends Page implements HasForms ->label('Upload-Pfad') ->helperText('Relativ zu public/storage, z.B. uploads/event-xyz') ->default(fn () => 'uploads/'.Str::slug('event-'.Str::random(4))) - ->required(), + ->required() + ->rule('regex:/^[A-Za-z0-9._\\/-]+$/') + ->unique(table: Gallery::class, column: 'images_path') + ->dehydrateStateUsing(fn (string $state): string => trim($state, '/')), Toggle::make('allow_print') ->label('Drucken erlauben') ->default(true), @@ -100,8 +103,15 @@ class SparkboothSetup extends Page implements HasForms { return [ \Filament\Actions\Action::make('save') - ->label('Setup erstellen') + ->label('Speichern') + ->icon('heroicon-m-check') + ->color('primary') ->submit('save'), + \Filament\Actions\Action::make('cancel') + ->label('Abbrechen') + ->icon('heroicon-m-x-mark') + ->color('gray') + ->url(route('filament.admin.pages.dashboard')), ]; } } diff --git a/database/migrations/2025_12_05_204650_add_unique_index_to_galleries_images_path.php b/database/migrations/2025_12_05_204650_add_unique_index_to_galleries_images_path.php new file mode 100644 index 0000000..e396479 --- /dev/null +++ b/database/migrations/2025_12_05_204650_add_unique_index_to_galleries_images_path.php @@ -0,0 +1,60 @@ +select('images_path', DB::raw('COUNT(*) as aggregate')) + ->whereNotNull('images_path') + ->groupBy('images_path') + ->having('aggregate', '>', 1) + ->pluck('images_path') + ->all(); + + if (! empty($duplicates)) { + foreach ($duplicates as $path) { + $idsToKeep = DB::table('galleries') + ->where('images_path', $path) + ->orderBy('id') + ->limit(1) + ->pluck('id'); + + DB::table('galleries') + ->where('images_path', $path) + ->whereNotIn('id', $idsToKeep) + ->delete(); + } + } + + $table->unique('images_path'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('galleries', function (Blueprint $table) { + if (! Schema::hasColumn('galleries', 'images_path')) { + return; + } + + $table->dropUnique('galleries_images_path_unique'); + }); + } +}; diff --git a/resources/views/filament/pages/partials/sparkbooth-token.blade.php b/resources/views/filament/pages/partials/sparkbooth-token.blade.php new file mode 100644 index 0000000..3383a79 --- /dev/null +++ b/resources/views/filament/pages/partials/sparkbooth-token.blade.php @@ -0,0 +1,34 @@ +
Upload Endpoint
+{{ $upload_url }}
+Upload Token
+{{ $upload_token }}
+Trage diesen Token in Sparkbooth unter „Upload Secret“ ein.
+Galerie-Link
+{{ $gallery_url }}
+Slug: {{ $gallery['slug'] }}, Pfad: storage/{{ $gallery['images_path'] }}
+QR Code (Galerie)
+QR Code (Upload)
+