user(); if (!$user) { return response()->json([ 'message' => 'Utilizador não autenticado', 'data' => null, ], 401); } $userId = $user->id; $role = $user->role_id; $search = trim((string) $request->query('search', '')); $categoryId = $request->query('category'); $watched = $request->query('watched'); $status = $request->query('status'); $perPage = $request->query('per_page', 9); $query = Video::select(['id', 'title', 'thumbnail', 'is_active']) ->with('categories:id,name') ->withCount([ 'views as watched' => fn($q) => $q->where('user_id', $userId) ]) ->when($search !== '', function ($q) use ($search) { $q->where(function ($sub) use ($search) { $sub->where('title', 'like', "%{$search}%") ->orWhere('tags', 'like', "%{$search}%"); }); }) ->when($categoryId, function ($q) use ($categoryId) { $q->whereHas('categories', function ($c) use ($categoryId) { $c->where('categories.id', $categoryId); }); }) ->when($role !== 1, fn($q) => $q->where('is_active', true)) ->when( $role === 1 && $status === 'active', fn($q) => $q->where('is_active', true) ) ->when( $role === 1 && $status === 'inactive', fn($q) => $q->where('is_active', false) ) ->when($watched !== null, function ($q) use ($watched, $userId) { (int) $watched === 1 ? $q->whereHas('views', fn($v) => $v->where('user_id', $userId)) : $q->whereDoesntHave('views', fn($v) => $v->where('user_id', $userId)); }) // left join para trazer os vídeos vistos pelo utilizador ->leftJoin('video_views as vv', function ($join) use ($userId) { $join->on('vv.video_id', '=', 'videos.id') ->where('vv.user_id', '=', $userId); }) ->orderByRaw('CASE WHEN vv.id IS NOT NULL THEN 1 ELSE 0 END ASC') ->orderBy('videos.order', 'ASC') ->select(['videos.id', 'videos.title', 'videos.thumbnail', 'videos.is_active']) ->paginate($perPage); // 1 única query para obter o número de vídeos ativos e vistos $stats = Video::selectRaw(' COUNT(CASE WHEN is_active = 1 THEN 1 END) as active_count, COUNT(CASE WHEN is_active = 1 AND EXISTS ( SELECT 1 FROM video_views WHERE video_views.video_id = videos.id AND video_views.user_id = ? ) THEN 1 END) as watched_count ', [$userId]) ->first(); $query->getCollection()->transform(function ($video) { return [ 'id' => $video->id, 'title' => $video->title, 'thumbnail' => $video->thumbnail, 'is_active' => $video->is_active, 'categories' => $video->categories, 'watched' => $video->views->isNotEmpty(), ]; }); // Guarda as categorias em cache durante 1 minuto para evitar consultas repetidas $categories = cache()->remember( 'active_categories_with_videos', 60, fn() => Category::select('id', 'name') ->where('is_active', true) ->whereHas('videos') ->orderBy('name') ->get() ); return response()->json([ 'message' => 'Vídeos obtidos com sucesso', 'data' => $query->items(), 'meta' => [ 'current_page' => $query->currentPage(), 'last_page' => $query->lastPage(), 'per_page' => $query->perPage(), 'total' => $query->total(), ], 'userId' => $userId, 'role' => $role, 'categories' => $categories, 'videosActive' => $stats->active_count, 'videosWatched' => $stats->watched_count ?? 0, ]); } public function search(Request $request) { $user = auth()->user(); $search = trim((string) $request->query('search', '')); $videos = Video::select(['id', 'title', 'thumbnail', 'is_active']) ->with([ 'categories:id,name', 'views' => function ($query) use ($user) { $query->select('id', 'video_id', 'user_id') ->where('user_id', $user->id); } ]) ->when($user->role_id !== 1, function ($query) { $query->where('is_active', true); }) ->where(function ($query) use ($search) { $query->where('title', 'like', "%{$search}%") ->orWhere('tags', 'like', "%{$search}%"); }) ->limit(20) ->get() ->map(function ($video) { return [ 'id' => $video->id, 'title' => $video->title, 'thumbnail' => $video->thumbnail, 'is_active' => $video->is_active, 'categories' => $video->categories, 'watched' => $video->views->isNotEmpty(), ]; }); return response()->json([ 'message' => 'Resultados obtidos com sucesso', 'data' => $videos, ]); } public function getVideo($id) { $user = auth()->user(); if (!$user) { return response()->json([ 'message' => 'Utilizador não autenticado', 'data' => null, 'errors' => null, ], 404); } $userID = $user->id; if ($user->role_id !== 1) { /* $video = Video::with('categories')->where('is_active', true)->find($id); */ $video = Video::with([ 'categories' => function ($query) use ($userID) { $query->where('is_active', true); }, 'views' => function ($query) use ($userID) { $query->where('user_id', $userID); } ])->find($id); $nextVideo = Video::select('id')->where('order', '>', $video->order)->where('is_active', true)->orderBy('order', 'asc')->first(); $previousVideo = Video::select('id')->where('order', '<', $video->order)->where('is_active', true)->orderBy('order', 'desc')->first(); /* Para não mostrar vídeos inactivos para utilizadores não administradores */ if (!$video || $video->is_active === false) { return response()->json([ 'message' => 'Acesso negado', 'data' => null, 'errors' => null, ], 404); } } $video = Video::with('categories')->find($id); if ($video) { $video->url = Storage::url($video->url); $video->thumbnail = Storage::url($video->thumbnail); return response()->json([ 'message' => 'Vídeo obtido com sucesso', 'data' => [ 'id' => $video->id, 'title' => $video->title, 'description' => $video->description, 'url' => $video->url, 'thumbnail' => $video->thumbnail, 'duration' => $video->duration, 'tags' => $video->tags, 'order' => $video->order, 'categories' => $video->categories->map(function ($category) { return [ 'id' => $category->id, 'name' => $category->name, ]; })->values(), 'is_active' => $video->is_active, 'watched' => $video->views->isNotEmpty(), ], 'errors' => null, 'nextVideo' => $nextVideo->id ?? null, 'previousVideo' => $previousVideo->id ?? null, ], 200); } else { return response()->json([ 'message' => 'Vídeo não encontrado', 'data' => null, 'errors' => null, ], 404); } } public function create(CreateVideoRequest $request) { $user = auth()->user(); if (!$user) { return response()->json([ 'message' => 'Utilizador não autenticado', 'data' => null, 'errors' => null, ], 404); } $validated = $request->validated(); $videoPath = $request->file('url')->store('videos', 'public'); $thumbnailPath = $request->file('thumbnail')->store('thumbnails', 'public'); $video = Video::create([ 'title' => $validated['title'], 'description' => $validated['description'], 'url' => $videoPath, 'thumbnail' => $thumbnailPath, 'duration' => $validated['duration'] ?? '00:00', 'tags' => $validated['tags'], 'order' => $validated['order'], ]); $video->categories()->sync($request->input('category_ids', [])); $baseUrl = $request->getSchemeAndHttpHost(); return response()->json([ 'message' => 'Vídeo adicionado com sucesso', 'data' => [ 'id' => $video->id, 'title' => $video->title, 'description' => $video->description, 'url' => $baseUrl . Storage::url($video->url), 'thumbnail' => $baseUrl . Storage::url($video->thumbnail), 'duration' => $video->duration, 'tags' => $video->tags, 'order' => $video->order, 'categories' => $video->categories->pluck('name'), 'is_active' => $video->is_active, ], 'errors' => null, ], 201); } public function update(UpdateVideoRequest $request, $id) { $user = auth()->user(); if (!$user) { return response()->json([ 'message' => 'Utilizador não autenticado', 'data' => null, 'errors' => null, ], 404); } $videoToUpdate = Video::find($id); if (!$videoToUpdate) { return response()->json([ 'message' => 'Vídeo não encontrado', 'data' => null, 'errors' => null, ], 404); } $validated = $request->validated(); if ($request->hasFile('thumbnail')) { // Apagar thumbnail antiga if ($videoToUpdate->thumbnail && Storage::disk('public')->exists($videoToUpdate->thumbnail)) { Storage::disk('public')->delete($videoToUpdate->thumbnail); } $validated['thumbnail'] = $request->file('thumbnail')->store('thumbnails', 'public'); } try { $videoToUpdate->update([ 'title' => $validated['title'] ?? $videoToUpdate->title, 'description' => $validated['description'] ?? $videoToUpdate->description, 'tags' => $validated['tags'] ?? $videoToUpdate->tags, 'thumbnail' => $validated['thumbnail'] ?? $videoToUpdate->thumbnail, 'is_active' => array_key_exists('is_active', $validated) ? $validated['is_active'] : $videoToUpdate->is_active, 'order' => $validated['order'] ?? $videoToUpdate->order, ]); $videoToUpdate->categories()->sync($request->input('category_ids', [])); return response()->json([ 'message' => 'Dados do vídeo atualizados com sucesso', 'data' => $videoToUpdate->load('categories'), 'errors' => null, ], 200); } catch (\Throwable $th) { return response()->json([ 'message' => 'Não foi possível atualizar o vídeo', 'data' => null, 'errors' => null, ], 500); } } public function destroy($id) { $user = auth()->user(); if (!$user) { return response()->json([ 'message' => 'Utilizador não autenticado', 'data' => null, 'errors' => null, ], 404); } $video = Video::find($id); if (!$video) { return response()->json([ 'message' => 'Vídeo não encontrado', 'data' => null, 'errors' => null, ], 404); } $video->delete(); return response()->json([ 'message' => 'Vídeo apagado com sucesso', 'data' => null, 'errors' => null, ], 200); } }