Autor: Dante Testa
Data: 24/11/2025
Versão: 1.0.0
- Visão Geral
- Pré-requisitos
- Configuração no Google Cloud Console
- Instalação de Dependências
- Configuração do Laravel
- Autenticação OAuth 2.0
- GoogleDriveService
- Estrutura de Pastas
- Exemplos de Uso
- Segurança
- Troubleshooting
Esta integração permite que aplicações Laravel gerenciem arquivos no Google Drive de forma profissional, incluindo:
- ✅ Upload de arquivos (simples e resumable para arquivos grandes)
- ✅ Download e streaming de arquivos
- ✅ Criação e gerenciamento de estrutura de pastas
- ✅ Autenticação OAuth 2.0 com refresh token
- ✅ Controle de permissões e acesso privado
- ✅ Integração com Laravel Storage (Flysystem)
- Laravel 11 (PHP 8.2+)
- Google API Client (via Composer)
- Flysystem Google Drive Adapter (
masbug/flysystem-google-drive-ext) - OAuth 2.0 para autenticação
- Acesso ao Google Cloud Console
- Projeto criado no Google Cloud
- Faturamento habilitado (não será cobrado para uso básico)
PHP >= 8.2
Laravel >= 11.0
Composer- Acesse Google Cloud Console
- Clique em "Selecionar projeto" → "Novo Projeto"
- Nome do projeto:
DanteFlix(ou nome da sua aplicação) - Clique em "Criar"
- No menu lateral, vá em "APIs e Serviços" → "Biblioteca"
- Busque por "Google Drive API"
- Clique em "Ativar"
- Vá em "APIs e Serviços" → "Tela de consentimento OAuth"
- Escolha "Externo" (para uso público) ou "Interno" (apenas G Suite)
- Preencha os campos obrigatórios:
- Nome do app: DanteFlix
- E-mail de suporte: seu-email@dominio.com
- Domínio autorizado: wprevolution.com.br
- E-mail do desenvolvedor: seu-email@dominio.com
- Clique em "Salvar e continuar"
- Na seção "Escopos", clique em "Adicionar ou remover escopos"
- Adicione o escopo:
https://www.googleapis.com/auth/drive - Clique em "Atualizar" e "Salvar e continuar"
- Vá em "APIs e Serviços" → "Credenciais"
- Clique em "Criar credenciais" → "ID do cliente OAuth"
- Tipo de aplicativo: "Aplicativo da Web"
- Nome:
DanteFlix Web Client - URIs de redirecionamento autorizados:
https://wprevolution.com.br/admin/settings/google-drive/callback http://localhost:8000/admin/settings/google-drive/callback - Clique em "Criar"
- IMPORTANTE: Copie e salve:
- Client ID (ex:
689791833607-xxxxx.apps.googleusercontent.com) - Client Secret (ex:
GOCSPX-xxxxxxxxxxxxx)
- Client ID (ex:
composer require masbug/flysystem-google-drive-ext:^2.3Esta biblioteca já inclui:
google/apiclient(Google API Client)league/flysystem(Flysystem 3)
composer show | grep google
composer show | grep flysystemAdicione as seguintes variáveis no arquivo .env:
# Google Drive API
GOOGLE_DRIVE_CLIENT_ID=689791833607-xxxxx.apps.googleusercontent.com
GOOGLE_DRIVE_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxxx
GOOGLE_DRIVE_REDIRECT_URI=https://wprevolution.com.br/admin/settings/google-drive/callback
GOOGLE_DRIVE_SCOPES="https://www.googleapis.com/auth/drive"
GOOGLE_DRIVE_REFRESH_TOKEN=
GOOGLE_DRIVE_FOLDER_ROOT_ID=Observações:
GOOGLE_DRIVE_REFRESH_TOKENserá preenchido após autenticação OAuthGOOGLE_DRIVE_FOLDER_ROOT_IDé o ID da pasta raiz no Drive (opcional)
Adicione o disco google no array disks:
'disks' => [
// ... outros discos
'google' => [
'driver' => 'google',
'clientId' => env('GOOGLE_DRIVE_CLIENT_ID'),
'clientSecret' => env('GOOGLE_DRIVE_CLIENT_SECRET'),
'refreshToken' => env('GOOGLE_DRIVE_REFRESH_TOKEN'),
'folder' => 'root', // ou ID de pasta específica
'teamDriveId' => env('GOOGLE_DRIVE_TEAM_DRIVE_ID', null),
],
],Crie o arquivo app/Providers/GoogleDriveServiceProvider.php:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Storage;
use League\Flysystem\Filesystem;
use Masbug\Flysystem\GoogleDriveAdapter;
use Google\Client as GoogleClient;
class GoogleDriveServiceProvider extends ServiceProvider
{
public function boot(): void
{
Storage::extend('google', function ($app, $config) {
$client = new GoogleClient();
$client->setClientId($config['clientId']);
$client->setClientSecret($config['clientSecret']);
// Usar refresh token para obter access token
$client->fetchAccessTokenWithRefreshToken($config['refreshToken']);
$service = new \Google_Service_Drive($client);
// Usar o folder ID como raiz
$folderId = $config['folder'] ?? 'root';
$adapter = new GoogleDriveAdapter($service, $folderId);
return new Filesystem($adapter);
});
}
}Adicione no arquivo bootstrap/providers.php:
return [
App\Providers\AppServiceProvider::class,
App\Providers\GoogleDriveServiceProvider::class,
];1. Usuário clica em "Conectar Google Drive"
2. Redireciona para Google OAuth
3. Usuário autoriza o app
4. Google redireciona de volta com código
5. App troca código por refresh token
6. Refresh token é salvo no .env
Veja implementação completa em: app/Http/Controllers/Admin/GoogleDriveSettingsController.php
Métodos principais:
connect()- Inicia autenticação OAuthcallback()- Recebe código e obtém refresh tokendisconnect()- Remove autenticação
Classe principal para gerenciar arquivos: app/Services/GoogleDriveService.php
// Upload simples
$result = $googleDrive->uploadToFolder($file, $folderId, 'nome-arquivo.pdf');
// Upload com estrutura organizada
$result = $googleDrive->uploadToOrganizedFolder($file, 'Nome Download', 'categoria-slug');// Criar ou obter pasta
$folderId = $googleDrive->createOrGetFolder('Nome Pasta', $parentId);
// Criar estrutura de curso
$result = $googleDrive->createCourseStructure('Nome Curso', 'categoria-slug');
// Criar pasta de módulo
$result = $googleDrive->createModuleFolder('Módulo 1', $courseFolderId);
// Criar pasta de aula
$result = $googleDrive->createLessonFolder('Aula 1', $moduleFolderId);// Download de arquivo
$result = $googleDrive->downloadFile($fileId);
// Streaming (para vídeos)
$result = $googleDrive->streamFile($fileId);// Deletar arquivo
$result = $googleDrive->deleteFile($fileId);
// Deletar pasta de curso
$result = $googleDrive->deleteCourseFolder($courseFolderId);
// Deletar pasta de download
$result = $googleDrive->deleteDownloadFolder($downloadName, $categorySlug);// Renomear pasta de curso
$result = $googleDrive->renameCourseFolder($folderId, 'Novo Nome');
// Renomear pasta de módulo
$result = $googleDrive->renameModuleFolder($folderId, 'Novo Nome');
// Renomear pasta de aula
$result = $googleDrive->renameLessonFolder($folderId, 'Novo Nome');Google Drive (Raiz)
└── DanteFlix/
├── Cursos/
│ ├── php-avancado/
│ │ ├── anexos/
│ │ │ ├── capa.jpg
│ │ │ └── apresentacao.mp4
│ │ ├── Modulo 1 - Introducao/
│ │ │ ├── anexos/
│ │ │ └── Aula 1 - Bem-vindo/
│ │ │ ├── video.mp4
│ │ │ └── slides.pdf
│ │ └── Modulo 2 - POO/
│ │ └── Aula 1 - Classes/
│ │ └── video.mp4
│ └── laravel-11/
│ └── ...
└── Downloads/
├── ebooks/
│ └── PHP Moderno/
│ └── php-moderno.pdf
└── ferramentas/
└── VSCode Setup/
└── vscode-setup.zip
use Illuminate\Support\Facades\Storage;
// Upload via Flysystem
$path = $request->file('document')->store('/', 'google');
// Obter URL
$url = Storage::disk('google')->url($path);use App\Services\GoogleDriveService;
public function store(Request $request, GoogleDriveService $googleDrive)
{
$file = $request->file('document');
// Criar pasta se não existir
$folderId = $googleDrive->createOrGetFolder('Documentos');
// Upload
$result = $googleDrive->uploadToFolder($file, $folderId);
if ($result['success']) {
Document::create([
'title' => $request->title,
'google_drive_file_id' => $result['file_id'],
'file_name' => $result['file_name'],
]);
return redirect()->back()->with('success', 'Upload realizado!');
}
return redirect()->back()->with('error', $result['error']);
}public function store(Request $request, GoogleDriveService $googleDrive)
{
// Criar estrutura no Google Drive
$result = $googleDrive->createCourseStructure(
$request->title,
$request->category->slug ?? null
);
if ($result['success']) {
$course = Course::create([
'title' => $request->title,
'google_drive_folder_id' => $result['course_folder_id'],
]);
// Upload de capa
if ($request->hasFile('cover_image')) {
$coverResult = $googleDrive->uploadToFolder(
$request->file('cover_image'),
$result['attachments_folder_id'],
'capa.jpg'
);
$course->update([
'cover_image_drive_id' => $coverResult['file_id'],
]);
}
return redirect()->route('admin.courses.index')
->with('success', 'Curso criado!');
}
}-
Nunca expor credenciais
- Client ID e Secret devem estar no
.env - Adicionar
.envno.gitignore
- Client ID e Secret devem estar no
-
Refresh Token
- Armazenar com segurança
- Nunca compartilhar publicamente
- Renovar periodicamente
-
Permissões de Arquivos
- Por padrão, arquivos são privados
- Usar proxy autenticado para streaming
- Validar acesso do usuário antes de servir arquivo
-
Validação de Upload
$request->validate([ 'file' => 'required|file|max:102400|mimes:pdf,doc,docx', ]);
-
Rate Limiting
- Implementar rate limit nas rotas de upload
- Limitar tamanho de arquivos
Solução: Execute o fluxo OAuth novamente para obter novo refresh token.
// Acessar rota de conexão
https://seu-dominio.com/admin/settings/google-drive/connectSolução: Verificar se Client ID e Secret estão corretos no .env
php artisan config:clear
php artisan cache:clearSolução: Verificar se o ID da pasta está correto
// Listar pastas disponíveis
$folders = $googleDrive->listFolders();Solução: Refresh token expirou, refazer autenticação OAuth
Solução: Aumentar limites no php.ini
upload_max_filesize = 512M
post_max_size = 512M
max_execution_time = 300
memory_limit = 512MSolução: Verificar escopos OAuth
// Adicionar escopo completo
$client->addScope(GoogleDrive::DRIVE);- Criar projeto no Google Cloud Console
- Habilitar Google Drive API
- Configurar tela de consentimento OAuth
- Criar credenciais OAuth 2.0
- Instalar dependências via Composer
- Configurar variáveis de ambiente (.env)
- Criar GoogleDriveServiceProvider
- Registrar Service Provider
- Criar GoogleDriveService
- Implementar rotas de autenticação
- Implementar controller de autenticação
- Testar fluxo OAuth
- Implementar upload de arquivos
- Implementar download/streaming
- Implementar gerenciamento de pastas
- Adicionar validações de segurança
- Testar em produção
Autor: Dante Testa
Data: 24/11/2025
- Controller Completo de Upload
- Sistema de Cursos
- Sistema de Downloads
- Proxy para Streaming
- Migrations
- Rotas Completas
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Course;
use App\Models\CourseCategory;
use App\Services\GoogleDriveService;
use Illuminate\Http\Request;
class CourseController extends Controller
{
/**
* Criar novo curso com estrutura no Google Drive
*/
public function store(Request $request, GoogleDriveService $googleDrive)
{
$validated = $request->validate([
'category_id' => 'nullable|exists:course_categories,id',
'title' => 'required|string|max:255',
'description' => 'required|string',
'cover_image' => 'nullable|image|max:10240', // 10MB
'presentation_video' => 'nullable|file|mimes:mp4,mov|max:512000', // 500MB
'is_free' => 'boolean',
]);
try {
// Buscar categoria se informada
$category = $validated['category_id']
? CourseCategory::find($validated['category_id'])
: null;
// Criar estrutura no Google Drive
$driveResult = $googleDrive->createCourseStructure(
$validated['title'],
$category?->slug
);
if (!$driveResult['success']) {
return redirect()->back()
->with('error', 'Erro ao criar estrutura: ' . $driveResult['error'])
->withInput();
}
// Criar curso no banco
$course = Course::create([
'category_id' => $validated['category_id'],
'title' => $validated['title'],
'slug' => \Str::slug($validated['title']),
'description' => $validated['description'],
'is_free' => $validated['is_free'] ?? false,
'google_drive_folder_id' => $driveResult['course_folder_id'],
'attachments_folder_id' => $driveResult['attachments_folder_id'],
]);
// Upload de capa
if ($request->hasFile('cover_image')) {
$coverResult = $googleDrive->uploadToFolder(
$request->file('cover_image'),
$driveResult['attachments_folder_id'],
'capa.jpg'
);
if ($coverResult['success']) {
$course->update([
'cover_image_drive_id' => $coverResult['file_id'],
'cover_image_url' => $coverResult['file_url'],
]);
}
}
// Upload de vídeo de apresentação
if ($request->hasFile('presentation_video')) {
$videoResult = $googleDrive->uploadToFolder(
$request->file('presentation_video'),
$driveResult['attachments_folder_id'],
'apresentacao.mp4'
);
if ($videoResult['success']) {
$course->update([
'presentation_video_drive_id' => $videoResult['file_id'],
'presentation_video_url' => $videoResult['file_url'],
]);
}
}
return redirect()->route('admin.courses.index')
->with('success', 'Curso criado com sucesso!');
} catch (\Exception $e) {
\Log::error('Erro ao criar curso', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return redirect()->back()
->with('error', 'Erro ao criar curso: ' . $e->getMessage())
->withInput();
}
}
/**
* Atualizar curso
*/
public function update(Request $request, Course $course, GoogleDriveService $googleDrive)
{
$validated = $request->validate([
'category_id' => 'nullable|exists:course_categories,id',
'title' => 'required|string|max:255',
'description' => 'required|string',
'cover_image' => 'nullable|image|max:10240',
'presentation_video' => 'nullable|file|mimes:mp4,mov|max:512000',
'is_free' => 'boolean',
]);
try {
// Se mudou de categoria, mover pasta no Google Drive
if ($validated['category_id'] != $course->category_id) {
$newCategory = $validated['category_id']
? CourseCategory::find($validated['category_id'])
: null;
$googleDrive->moveCourseFolderToCategory(
$course->google_drive_folder_id,
$newCategory
);
}
// Se mudou o título, renomear pasta no Google Drive
if ($validated['title'] != $course->title) {
$googleDrive->renameCourseFolder(
$course->google_drive_folder_id,
$validated['title']
);
}
// Atualizar curso
$course->update([
'category_id' => $validated['category_id'],
'title' => $validated['title'],
'slug' => \Str::slug($validated['title']),
'description' => $validated['description'],
'is_free' => $validated['is_free'] ?? false,
]);
// Upload de nova capa
if ($request->hasFile('cover_image')) {
// Deletar capa antiga
if ($course->cover_image_drive_id) {
$googleDrive->deleteFile($course->cover_image_drive_id);
}
// Upload nova capa
$coverResult = $googleDrive->uploadToFolder(
$request->file('cover_image'),
$course->attachments_folder_id,
'capa.jpg'
);
if ($coverResult['success']) {
$course->update([
'cover_image_drive_id' => $coverResult['file_id'],
'cover_image_url' => $coverResult['file_url'],
]);
}
}
return redirect()->route('admin.courses.index')
->with('success', 'Curso atualizado com sucesso!');
} catch (\Exception $e) {
\Log::error('Erro ao atualizar curso', [
'course_id' => $course->id,
'error' => $e->getMessage()
]);
return redirect()->back()
->with('error', 'Erro ao atualizar curso: ' . $e->getMessage())
->withInput();
}
}
/**
* Deletar curso
*/
public function destroy(Course $course, GoogleDriveService $googleDrive)
{
try {
// Deletar pasta do Google Drive
if ($course->google_drive_folder_id) {
$googleDrive->deleteCourseFolder($course->google_drive_folder_id);
}
// Deletar curso do banco
$course->delete();
return redirect()->route('admin.courses.index')
->with('success', 'Curso deletado com sucesso!');
} catch (\Exception $e) {
\Log::error('Erro ao deletar curso', [
'course_id' => $course->id,
'error' => $e->getMessage()
]);
return redirect()->back()
->with('error', 'Erro ao deletar curso: ' . $e->getMessage());
}
}
}<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Module;
use App\Models\Course;
use App\Services\GoogleDriveService;
use Illuminate\Http\Request;
class ModuleController extends Controller
{
public function store(Request $request, GoogleDriveService $googleDrive)
{
$validated = $request->validate([
'course_id' => 'required|exists:courses,id',
'title' => 'required|string|max:255',
'order' => 'required|integer|min:1',
'is_free' => 'boolean',
]);
try {
$course = Course::findOrFail($validated['course_id']);
// Nome da pasta: "Módulo X - Título"
$folderName = "Modulo {$validated['order']} - {$validated['title']}";
// Criar pasta no Google Drive
$driveResult = $googleDrive->createModuleFolder(
$folderName,
$course->google_drive_folder_id
);
if (!$driveResult['success']) {
return redirect()->back()
->with('error', 'Erro ao criar pasta: ' . $driveResult['error'])
->withInput();
}
// Criar módulo no banco
$module = Module::create([
'course_id' => $validated['course_id'],
'title' => $validated['title'],
'order' => $validated['order'],
'is_free' => $validated['is_free'] ?? false,
'google_drive_folder_id' => $driveResult['module_folder_id'],
'attachments_folder_id' => $driveResult['attachments_folder_id'],
]);
return redirect()->route('admin.modules.index')
->with('success', 'Módulo criado com sucesso!');
} catch (\Exception $e) {
\Log::error('Erro ao criar módulo', [
'error' => $e->getMessage()
]);
return redirect()->back()
->with('error', 'Erro ao criar módulo: ' . $e->getMessage())
->withInput();
}
}
}<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Lesson;
use App\Models\Module;
use App\Services\GoogleDriveService;
use Illuminate\Http\Request;
class LessonController extends Controller
{
public function store(Request $request, GoogleDriveService $googleDrive)
{
$validated = $request->validate([
'module_id' => 'required|exists:modules,id',
'title' => 'required|string|max:255',
'order' => 'required|integer|min:1',
'video_file' => 'required|file|mimes:mp4,mov|max:512000',
'is_free_preview' => 'boolean',
'attachments.*' => 'nullable|file|max:51200', // 50MB cada
]);
try {
$module = Module::findOrFail($validated['module_id']);
// Nome da pasta: "Aula X - Título"
$folderName = "Aula {$validated['order']} - {$validated['title']}";
// Criar pasta da aula
$driveResult = $googleDrive->createLessonFolder(
$folderName,
$module->google_drive_folder_id
);
if (!$driveResult['success']) {
return redirect()->back()
->with('error', 'Erro ao criar pasta: ' . $driveResult['error'])
->withInput();
}
// Upload do vídeo
$videoResult = $googleDrive->uploadToFolder(
$request->file('video_file'),
$driveResult['lesson_folder_id'],
'video.mp4'
);
if (!$videoResult['success']) {
return redirect()->back()
->with('error', 'Erro ao fazer upload do vídeo: ' . $videoResult['error'])
->withInput();
}
// Criar aula no banco
$lesson = Lesson::create([
'module_id' => $validated['module_id'],
'title' => $validated['title'],
'order' => $validated['order'],
'is_free_preview' => $validated['is_free_preview'] ?? false,
'google_drive_folder_id' => $driveResult['lesson_folder_id'],
'video_drive_id' => $videoResult['file_id'],
'video_url' => $videoResult['file_url'],
]);
// Upload de anexos
if ($request->hasFile('attachments')) {
foreach ($request->file('attachments') as $attachment) {
$attachmentResult = $googleDrive->uploadToFolder(
$attachment,
$driveResult['lesson_folder_id'],
$attachment->getClientOriginalName()
);
if ($attachmentResult['success']) {
$lesson->attachments()->create([
'title' => $attachment->getClientOriginalName(),
'file_drive_id' => $attachmentResult['file_id'],
'file_url' => $attachmentResult['file_url'],
'file_type' => $attachment->getClientMimeType(),
'file_size' => $attachment->getSize(),
]);
}
}
}
return redirect()->route('admin.lessons.index')
->with('success', 'Aula criada com sucesso!');
} catch (\Exception $e) {
\Log::error('Erro ao criar aula', [
'error' => $e->getMessage()
]);
return redirect()->back()
->with('error', 'Erro ao criar aula: ' . $e->getMessage())
->withInput();
}
}
}<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Download;
use App\Models\DownloadCategory;
use App\Services\GoogleDriveService;
use Illuminate\Http\Request;
class DownloadController extends Controller
{
public function store(Request $request, GoogleDriveService $googleDrive)
{
$validated = $request->validate([
'category_id' => 'nullable|exists:download_categories,id',
'title' => 'required|string|max:255',
'description' => 'nullable|string',
'file' => 'required|file|max:512000', // 500MB
]);
try {
$category = $validated['category_id']
? DownloadCategory::find($validated['category_id'])
: null;
// Upload organizado: downloads/categoria/nome-download/arquivo.ext
$uploadResult = $googleDrive->uploadToOrganizedFolder(
$request->file('file'),
$validated['title'],
$category?->slug
);
if (!$uploadResult['success']) {
return redirect()->back()
->with('error', 'Erro ao fazer upload: ' . $uploadResult['error'])
->withInput();
}
// Criar download no banco
$download = Download::create([
'category_id' => $validated['category_id'],
'title' => $validated['title'],
'slug' => \Str::slug($validated['title']),
'description' => $validated['description'],
'file_drive_id' => $uploadResult['file_id'],
'file_name' => $uploadResult['file_name'],
'file_url' => $uploadResult['web_view_link'],
'download_url' => $uploadResult['download_link'],
'folder_path' => $uploadResult['folder_path'],
]);
return redirect()->route('admin.downloads.index')
->with('success', 'Download criado com sucesso!');
} catch (\Exception $e) {
\Log::error('Erro ao criar download', [
'error' => $e->getMessage()
]);
return redirect()->back()
->with('error', 'Erro ao criar download: ' . $e->getMessage())
->withInput();
}
}
public function destroy(Download $download, GoogleDriveService $googleDrive)
{
try {
// Deletar pasta do Google Drive
$category = $download->category;
$googleDrive->deleteDownloadFolder(
$download->title,
$category?->slug
);
// Deletar do banco
$download->delete();
return redirect()->route('admin.downloads.index')
->with('success', 'Download deletado com sucesso!');
} catch (\Exception $e) {
\Log::error('Erro ao deletar download', [
'download_id' => $download->id,
'error' => $e->getMessage()
]);
return redirect()->back()
->with('error', 'Erro ao deletar download: ' . $e->getMessage());
}
}
}<?php
namespace App\Http\Controllers;
use App\Services\GoogleDriveService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Response;
class GoogleDriveProxyController extends Controller
{
/**
* Fazer streaming de vídeo do Google Drive
* Rota: /drive/stream/{fileId}
*/
public function stream($fileId, GoogleDriveService $googleDrive)
{
// Verificar autenticação
if (!auth()->check()) {
abort(403, 'Acesso negado');
}
// Fazer streaming via API
$result = $googleDrive->streamFile($fileId);
if (!$result['success']) {
abort(404, 'Arquivo não encontrado');
}
return response($result['content'])
->header('Content-Type', $result['mime_type'])
->header('Content-Length', $result['size'])
->header('Accept-Ranges', 'bytes')
->header('Cache-Control', 'no-cache, no-store, must-revalidate');
}
/**
* Fazer download de arquivo do Google Drive
* Rota: /drive/download/{fileId}
*/
public function download($fileId, GoogleDriveService $googleDrive)
{
// Verificar autenticação
if (!auth()->check()) {
abort(403, 'Acesso negado');
}
// Fazer download via API
$result = $googleDrive->downloadFile($fileId);
if (!$result['success']) {
abort(404, 'Arquivo não encontrado');
}
return response($result['content'])
->header('Content-Type', $result['mimeType'])
->header('Content-Disposition', 'attachment; filename="' . $result['filename'] . '"')
->header('Content-Length', $result['size']);
}
}Schema::create('courses', function (Blueprint $table) {
$table->id();
$table->foreignId('category_id')->nullable()->constrained('course_categories')->nullOnDelete();
$table->string('title');
$table->string('slug')->unique();
$table->text('description');
$table->boolean('is_free')->default(false);
// Google Drive
$table->string('google_drive_folder_id')->nullable();
$table->string('attachments_folder_id')->nullable();
$table->string('cover_image_drive_id')->nullable();
$table->string('cover_image_url')->nullable();
$table->string('presentation_video_drive_id')->nullable();
$table->string('presentation_video_url')->nullable();
$table->timestamps();
});Schema::create('modules', function (Blueprint $table) {
$table->id();
$table->foreignId('course_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->integer('order')->default(1);
$table->boolean('is_free')->default(false);
// Google Drive
$table->string('google_drive_folder_id')->nullable();
$table->string('attachments_folder_id')->nullable();
$table->timestamps();
});Schema::create('lessons', function (Blueprint $table) {
$table->id();
$table->foreignId('module_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->integer('order')->default(1);
$table->boolean('is_free_preview')->default(false);
// Google Drive
$table->string('google_drive_folder_id')->nullable();
$table->string('video_drive_id')->nullable();
$table->string('video_url')->nullable();
$table->timestamps();
});Schema::create('downloads', function (Blueprint $table) {
$table->id();
$table->foreignId('category_id')->nullable()->constrained('download_categories')->nullOnDelete();
$table->string('title');
$table->string('slug')->unique();
$table->text('description')->nullable();
// Google Drive
$table->string('file_drive_id')->nullable();
$table->string('file_name')->nullable();
$table->string('file_url')->nullable();
$table->string('download_url')->nullable();
$table->string('folder_path')->nullable();
$table->timestamps();
});use App\Http\Controllers\Admin\{
CourseController,
ModuleController,
LessonController,
DownloadController,
GoogleDriveSettingsController
};
Route::middleware(['auth', 'admin'])->prefix('admin')->name('admin.')->group(function () {
// Cursos
Route::resource('courses', CourseController::class);
// Módulos
Route::resource('modules', ModuleController::class);
// Aulas
Route::resource('lessons', LessonController::class);
// Downloads
Route::resource('downloads', DownloadController::class);
// Google Drive Settings
Route::prefix('settings/google-drive')->name('settings.google-drive.')->group(function () {
Route::get('/', [GoogleDriveSettingsController::class, 'index'])->name('index');
Route::get('/connect', [GoogleDriveSettingsController::class, 'connect'])->name('connect');
Route::get('/callback', [GoogleDriveSettingsController::class, 'callback'])->name('callback');
Route::post('/disconnect', [GoogleDriveSettingsController::class, 'disconnect'])->name('disconnect');
});
});use App\Http\Controllers\GoogleDriveProxyController;
// Proxy para streaming/download (autenticado)
Route::middleware('auth')->group(function () {
Route::get('/drive/stream/{fileId}', [GoogleDriveProxyController::class, 'stream'])->name('drive.stream');
Route::get('/drive/download/{fileId}', [GoogleDriveProxyController::class, 'download'])->name('drive.download');
});Desenvolvido por: Dante Testa
Data: 24/11/2025