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->config ); $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', '/'); try { if (!$storageRepository->is_active) { return response()->json([ 'success' => false, 'message' => 'Repository non attivo', 'contents' => [] ], 400); } $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, ], ]); } catch (\Exception $e) { Log::error("StorageRepository browse failed for repo #{$storageRepository->id}: " . $e->getMessage()); return response()->json([ 'success' => false, 'message' => 'Errore: ' . $e->getMessage(), 'contents' => [] ], 500); } } 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 . '"', ]); } public function importToLocal(Request $request, StorageRepository $storageRepository): JsonResponse { $this->authorizeWrite('documenti'); $validated = $request->validate([ 'path' => 'required|string', 'basename' => 'required|string|max:255', 'cartella_id' => 'nullable|integer|exists:documenti_cartelle,id', 'tipologia' => 'nullable|string|in:avatar,galleria,documento,statuto,altro', 'visibilita' => 'nullable|string|in:pubblico,individuo,gruppo,evento,mailing', 'visibilita_target_id' => 'nullable|integer', ]); $path = $validated['path']; $basename = $validated['basename']; $filesystem = $this->repoService->buildFilesystem($storageRepository); if (!$filesystem) { return response()->json(['success' => false, 'message' => 'Impossibile connettersi al repository remoto.'], 500); } $normalizedPath = $this->repoService->normalizePath($path); if ($storageRepository->tipo === 'google_drive') { try { $fileData = $this->repoService->readGoogleDriveFile($storageRepository, $normalizedPath, $basename); } catch (\Exception $e) { Log::error("StorageRepository importToLocal: readGoogleDriveFile failed for repo #{$storageRepository->id} path '{$path}': " . $e->getMessage()); return response()->json(['success' => false, 'message' => 'Impossibile leggere il file da Google Drive: ' . $e->getMessage()], 500); } } else { try { $fileData = []; $fileData['stream'] = $filesystem->readStream($normalizedPath); $fileData['mimeType'] = null; try { $fileData['mimeType'] = $filesystem->mimeType($normalizedPath); } catch (\Exception) {} $fileData['fileSize'] = null; try { $fileData['fileSize'] = $filesystem->fileSize($normalizedPath); } catch (\Exception) {} $fileData['basename'] = $basename; } catch (\Exception $e) { Log::error("StorageRepository importToLocal: readStream failed for repo #{$storageRepository->id} path '{$path}': " . $e->getMessage()); return response()->json(['success' => false, 'message' => 'File non trovato nel repository remoto o impossibile da leggere.'], 404); } } $stream = $fileData['stream']; if (!$stream) { return response()->json(['success' => false, 'message' => 'Impossibile leggere il file remoto.'], 500); } $basename = $fileData['basename']; $mimeType = $fileData['mimeType'] ?? null; $fileSize = $fileData['fileSize'] ?? null; $disk = \App\Models\AppSetting::getDocumentiStorageDisk(); $storagePath = \App\Models\AppSetting::getDocumentiStoragePath(); $extension = strtolower(pathinfo($basename, PATHINFO_EXTENSION)); $nomeSenzaEstensione = pathinfo($basename, PATHINFO_FILENAME); $uniqueName = preg_replace('/[^a-zA-Z0-9_\-\x80-\x{FF}]/u', '_', $nomeSenzaEstensione) . '_' . time() . ($extension ? '.' . $extension : ''); $filePath = $storagePath . '/' . date('Y/m/d') . '/' . $uniqueName; $stored = \Illuminate\Support\Facades\Storage::disk($disk)->writeStream($filePath, $stream); fclose($stream); if (!$stored) { return response()->json(['success' => false, 'message' => 'Errore durante il salvataggio locale del file.'], 500); } $visibilita = $validated['visibilita'] ?? 'pubblico'; $targetType = null; $targetId = null; if ($visibilita === 'individuo' && !empty($validated['visibilita_target_id'])) { $targetType = \App\Models\Individuo::class; $targetId = (int) $validated['visibilita_target_id']; } elseif ($visibilita === 'gruppo' && !empty($validated['visibilita_target_id'])) { $targetType = \App\Models\Gruppo::class; $targetId = (int) $validated['visibilita_target_id']; } elseif ($visibilita === 'evento' && !empty($validated['visibilita_target_id'])) { $targetType = \App\Models\Evento::class; $targetId = (int) $validated['visibilita_target_id']; } elseif ($visibilita === 'mailing' && !empty($validated['visibilita_target_id'])) { $targetType = \App\Models\MailingList::class; $targetId = (int) $validated['visibilita_target_id']; } $documento = \App\Models\Documento::create([ 'nome_file' => $nomeSenzaEstensione, 'file_path' => $filePath, 'storage_disk' => $disk, 'tipo' => 'upload', 'tipologia' => $validated['tipologia'] ?? 'documento', 'visibilita' => $visibilita, 'visibilita_target_id' => $targetId, 'visibilita_target_type' => $targetType, 'mime_type' => $mimeType, 'dimensione' => $fileSize, 'user_id' => auth()->id(), 'cartella_id' => $validated['cartella_id'] ?? null, 'repository_id' => null, ]); return response()->json([ 'success' => true, 'message' => 'File importato come documento locale: ' . $documento->nome_file, 'documento_id' => $documento->id, ]); } public function oauthRedirect(Request $request): RedirectResponse { $this->authorizeWrite('settings'); $clientId = $request->get('client_id'); $clientSecret = $request->get('client_secret'); $repoId = (int) $request->get('repo_id', 0); if (empty($clientId) || empty($clientSecret)) { return redirect('/impostazioni#repository')->with('error', 'Inserisci Client ID e Client Secret prima di autorizzare.'); } session(['google_drive_oauth' => [ 'client_id' => $clientId, 'client_secret' => $clientSecret, 'repo_id' => $repoId, 'nome' => $request->get('nome', ''), 'is_active' => $request->get('is_active', '1'), 'root_folder_id' => $request->get('root_folder_id', 'root'), ]]); $client = new \Google_Client(); $client->setClientId($clientId); $client->setClientSecret($clientSecret); $client->setRedirectUri(url('/auth/google-drive/callback')); $client->addScope(\Google_Service_Drive::DRIVE); $client->setAccessType('offline'); $client->setPrompt('consent'); $authUrl = $client->createAuthUrl(); return redirect()->away($authUrl); } public function oauthCallback(Request $request): RedirectResponse { $this->authorizeWrite('settings'); if ($request->get('error')) { return redirect('/impostazioni#repository')->with('error', 'Autorizzazione Google Drive annullata o fallita.'); } $code = $request->get('code'); if (!$code) { return redirect('/impostazioni#repository')->with('error', 'Codice di autorizzazione mancante.'); } $oauthData = session('google_drive_oauth'); if (!$oauthData) { return redirect('/impostazioni#repository')->with('error', 'Sessione scaduta. Riprova l\'autorizzazione.'); } try { $client = new \Google_Client(); $client->setClientId($oauthData['client_id']); $client->setClientSecret($oauthData['client_secret']); $client->setRedirectUri(url('/auth/google-drive/callback')); $client->addScope(\Google_Service_Drive::DRIVE); $token = $client->fetchAccessTokenWithAuthCode($code); if (isset($token['error'])) { throw new \Exception($token['error_description'] ?? $token['error']); } $refreshToken = $client->getRefreshToken(); if (!$refreshToken) { throw new \Exception('Refresh token non ricevuto. Assicurati che l\'app OAuth sia configurata con access_type=offline e prompt=consent nella Google Cloud Console.'); } $repoId = $oauthData['repo_id']; session()->forget('google_drive_oauth'); if ($repoId > 0) { $repo = StorageRepository::find($repoId); if ($repo) { $config = $repo->config; $config['client_id'] = $oauthData['client_id']; $config['client_secret'] = $oauthData['client_secret']; $config['refresh_token'] = $refreshToken; $config = $this->repoService->encryptSensitiveConfig($config, 'google_drive'); $repo->update(['config' => $config]); return redirect('/impostazioni#repository')->with('success', 'Google Drive autorizzato con successo! Refresh token salvato.'); } return redirect('/impostazioni#repository')->with('error', 'Repository non trovato (ID: ' . $repoId . ').'); } session()->flash('google_drive_new_token', [ 'refresh_token' => $refreshToken, 'client_id' => $oauthData['client_id'], 'client_secret' => $oauthData['client_secret'], 'nome' => $oauthData['nome'] ?? '', 'is_active' => $oauthData['is_active'] ?? '1', 'root_folder_id' => $oauthData['root_folder_id'] ?? 'root', ]); return redirect('/impostazioni#repository')->with('success', 'Autorizzazione Google Drive completata! Ora completa la creazione del repository e salva.'); } catch (\Exception $e) { return redirect('/impostazioni#repository')->with('error', 'Errore autorizzazione Google Drive: ' . $e->getMessage()); } } }