mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-15 09:14:08 +00:00
Refactor FileDisk system with per-disk unique names and disk assignments UI
Major changes to the file disk subsystem:
- Each FileDisk now gets a unique Laravel disk name (disk_{id}) instead
of temp_{driver}, fixing the bug where multiple local disks with
different roots overwrote each other's config.
- Move disk registration logic from FileDisk model to FileDiskService
(registerDisk, getDiskName). Model keeps only getDecodedCredentials
and a deprecated setConfig() wrapper.
- Add Disk Assignments admin UI (File Disk tab) with three purpose
dropdowns: Media Storage, PDF Storage, Backup Storage. Stored as
settings (media_disk_id, pdf_disk_id, backup_disk_id).
- Backup tab now uses the assigned backup disk instead of a per-backup
dropdown. BackupsController refactored to use BackupService which
centralizes disk resolution. Removed stale 4-second cache.
- Add local_public disk to config/filesystems.php so system disks
are properly defined.
- Local disk roots stored relative to storage/app/ with hint text
in the admin modal explaining the convention.
- Fix BaseModal watchEffect -> watch to prevent infinite request
loops on the File Disk page.
- Fix string/number comparison for disk purpose IDs from settings.
- Add safeguards: prevent deleting disks with files, warn on
purpose change, prevent deleting system disks.
This commit is contained in:
@@ -4,61 +4,47 @@ namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\CreateBackupJob;
|
||||
use App\Models\FileDisk;
|
||||
use App\Rules\Backup\PathToZip;
|
||||
use App\Services\Backup\BackupService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Spatie\Backup\BackupDestination\Backup;
|
||||
use Spatie\Backup\BackupDestination\BackupDestination;
|
||||
use Spatie\Backup\Helpers\Format;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class BackupsController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly BackupService $backupService,
|
||||
) {}
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$this->authorize('manage backups');
|
||||
|
||||
$configuredBackupDisks = config('backup.backup.destination.disks');
|
||||
|
||||
try {
|
||||
if ($request->file_disk_id) {
|
||||
$fileDisk = FileDisk::find($request->file_disk_id);
|
||||
if ($fileDisk) {
|
||||
$fileDisk->setConfig();
|
||||
$prefix = env('DYNAMIC_DISK_PREFIX', 'temp_');
|
||||
config(['backup.backup.destination.disks' => [$prefix.$fileDisk->driver]]);
|
||||
$configuredBackupDisks = config('backup.backup.destination.disks');
|
||||
}
|
||||
}
|
||||
$destination = $this->backupService->getDestination($request->file_disk_id);
|
||||
|
||||
$backupDestination = BackupDestination::create(config('filesystems.default'), config('backup.backup.name'));
|
||||
|
||||
$backups = Cache::remember("backups-{$request->file_disk_id}", now()->addSeconds(4), function () use ($backupDestination) {
|
||||
return $backupDestination
|
||||
->backups()
|
||||
->map(function (Backup $backup) {
|
||||
return [
|
||||
'path' => $backup->path(),
|
||||
'created_at' => $backup->date()->format('Y-m-d H:i:s'),
|
||||
'size' => Format::humanReadableSize($backup->sizeInBytes()),
|
||||
];
|
||||
})
|
||||
->toArray();
|
||||
});
|
||||
$backups = $destination
|
||||
->backups()
|
||||
->map(function (Backup $backup) {
|
||||
return [
|
||||
'path' => $backup->path(),
|
||||
'created_at' => $backup->date()->format('Y-m-d H:i:s'),
|
||||
'size' => Format::humanReadableSize($backup->sizeInBytes()),
|
||||
];
|
||||
})
|
||||
->toArray();
|
||||
|
||||
return response()->json([
|
||||
'backups' => $backups,
|
||||
'disks' => $configuredBackupDisks,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'backups' => [],
|
||||
'error' => 'invalid_disk_credentials',
|
||||
'error_message' => $e->getMessage(),
|
||||
'disks' => $configuredBackupDisks,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -82,9 +68,9 @@ class BackupsController extends Controller
|
||||
'path' => ['required', new PathToZip],
|
||||
]);
|
||||
|
||||
$backupDestination = BackupDestination::create(config('filesystems.default'), config('backup.backup.name'));
|
||||
$destination = $this->backupService->getDestination($request->file_disk_id);
|
||||
|
||||
$backupDestination
|
||||
$destination
|
||||
->backups()
|
||||
->first(function (Backup $backup) use ($validated) {
|
||||
return $backup->path() === $validated['path'];
|
||||
@@ -102,9 +88,9 @@ class BackupsController extends Controller
|
||||
'path' => ['required', new PathToZip],
|
||||
]);
|
||||
|
||||
$backupDestination = BackupDestination::create(config('filesystems.default'), config('backup.backup.name'));
|
||||
$destination = $this->backupService->getDestination($request->file_disk_id);
|
||||
|
||||
$backup = $backupDestination->backups()->first(function (Backup $backup) use ($validated) {
|
||||
$backup = $destination->backups()->first(function (Backup $backup) use ($validated) {
|
||||
return $backup->path() === $validated['path'];
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\DiskEnvironmentRequest;
|
||||
use App\Http\Resources\FileDiskResource;
|
||||
use App\Models\FileDisk;
|
||||
use App\Models\Setting;
|
||||
use App\Services\FileDiskService;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -91,8 +92,10 @@ class DiskController extends Controller
|
||||
$diskData = [];
|
||||
switch ($disk) {
|
||||
case 'local':
|
||||
// Path is relative to storage/app/.
|
||||
// e.g., "backups" resolves to storage/app/backups/ at runtime.
|
||||
$diskData = [
|
||||
'root' => config('filesystems.disks.local.root'),
|
||||
'root' => '',
|
||||
];
|
||||
|
||||
break;
|
||||
@@ -216,11 +219,50 @@ class DiskController extends Controller
|
||||
],
|
||||
];
|
||||
|
||||
$default = config('filesystems.default');
|
||||
$defaultDisk = FileDisk::where('set_as_default', true)->first();
|
||||
|
||||
return response()->json([
|
||||
'drivers' => $drivers,
|
||||
'default' => $default,
|
||||
'default' => $defaultDisk?->driver ?? 'local',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getDiskPurposes(): JsonResponse
|
||||
{
|
||||
$this->authorize('manage file disk');
|
||||
|
||||
$defaultDisk = FileDisk::where('set_as_default', true)->first();
|
||||
$defaultId = $defaultDisk?->id;
|
||||
|
||||
return response()->json([
|
||||
'media_disk_id' => Setting::getSetting('media_disk_id') ?? $defaultId,
|
||||
'pdf_disk_id' => Setting::getSetting('pdf_disk_id') ?? $defaultId,
|
||||
'backup_disk_id' => Setting::getSetting('backup_disk_id') ?? $defaultId,
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateDiskPurposes(Request $request): JsonResponse
|
||||
{
|
||||
$this->authorize('manage file disk');
|
||||
|
||||
$request->validate([
|
||||
'media_disk_id' => 'nullable|exists:file_disks,id',
|
||||
'pdf_disk_id' => 'nullable|exists:file_disks,id',
|
||||
'backup_disk_id' => 'nullable|exists:file_disks,id',
|
||||
]);
|
||||
|
||||
if ($request->has('media_disk_id')) {
|
||||
Setting::setSetting('media_disk_id', $request->media_disk_id);
|
||||
}
|
||||
|
||||
if ($request->has('pdf_disk_id')) {
|
||||
Setting::setSetting('pdf_disk_id', $request->pdf_disk_id);
|
||||
}
|
||||
|
||||
if ($request->has('backup_disk_id')) {
|
||||
Setting::setSetting('backup_disk_id', $request->backup_disk_id);
|
||||
}
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user