gestione documentale avanzata 1 parte

This commit is contained in:
2026-05-28 09:34:28 +02:00
parent 3471befb1a
commit f2b0833b90
34482 changed files with 4312269 additions and 546 deletions
@@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Models\DocumentoCartella;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class DocumentoCartellaController extends Controller
{
public function store(Request $request): JsonResponse
{
$this->authorizeWrite('documenti');
$data = $request->validate([
'nome' => 'required|string|max:255',
'parent_id' => 'nullable|integer|exists:documenti_cartelle,id',
]);
$cartella = DocumentoCartella::create([
'nome' => $data['nome'],
'parent_id' => $data['parent_id'] ?? null,
]);
return response()->json([
'success' => true,
'cartella' => $cartella,
]);
}
public function update(Request $request, int $id): JsonResponse
{
$this->authorizeWrite('documenti');
$cartella = DocumentoCartella::findOrFail($id);
$data = $request->validate([
'nome' => 'required|string|max:255',
'parent_id' => 'nullable|integer|exists:documenti_cartelle,id',
]);
$updateData = ['nome' => $data['nome']];
if (array_key_exists('parent_id', $data)) {
if ((int) $data['parent_id'] === $id) {
return response()->json(['success' => false, 'message' => 'Una cartella non può essere spostata dentro se stessa.'], 422);
}
$updateData['parent_id'] = $data['parent_id'];
}
$cartella->update($updateData);
return response()->json(['success' => true]);
}
public function destroy(int $id): JsonResponse
{
$this->authorizeDelete('documenti');
$cartella = DocumentoCartella::findOrFail($id);
$documentCount = $cartella->documenti()->count();
if ($documentCount > 0) {
return response()->json([
'success' => false,
'message' => "Impossibile eliminare: {$documentCount} documento(i) presenti nella cartella. Sposta o elimina prima i documenti.",
], 409);
}
$childrenCount = $cartella->children()->count();
if ($childrenCount > 0) {
return response()->json([
'success' => false,
'message' => "Impossibile eliminare: {$childrenCount} sottocartella(e) presenti. Elimina prima le sottocartelle.",
], 409);
}
$cartella->delete();
return response()->json(['success' => true]);
}
public function breadcrumb(int $id): JsonResponse
{
$cartella = DocumentoCartella::with('parent')->findOrFail($id);
$ancestors = $cartella->ancestors();
$items = [];
$items[] = ['id' => null, 'nome' => 'Tutti i documenti'];
foreach ($ancestors as $anc) {
$items[] = ['id' => $anc->id, 'nome' => $anc->nome];
}
$items[] = ['id' => $cartella->id, 'nome' => $cartella->nome];
return response()->json(['items' => $items]);
}
}
+315 -38
View File
@@ -1,23 +1,73 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Models\AppSetting;
use App\Models\Documento;
use App\Models\DocumentoCartella;
use App\Models\Individuo;
use App\Models\Gruppo;
use App\Models\Evento;
use App\Models\StorageRepository;
use App\Models\TipologiaDocumento;
use App\Services\StorageRepositoryService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class DocumentoController extends Controller
{
public function __construct(
private readonly StorageRepositoryService $repoService
) {}
private function resolveStorageDisk(?Documento $documento = null): string
{
if ($documento && $documento->repository_id) {
return 'local';
}
if ($documento && $documento->storage_disk) {
return $documento->storage_disk;
}
return AppSetting::getDocumentiStorageDisk();
}
public function index(Request $request)
{
$this->authorizeRead('documenti');
$documenti = Documento::with(['user', 'target'])
->orderByDesc('created_at')
->paginate(20);
$folderId = $request->get('folder_id');
$repoId = $request->get('repository_id');
$query = Documento::with(['user', 'target', 'cartella', 'repository'])
->orderByDesc('created_at');
if ($repoId) {
$query->where('repository_id', $repoId);
} elseif ($folderId) {
$query->where('cartella_id', $folderId);
} else {
$query->whereNull('repository_id');
}
$documenti = $query->paginate(20);
$cartelle = DocumentoCartella::with('children')
->whereNull('parent_id')
->orderBy('nome')
->get();
$currentFolder = $folderId ? DocumentoCartella::find($folderId) : null;
$breadcrumb = $currentFolder ? $currentFolder->ancestors() : [];
$sottoCartelle = $folderId
? DocumentoCartella::where('parent_id', $currentFolder->id)->orderBy('nome')->get()
: DocumentoCartella::whereNull('parent_id')->orderBy('nome')->get();
$currentRepo = $repoId ? StorageRepository::find($repoId) : null;
$repositories = StorageRepository::attivi()->get();
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
$gruppi = Gruppo::orderBy('nome')->get();
@@ -25,7 +75,22 @@ class DocumentoController extends Controller
$mailingLists = \App\Models\MailingList::orderBy('nome')->get();
$tipologie = TipologiaDocumento::attive();
return view('documenti.index', compact('documenti', 'individui', 'gruppi', 'eventi', 'mailingLists', 'tipologie'));
$cartelleMoveOptions = DocumentoCartella::whereNull('parent_id')
->with('children')
->orderBy('nome')
->get()
->map(fn($f) => [
'id' => $f->id,
'nome' => $f->nome,
'children' => $f->children->map(fn($c) => ['id' => $c->id, 'nome' => $c->nome]),
]);
return view('documenti.index', compact(
'documenti', 'cartelle', 'currentFolder', 'breadcrumb', 'sottoCartelle',
'currentRepo', 'repositories',
'individui', 'gruppi', 'eventi', 'mailingLists', 'tipologie',
'cartelleMoveOptions'
));
}
public function create()
@@ -37,6 +102,7 @@ class DocumentoController extends Controller
$tipologie = TipologiaDocumento::attive();
return view('documenti.create', compact('individui', 'gruppi', 'eventi', 'tipologie'));
}
public function edit($documento)
{
$this->authorizeWrite('documenti');
@@ -132,10 +198,33 @@ class DocumentoController extends Controller
'visibilita' => 'required|in:pubblico,individuo,gruppo',
'visibilita_target_id' => 'nullable|integer',
'visibilita_target_type' => 'nullable|string',
'cartella_id' => 'nullable|integer|exists:documenti_cartelle,id',
'repository_id' => 'nullable|integer|exists:storage_repositories,id',
]);
$file = $request->file('file');
$path = $file->store('documenti', 'local');
$repositoryId = $data['repository_id'] ?? null;
if ($repositoryId) {
$repo = StorageRepository::findOrFail($repositoryId);
$filesystem = $this->repoService->buildFilesystem($repo);
if (!$filesystem) {
return back()->with('error', 'Impossibile connettersi al repository remoto.');
}
$extension = $request->file('file')->getClientOriginalExtension();
$filename = $data['nome_file'] . '.' . ($extension ?: $request->file('file')->guessExtension());
$path = 'documents/' . date('Y/m/d') . '/' . $filename;
$stream = fopen($request->file('file')->getRealPath(), 'rb');
$filesystem->writeStream($path, $stream);
fclose($stream);
$filePath = $path;
} else {
$disk = $this->resolveStorageDisk();
$storagePath = AppSetting::getDocumentiStoragePath();
$filePath = $request->file('file')->store($storagePath, $disk);
}
$visibilitaTargetType = null;
if ($data['visibilita'] === 'individuo' && $data['visibilita_target_id']) {
@@ -146,15 +235,18 @@ class DocumentoController extends Controller
Documento::create([
'nome_file' => $data['nome_file'],
'file_path' => $path,
'file_path' => $filePath,
'storage_disk' => $disk ?? null,
'tipo' => 'upload',
'tipologia' => $data['tipologia'],
'visibilita' => $data['visibilita'],
'visibilita_target_id' => $data['visibilita_target_id'] ?? null,
'visibilita_target_type' => $visibilitaTargetType,
'mime_type' => $file->getMimeType(),
'dimensione' => $file->getSize(),
'mime_type' => $request->file('file')->getMimeType(),
'dimensione' => $request->file('file')->getSize(),
'user_id' => auth()->id(),
'cartella_id' => $data['cartella_id'] ?? null,
'repository_id' => $repositoryId,
]);
$redirect = $request->_redirect ?? url()->previous();
@@ -166,14 +258,40 @@ class DocumentoController extends Controller
$this->authorizeRead('documenti');
$documento = Documento::findOrFail($documento);
if (!Storage::disk('local')->exists($documento->file_path)) {
abort(404);
}
$extension = pathinfo($documento->file_path, PATHINFO_EXTENSION);
$filename = $extension ? $documento->nome_file . '.' . $extension : $documento->nome_file;
return Storage::disk('local')->download(
if ($documento->repository_id) {
$repo = StorageRepository::find($documento->repository_id);
if (!$repo) {
abort(404);
}
$filesystem = $this->repoService->buildFilesystem($repo);
if (!$filesystem || !$filesystem->fileExists($documento->file_path)) {
abort(404);
}
$stream = $filesystem->readStream($documento->file_path);
if (!$stream) {
abort(404);
}
return response()->stream(function () use ($stream) {
fpassthru($stream);
fclose($stream);
}, 200, [
'Content-Type' => $documento->mime_type ?: 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
'Content-Length' => $documento->dimensione,
]);
}
$disk = $this->resolveStorageDisk($documento);
if (!Storage::disk($disk)->exists($documento->file_path)) {
abort(404);
}
return Storage::disk($disk)->download(
$documento->file_path,
$filename,
['Content-Type' => $documento->mime_type]
@@ -185,26 +303,49 @@ class DocumentoController extends Controller
$this->authorizeRead('documenti');
$documento = Documento::findOrFail($documento);
if (!Storage::disk('local')->exists($documento->file_path)) {
abort(404);
}
$fullPath = Storage::disk('local')->path($documento->file_path);
$mimeType = $documento->mime_type;
$previewableMimeTypes = [
'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml',
'application/pdf',
'text/plain', 'text/html', 'text/csv',
];
if (in_array($mimeType, $previewableMimeTypes)) {
return response()->file($fullPath, ['Content-Type' => $mimeType]);
if (!in_array($documento->mime_type, $previewableMimeTypes)) {
return response()->view('documenti.preview-fallback', [
'documento' => $documento,
])->header('X-Preview-Fallback', 'true');
}
return response()->view('documenti.preview-fallback', [
'documento' => $documento,
])->header('X-Preview-Fallback', 'true');
if ($documento->repository_id) {
$repo = StorageRepository::find($documento->repository_id);
if (!$repo) {
abort(404);
}
$filesystem = $this->repoService->buildFilesystem($repo);
if (!$filesystem || !$filesystem->fileExists($documento->file_path)) {
abort(404);
}
$stream = $filesystem->readStream($documento->file_path);
if (!$stream) {
abort(404);
}
return response()->stream(function () use ($stream) {
fpassthru($stream);
fclose($stream);
}, 200, [
'Content-Type' => $documento->mime_type,
'Content-Disposition' => 'inline; filename="' . basename($documento->file_path) . '"',
]);
}
$disk = $this->resolveStorageDisk($documento);
if (!Storage::disk($disk)->exists($documento->file_path)) {
abort(404);
}
$fullPath = Storage::disk($disk)->path($documento->file_path);
return response()->file($fullPath, ['Content-Type' => $documento->mime_type]);
}
public function destroy($documento)
@@ -212,13 +353,24 @@ class DocumentoController extends Controller
$this->authorizeDelete('documenti');
$documento = Documento::findOrFail($documento);
if (Storage::disk('local')->exists($documento->file_path)) {
Storage::disk('local')->delete($documento->file_path);
if ($documento->repository_id) {
$repo = StorageRepository::find($documento->repository_id);
if ($repo) {
$filesystem = $this->repoService->buildFilesystem($repo);
if ($filesystem && $filesystem->fileExists($documento->file_path)) {
$filesystem->delete($documento->file_path);
}
}
} else {
$disk = $this->resolveStorageDisk($documento);
if (Storage::disk($disk)->exists($documento->file_path)) {
Storage::disk($disk)->delete($documento->file_path);
}
}
$documento->delete();
$redirect = request('_redirect', url('/'));
$redirect = request('_redirect', url()->previous());
return redirect($redirect)->with('success', 'Documento eliminato.');
}
@@ -364,21 +516,96 @@ class DocumentoController extends Controller
]);
}
public function massDestroy(Request $request)
public function massDownload(Request $request)
{
$this->authorizeDelete('documenti');
$this->authorizeRead('documenti');
$idsInput = $request->input('ids', '');
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
if (empty($ids)) {
return back()->with('error', 'Nessun documento selezionato.');
}
$documenti = Documento::whereIn('id', $ids)->get();
if ($documenti->isEmpty()) {
return back()->with('error', 'Nessun documento trovato.');
}
$zip = new \ZipArchive();
$zipPath = tempnam(sys_get_temp_dir(), 'docs_') . '.zip';
if ($zip->open($zipPath, \ZipArchive::CREATE) !== true) {
return back()->with('error', 'Impossibile creare l\'archivio ZIP.');
}
$addedCount = 0;
foreach ($documenti as $documento) {
if (Storage::disk('local')->exists($documento->file_path)) {
Storage::disk('local')->delete($documento->file_path);
$extension = pathinfo($documento->file_path, PATHINFO_EXTENSION);
$filename = $extension ? $documento->nome_file . '.' . $extension : $documento->nome_file;
try {
if ($documento->repository_id) {
$repo = StorageRepository::find($documento->repository_id);
if ($repo) {
$filesystem = $this->repoService->buildFilesystem($repo);
if ($filesystem && $filesystem->fileExists($documento->file_path)) {
$stream = $filesystem->readStream($documento->file_path);
if ($stream) {
$zip->addFromString($filename, stream_get_contents($stream));
fclose($stream);
$addedCount++;
}
}
}
} else {
$disk = $this->resolveStorageDisk($documento);
if (Storage::disk($disk)->exists($documento->file_path)) {
$zip->addFile(Storage::disk($disk)->path($documento->file_path), $filename);
$addedCount++;
}
}
} catch (\Exception $e) {
\Illuminate\Support\Facades\Log::warning('ZIP download skipped file: ' . $documento->id . ' - ' . $e->getMessage());
}
}
$zip->close();
if ($addedCount === 0) {
unlink($zipPath);
return back()->with('error', 'Nessun file disponibile per il download.');
}
return response()->download($zipPath, 'documenti.zip')->deleteFileAfterSend(true);
}
public function massDestroy(Request $request)
{
$this->authorizeDelete('documenti');
$idsInput = $request->input('ids', '');
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
if (empty($ids)) {
return back()->with('error', 'Nessun documento selezionato.');
}
$documenti = Documento::whereIn('id', $ids)->get();
foreach ($documenti as $documento) {
if ($documento->repository_id) {
$repo = StorageRepository::find($documento->repository_id);
if ($repo) {
$filesystem = $this->repoService->buildFilesystem($repo);
if ($filesystem && $filesystem->fileExists($documento->file_path)) {
$filesystem->delete($documento->file_path);
}
}
} else {
$disk = $this->resolveStorageDisk($documento);
if (Storage::disk($disk)->exists($documento->file_path)) {
Storage::disk($disk)->delete($documento->file_path);
}
}
$documento->delete();
}
@@ -391,7 +618,7 @@ class DocumentoController extends Controller
$this->authorizeWrite('documenti');
$idsInput = $request->input('ids', '');
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
if (empty($ids)) {
return back()->with('error', 'Nessun documento selezionato.');
}
@@ -399,6 +626,8 @@ class DocumentoController extends Controller
$data = $request->validate([
'tipologia' => 'nullable|string',
'visibilita' => 'nullable|string',
'visibilita_target_id' => 'nullable|integer',
'visibilita_target_type' => 'nullable|string',
]);
$tipologia = $data['tipologia'] ?? null;
@@ -425,6 +654,18 @@ class DocumentoController extends Controller
}
}
if (!empty($data['visibilita_target_id']) && !empty($data['visibilita_target_type'])) {
$typeMap = [
'individuo' => Individuo::class,
'gruppo' => Gruppo::class,
'evento' => Evento::class,
'mailing' => \App\Models\MailingList::class,
];
$updateData['visibilita'] = $data['visibilita_target_type'];
$updateData['visibilita_target_id'] = (int) $data['visibilita_target_id'];
$updateData['visibilita_target_type'] = $typeMap[$data['visibilita_target_type']] ?? $data['visibilita_target_type'];
}
Documento::whereIn('id', $ids)->update($updateData);
return back()->with('success', count($ids) . ' documenti aggiornati.');
@@ -435,7 +676,7 @@ class DocumentoController extends Controller
$this->authorizeWrite('documenti');
$idsInput = $request->input('ids', '');
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
if (empty($ids)) {
return back()->with('error', 'Nessun documento selezionato.');
}
@@ -460,4 +701,40 @@ class DocumentoController extends Controller
return back()->with('success', count($ids) . ' documenti associati.');
}
}
public function massMove(Request $request): RedirectResponse
{
$this->authorizeWrite('documenti');
$idsInput = $request->input('ids', '');
$ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []);
if (empty($ids)) {
return back()->with('error', 'Nessun documento selezionato.');
}
$data = $request->validate([
'cartella_id' => 'nullable|integer|exists:documenti_cartelle,id',
]);
Documento::whereIn('id', $ids)->update([
'cartella_id' => $data['cartella_id'] ?? null,
]);
return back()->with('success', count($ids) . ' documenti spostati.');
}
public function moveDocumento(Request $request, int $id): JsonResponse
{
$this->authorizeWrite('documenti');
$documento = Documento::findOrFail($id);
$data = $request->validate([
'cartella_id' => 'nullable|integer|exists:documenti_cartelle,id',
]);
$documento->update(['cartella_id' => $data['cartella_id'] ?? null]);
return response()->json(['success' => true]);
}
}
+12 -2
View File
@@ -1,9 +1,15 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Models\Individuo;
use App\Models\Documento;
use App\Models\Evento;
use App\Models\Gruppo;
use App\Models\Individuo;
use App\Models\EmailMessage;
use App\Models\MailingList;
use App\Models\Notifica;
use Illuminate\Support\Facades\Auth;
@@ -12,11 +18,15 @@ class HomeController extends Controller
public function index()
{
$user = Auth::user();
$stats = [
'individui' => Individuo::count(),
'gruppi' => Gruppo::count(),
'notifiche' => $user->unreadNotificheCount(),
'documenti' => Documento::count(),
'eventi' => Evento::count(),
'email_nuove' => EmailMessage::where('is_read', false)->where('is_trash', false)->count(),
'mailing_liste' => MailingList::count(),
];
$notifiche = $user->notifiche()->take(5)->get();
+108 -6
View File
@@ -3,12 +3,15 @@
namespace App\Http\Controllers;
use App\Models\AppSetting;
use App\Models\Documento;
use App\Models\EmailSetting;
use App\Models\Ruolo;
use App\Models\SenderAccount;
use App\Models\StorageRepository;
use App\Models\TipologiaDocumento;
use App\Models\TipologiaEvento;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class ImpostazioniController extends Controller
@@ -23,8 +26,9 @@ class ImpostazioniController extends Controller
$appSettings = AppSetting::first();
$emailSettings = EmailSetting::first() ?? new EmailSetting();
$senderAccounts = SenderAccount::orderBy('email_address')->get();
$repositories = StorageRepository::orderBy('ordine')->get();
return view('impostazioni.index', compact('tipologie', 'tipologieEventi', 'ruoli', 'appSettings', 'emailSettings', 'senderAccounts'));
return view('impostazioni.index', compact('tipologie', 'tipologieEventi', 'ruoli', 'appSettings', 'emailSettings', 'senderAccounts', 'repositories'));
}
public function saveAppSettings(Request $request)
@@ -38,22 +42,120 @@ class ImpostazioniController extends Controller
'app_version' => 'nullable|string|max:50',
'dashboard_welcome' => 'nullable|string|max:1000',
'show_version' => 'boolean',
'documenti_storage_disk' => 'nullable|string|in:local,public',
'documenti_storage_path' => 'nullable|string|max:255',
]);
$settings = AppSetting::first() ?? new AppSetting();
$oldDisk = $settings->documenti_storage_disk ?? 'local';
$oldPath = $settings->documenti_storage_path ?? 'documenti';
$settings->nome_applicazione = $validated['nome_applicazione'] ?? null;
$settings->nome_organizzazione = $validated['nome_organizzazione'] ?? null;
$settings->footer_text = $validated['footer_text'] ?? null;
$settings->app_version = $validated['app_version'] ?? null;
$settings->dashboard_welcome = $validated['dashboard_welcome'] ?? null;
$settings->nome_applicazione = $validated['nome_applicazione'] ?? $settings->nome_applicazione ?? '';
$settings->nome_organizzazione = $validated['nome_organizzazione'] ?? $settings->nome_organizzazione ?? '';
$settings->footer_text = $validated['footer_text'] ?? $settings->footer_text ?? '';
$settings->app_version = $validated['app_version'] ?? $settings->app_version ?? '';
$settings->dashboard_welcome = $validated['dashboard_welcome'] ?? $settings->dashboard_welcome ?? null;
$settings->show_version = $request->boolean('show_version');
$settings->documenti_storage_disk = $validated['documenti_storage_disk'] ?? $settings->documenti_storage_disk ?? 'local';
$settings->documenti_storage_path = $validated['documenti_storage_path'] ?? $settings->documenti_storage_path ?? 'documenti';
$settings->save();
$newDisk = $settings->documenti_storage_disk;
$newPath = $settings->documenti_storage_path;
$pathChanged = $oldDisk !== $newDisk || $oldPath !== $newPath;
if ($pathChanged) {
try {
Storage::disk($newDisk)->makeDirectory($newPath);
} catch (\Exception $e) {
Log::error('Creazione directory documenti fallita: ' . $e->getMessage());
}
$fileCount = Documento::whereNull('repository_id')->count();
return redirect('/impostazioni#documenti')
->with('success', 'Percorso di archiviazione aggiornato. La nuova directory è stata creata.')
->with('path_changed', true)
->with('old_storage_disk', $oldDisk)
->with('old_storage_path', $oldPath)
->with('new_storage_disk', $newDisk)
->with('new_storage_path', $newPath)
->with('migration_count', $fileCount);
}
return redirect('/impostazioni#applicazione')->with('success', 'Impostazioni applicazione salvate.');
}
public function migrateStoragePath(Request $request)
{
$this->authorizeWrite('settings');
$oldDisk = $request->input('old_storage_disk', 'local');
$oldPath = $request->input('old_storage_path', 'documenti');
$newDisk = $request->input('new_storage_disk', 'local');
$newPath = $request->input('new_storage_path', 'documenti');
$documents = Documento::whereNull('repository_id')->get();
$moved = 0;
$errors = [];
foreach ($documents as $doc) {
$oldFilePath = $doc->file_path;
$relativePath = $oldFilePath;
if (str_starts_with($relativePath, $oldPath . '/') || $relativePath === $oldPath) {
$relativePath = substr($relativePath, strlen($oldPath));
$relativePath = ltrim($relativePath, '/');
}
$newFilePath = $newPath . ($relativePath ? '/' . $relativePath : '');
if ($newFilePath === $oldFilePath && $oldDisk === $newDisk) {
continue;
}
try {
if (!Storage::disk($oldDisk)->exists($oldFilePath)) {
$errors[] = "File non trovato: {$oldFilePath}";
continue;
}
Storage::disk($newDisk)->writeStream(
$newFilePath,
Storage::disk($oldDisk)->readStream($oldFilePath)
);
$doc->update([
'file_path' => $newFilePath,
'storage_disk' => $newDisk,
]);
Storage::disk($oldDisk)->delete($oldFilePath);
$moved++;
} catch (\Exception $e) {
$errors[] = "Errore su {$oldFilePath}: " . $e->getMessage();
Log::error('Migrazione file fallita: ' . $e->getMessage(), [
'doc_id' => $doc->id,
'old' => $oldFilePath,
'new' => $newFilePath,
]);
}
}
$message = "Migrazione completata: {$moved} file spostati.";
if (count($errors) > 0) {
$message .= ' ' . count($errors) . ' errori. Controlla i log per i dettagli.';
}
return redirect('/impostazioni#documenti')
->with('success', $message)
->with('migration_errors', count($errors) > 0 ? $errors : null);
}
public function tipologieStore(Request $request)
{
$this->authorizeWrite('settings');
@@ -0,0 +1,197 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Models\StorageRepository;
use App\Services\StorageRepositoryService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\StreamedResponse;
class StorageRepositoryController extends Controller
{
public function __construct(
private readonly StorageRepositoryService $repoService
) {}
public function index(Request $request)
{
$this->authorizeWrite('settings');
$repositories = StorageRepository::orderBy('ordine')->get();
if ($request->ajax()) {
return response()->json($repositories);
}
return view('storage-repositories.index', compact('repositories'));
}
public function store(Request $request): RedirectResponse
{
$this->authorizeWrite('settings');
$validated = $request->validate([
'nome' => 'required|string|max:255',
'tipo' => 'required|string|in:webdav,google_drive',
'config' => 'required|array',
'is_active' => 'boolean',
'ordine' => 'nullable|integer|min:0',
]);
$maxOrdine = StorageRepository::max('ordine') ?? 0;
$validated['ordine'] ??= $maxOrdine + 1;
$validated['config'] = $this->repoService->encryptSensitiveConfig($validated['config'], $validated['tipo']);
StorageRepository::create($validated);
return redirect('/impostazioni#repository')->with('success', 'Repository creato.');
}
public function update(Request $request, StorageRepository $storageRepository): RedirectResponse
{
$this->authorizeWrite('settings');
$validated = $request->validate([
'nome' => 'required|string|max:255',
'tipo' => 'required|string|in:webdav,google_drive',
'config' => 'required|array',
'is_active' => 'boolean',
'ordine' => 'nullable|integer|min:0',
]);
$validated['config'] = $this->repoService->encryptSensitiveConfig($validated['config'], $validated['tipo']);
$storageRepository->update($validated);
return redirect('/impostazioni#repository')->with('success', 'Repository aggiornato.');
}
public function destroy(StorageRepository $storageRepository): JsonResponse|RedirectResponse
{
$this->authorizeDelete('settings');
$docCount = $storageRepository->documenti()->count();
if ($docCount > 0) {
$message = "Impossibile eliminare: {$docCount} documenti associati a questo repository.";
if (request()->expectsJson()) {
return response()->json(['success' => false, 'message' => $message], 409);
}
return redirect('/impostazioni#repository')->with('error', $message);
}
$storageRepository->delete();
if (request()->expectsJson()) {
return response()->json(['success' => true]);
}
return redirect('/impostazioni#repository')->with('success', 'Repository eliminato.');
}
public function test(StorageRepository $storageRepository): JsonResponse
{
$this->authorizeWrite('settings');
$result = $this->repoService->testConnection($storageRepository);
return response()->json($result);
}
public function browse(Request $request, StorageRepository $storageRepository): JsonResponse
{
$this->authorizeRead('documenti');
$path = $request->get('path', '/');
$contents = $this->repoService->listContents($storageRepository, $path);
return response()->json([
'success' => true,
'contents' => $contents,
'path' => $path,
'repository' => [
'id' => $storageRepository->id,
'nome' => $storageRepository->nome,
'tipo' => $storageRepository->tipo,
],
]);
}
public function reorder(Request $request): JsonResponse
{
$this->authorizeWrite('settings');
$order = $request->input('order', []);
foreach ($order as $index => $id) {
StorageRepository::where('id', $id)->update(['ordine' => $index]);
}
return response()->json(['success' => true]);
}
public function downloadRemote(StorageRepository $storageRepository, Request $request): StreamedResponse
{
$this->authorizeRead('documenti');
$path = $request->get('path');
if (!$path) {
abort(400, 'Path required');
}
$filesystem = $this->repoService->buildFilesystem($storageRepository);
if (!$filesystem || !$filesystem->fileExists($path)) {
abort(404);
}
$stream = $filesystem->readStream($path);
if (!$stream) {
abort(404);
}
$mimeType = $filesystem->mimeType($path) ?? 'application/octet-stream';
$basename = basename($path);
return response()->stream(function () use ($stream) {
fpassthru($stream);
fclose($stream);
}, 200, [
'Content-Type' => $mimeType,
'Content-Disposition' => 'attachment; filename="' . $basename . '"',
]);
}
public function previewRemote(StorageRepository $storageRepository, Request $request): StreamedResponse
{
$this->authorizeRead('documenti');
$path = $request->get('path');
if (!$path) {
abort(400, 'Path required');
}
$filesystem = $this->repoService->buildFilesystem($storageRepository);
if (!$filesystem || !$filesystem->fileExists($path)) {
abort(404);
}
$stream = $filesystem->readStream($path);
if (!$stream) {
abort(404);
}
$mimeType = $filesystem->mimeType($path) ?? 'application/octet-stream';
$basename = basename($path);
return response()->stream(function () use ($stream) {
fpassthru($stream);
fclose($stream);
}, 200, [
'Content-Type' => $mimeType,
'Content-Disposition' => 'inline; filename="' . $basename . '"',
]);
}
}