admin panel added and comments are working and likes are working
This commit is contained in:
1 parent
72e9439727
commit
a28023c29b
32 files changed
+5077
-2999
No files matched your search
@@ -20,7 +20,9 @@ class AuthenticatedSessionController extends Controller
|
||||
'password' => ['required'],
|
||||
]);
|
||||
|
||||
if (Auth::attempt($credentials)) {
|
||||
$remember = $request->filled('remember');
|
||||
|
||||
if (Auth::attempt($credentials, $remember)) {
|
||||
$request->session()->regenerate();
|
||||
return redirect()->intended('/videos');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Comment;
|
||||
use App\Models\Video;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class CommentController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth')->except(['index']);
|
||||
}
|
||||
|
||||
public function index(Video $video)
|
||||
{
|
||||
$comments = $video->comments()->whereNull('parent_id')->with(['user', 'replies.user'])->get();
|
||||
return response()->json($comments);
|
||||
}
|
||||
|
||||
public function store(Request $request, Video $video)
|
||||
{
|
||||
$request->validate([
|
||||
'body' => 'required|string|max:1000',
|
||||
'parent_id' => 'nullable|exists:comments,id',
|
||||
]);
|
||||
|
||||
$comment = $video->comments()->create([
|
||||
'user_id' => Auth::id(),
|
||||
'body' => $request->body,
|
||||
'parent_id' => $request->parent_id,
|
||||
]);
|
||||
|
||||
// Handle mentions
|
||||
preg_match_all('/@(\w+)/', $request->body, $matches);
|
||||
if (!empty($matches[1])) {
|
||||
// Mentions found - in production, you would send notifications here
|
||||
// For now, we just parse them
|
||||
}
|
||||
|
||||
return response()->json($comment->load('user'));
|
||||
}
|
||||
|
||||
public function update(Request $request, Comment $comment)
|
||||
{
|
||||
if ($comment->user_id !== Auth::id()) {
|
||||
return response()->json(['error' => 'Unauthorized'], 403);
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'body' => 'required|string|max:1000',
|
||||
]);
|
||||
|
||||
$comment->update([
|
||||
'body' => $request->body,
|
||||
]);
|
||||
|
||||
return response()->json($comment->load('user'));
|
||||
}
|
||||
|
||||
public function destroy(Comment $comment)
|
||||
{
|
||||
if ($comment->user_id !== Auth::id()) {
|
||||
return response()->json(['error' => 'Unauthorized'], 403);
|
||||
}
|
||||
|
||||
$comment->delete();
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Video;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class SuperAdminController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('super_admin');
|
||||
}
|
||||
|
||||
// Dashboard - Overview statistics
|
||||
public function dashboard()
|
||||
{
|
||||
$stats = [
|
||||
'total_users' => User::count(),
|
||||
'total_videos' => Video::count(),
|
||||
'total_views' => \DB::table('video_views')->count('id'),
|
||||
'total_likes' => \DB::table('video_likes')->count('id'),
|
||||
];
|
||||
|
||||
// Recent users
|
||||
$recentUsers = User::latest()->take(5)->get();
|
||||
|
||||
// Recent videos
|
||||
$recentVideos = Video::with('user')->latest()->take(5)->get();
|
||||
|
||||
// Videos by status
|
||||
$videosByStatus = Video::select('status', \DB::raw('count(*) as count'))
|
||||
->groupBy('status')
|
||||
->pluck('count', 'status');
|
||||
|
||||
// Videos by visibility
|
||||
$videosByVisibility = Video::select('visibility', \DB::raw('count(*) as count'))
|
||||
->groupBy('visibility')
|
||||
->pluck('count', 'visibility');
|
||||
|
||||
return view('admin.dashboard', compact('stats', 'recentUsers', 'recentVideos', 'videosByStatus', 'videosByVisibility'));
|
||||
}
|
||||
|
||||
// List all users with search/filter
|
||||
public function users(Request $request)
|
||||
{
|
||||
$query = User::query();
|
||||
|
||||
// Search by name or email
|
||||
if ($request->has('search') && $request->search) {
|
||||
$search = $request->search;
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('name', 'like', "%{$search}%")
|
||||
->orWhere('email', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
// Filter by role
|
||||
if ($request->has('role') && $request->role) {
|
||||
$query->where('role', $request->role);
|
||||
}
|
||||
|
||||
// Sort by
|
||||
$sort = $request->get('sort', 'latest');
|
||||
switch ($sort) {
|
||||
case 'oldest':
|
||||
$query->oldest();
|
||||
break;
|
||||
case 'name_asc':
|
||||
$query->orderBy('name', 'asc');
|
||||
break;
|
||||
case 'name_desc':
|
||||
$query->orderBy('name', 'desc');
|
||||
break;
|
||||
default:
|
||||
$query->latest();
|
||||
}
|
||||
|
||||
$users = $query->paginate(20);
|
||||
$users->appends($request->query());
|
||||
|
||||
return view('admin.users', compact('users'));
|
||||
}
|
||||
|
||||
// Show edit user form
|
||||
public function editUser(User $user)
|
||||
{
|
||||
return view('admin.edit-user', compact('user'));
|
||||
}
|
||||
|
||||
// Update user
|
||||
public function updateUser(Request $request, User $user)
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|max:255|unique:users,email,' . $user->id,
|
||||
'role' => 'required|in:user,admin,super_admin',
|
||||
'new_password' => 'nullable|min:8|confirmed',
|
||||
]);
|
||||
|
||||
$data = [
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'role' => $request->role,
|
||||
];
|
||||
|
||||
// Update password if provided
|
||||
if ($request->new_password) {
|
||||
$data['password'] = Hash::make($request->new_password);
|
||||
}
|
||||
|
||||
$user->update($data);
|
||||
|
||||
return redirect()->route('admin.users')->with('success', 'User updated successfully!');
|
||||
}
|
||||
|
||||
// Delete user
|
||||
public function deleteUser(User $user)
|
||||
{
|
||||
// Prevent deleting yourself
|
||||
if (auth()->id() === $user->id) {
|
||||
return back()->with('error', 'You cannot delete your own account!');
|
||||
}
|
||||
|
||||
// Delete user's videos and associated files
|
||||
foreach ($user->videos as $video) {
|
||||
Storage::delete('public/videos/' . $video->filename);
|
||||
if ($video->thumbnail) {
|
||||
Storage::delete('public/thumbnails/' . $video->thumbnail);
|
||||
}
|
||||
}
|
||||
$user->videos()->delete();
|
||||
|
||||
// Delete user likes and views - use direct query since relationship is named 'viewers'
|
||||
\DB::table('video_likes')->where('user_id', $user->id)->delete();
|
||||
\DB::table('video_views')->where('user_id', $user->id)->delete();
|
||||
|
||||
$user->delete();
|
||||
|
||||
return redirect()->route('admin.users')->with('success', 'User deleted successfully!');
|
||||
}
|
||||
|
||||
// List all videos
|
||||
public function videos(Request $request)
|
||||
{
|
||||
$query = Video::with('user');
|
||||
|
||||
// Search by title or description
|
||||
if ($request->has('search') && $request->search) {
|
||||
$search = $request->search;
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('title', 'like', "%{$search}%")
|
||||
->orWhere('description', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
// Filter by status
|
||||
if ($request->has('status') && $request->status) {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Filter by visibility
|
||||
if ($request->has('visibility') && $request->visibility) {
|
||||
$query->where('visibility', $request->visibility);
|
||||
}
|
||||
|
||||
// Filter by type
|
||||
if ($request->has('type') && $request->type) {
|
||||
$query->where('type', $request->type);
|
||||
}
|
||||
|
||||
// Sort by
|
||||
$sort = $request->get('sort', 'latest');
|
||||
switch ($sort) {
|
||||
case 'oldest':
|
||||
$query->oldest();
|
||||
break;
|
||||
case 'title_asc':
|
||||
$query->orderBy('title', 'asc');
|
||||
break;
|
||||
case 'title_desc':
|
||||
$query->orderBy('title', 'desc');
|
||||
break;
|
||||
case 'views':
|
||||
// Can't use withCount for views due to pivot table issue
|
||||
$query->latest();
|
||||
break;
|
||||
case 'likes':
|
||||
$query->withCount('likes')->orderBy('likes_count', 'desc');
|
||||
break;
|
||||
default:
|
||||
$query->latest();
|
||||
}
|
||||
|
||||
$videos = $query->paginate(20);
|
||||
$videos->appends($request->query());
|
||||
|
||||
return view('admin.videos', compact('videos'));
|
||||
}
|
||||
|
||||
// Show edit video form
|
||||
public function editVideo(Video $video)
|
||||
{
|
||||
return view('admin.edit-video', compact('video'));
|
||||
}
|
||||
|
||||
// Update video
|
||||
public function updateVideo(Request $request, Video $video)
|
||||
{
|
||||
$request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'visibility' => 'required|in:public,unlisted,private',
|
||||
'type' => 'required|in:generic,music,match',
|
||||
'status' => 'required|in:pending,processing,ready,failed',
|
||||
]);
|
||||
|
||||
$data = $request->only(['title', 'description', 'visibility', 'type', 'status']);
|
||||
|
||||
$video->update($data);
|
||||
|
||||
return redirect()->route('admin.videos')->with('success', 'Video updated successfully!');
|
||||
}
|
||||
|
||||
// Delete video
|
||||
public function deleteVideo(Video $video)
|
||||
{
|
||||
$videoTitle = $video->title;
|
||||
|
||||
// Delete files
|
||||
Storage::delete('public/videos/' . $video->filename);
|
||||
if ($video->thumbnail) {
|
||||
Storage::delete('public/thumbnails/' . $video->thumbnail);
|
||||
}
|
||||
|
||||
// Delete likes and views - use direct queries since relationships have timestamp issues
|
||||
\DB::table('video_likes')->where('video_id', $video->id)->delete();
|
||||
\DB::table('video_views')->where('video_id', $video->id)->delete();
|
||||
|
||||
$video->delete();
|
||||
|
||||
return redirect()->route('admin.videos')->with('success', 'Video "' . $videoTitle . '" deleted successfully!');
|
||||
}
|
||||
}
|
||||
@@ -198,7 +198,17 @@ class VideoController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
return view('videos.show', compact('video'));
|
||||
// Load comments with user relationship
|
||||
$video->load(['comments.user', 'comments.replies.user']);
|
||||
|
||||
// Render the appropriate view based on video type
|
||||
$view = match($video->type) {
|
||||
'match' => 'videos.types.match',
|
||||
'music' => 'videos.types.music',
|
||||
default => 'videos.types.generic',
|
||||
};
|
||||
|
||||
return view($view, compact('video'));
|
||||
}
|
||||
|
||||
public function edit(Video $video, Request $request)
|
||||
|
||||
@@ -64,5 +64,6 @@ class Kernel extends HttpKernel
|
||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
'super_admin' => \App\Http\Middleware\IsSuperAdmin::class,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class IsSuperAdmin
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (!Auth::check()) {
|
||||
return redirect()->route('login')->with('error', 'Please login to access this page.');
|
||||
}
|
||||
|
||||
if (!Auth::user()->isSuperAdmin()) {
|
||||
abort(403, 'You do not have permission to access this page.');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Comment extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'video_id',
|
||||
'user_id',
|
||||
'parent_id',
|
||||
'body',
|
||||
];
|
||||
|
||||
// Relationships
|
||||
public function video()
|
||||
{
|
||||
return $this->belongsTo(Video::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function parent()
|
||||
{
|
||||
return $this->belongsTo(Comment::class, 'parent_id');
|
||||
}
|
||||
|
||||
public function replies()
|
||||
{
|
||||
return $this->hasMany(Comment::class, 'parent_id')->latest();
|
||||
}
|
||||
|
||||
// Get mentioned users from comment body
|
||||
public function getMentionedUsers()
|
||||
{
|
||||
preg_match_all('/@(\w+)/', $this->body, $matches);
|
||||
if (empty($matches[1])) {
|
||||
return collect();
|
||||
}
|
||||
return User::whereIn('username', $matches[1])->get();
|
||||
}
|
||||
|
||||
// Parse mentions and make them clickable
|
||||
public function getParsedBodyAttribute()
|
||||
{
|
||||
$body = e($this->body);
|
||||
$body = preg_replace('/@(\w+)/', '<a href="/channel/$1" class="user-mention">@$1</a>', $body);
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,11 @@ class User extends Authenticatable
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'username',
|
||||
'email',
|
||||
'password',
|
||||
'avatar',
|
||||
'role',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
@@ -45,6 +47,11 @@ class User extends Authenticatable
|
||||
return $this->belongsToMany(Video::class, 'video_views')->withTimestamps();
|
||||
}
|
||||
|
||||
public function comments()
|
||||
{
|
||||
return $this->hasMany(Comment::class);
|
||||
}
|
||||
|
||||
public function getAvatarUrlAttribute()
|
||||
{
|
||||
if ($this->avatar) {
|
||||
@@ -52,5 +59,28 @@ class User extends Authenticatable
|
||||
}
|
||||
return 'https://i.pravatar.cc/150?u=' . $this->id;
|
||||
}
|
||||
|
||||
// Role helper methods
|
||||
public function isSuperAdmin()
|
||||
{
|
||||
return $this->role === 'super_admin';
|
||||
}
|
||||
|
||||
public function isAdmin()
|
||||
{
|
||||
return in_array($this->role, ['admin', 'super_admin']);
|
||||
}
|
||||
|
||||
public function isUser()
|
||||
{
|
||||
return $this->role === 'user' || $this->role === null;
|
||||
}
|
||||
|
||||
// Placeholder for subscriber count (would need a separate table in full implementation)
|
||||
public function getSubscriberCountAttribute()
|
||||
{
|
||||
// For now, return a placeholder - in production this would come from a subscriptions table
|
||||
return rand(100, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
+25
-4
@@ -44,7 +44,9 @@ class Video extends Model
|
||||
|
||||
public function viewers()
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'video_views')->withTimestamps();
|
||||
return $this->belongsToMany(User::class, 'video_views')
|
||||
->withPivot('watched_at')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
// Accessors
|
||||
@@ -74,10 +76,10 @@ class Video extends Model
|
||||
return $this->likes()->count();
|
||||
}
|
||||
|
||||
// Get view count
|
||||
// Get view count - use direct query to avoid timestamp issues
|
||||
public function getViewCountAttribute()
|
||||
{
|
||||
return $this->viewers()->count();
|
||||
return \DB::table('video_views')->where('video_id', $this->id)->count();
|
||||
}
|
||||
|
||||
// Get shareable URL for the video
|
||||
@@ -149,6 +151,15 @@ class Video extends Model
|
||||
};
|
||||
}
|
||||
|
||||
public function getTypeSymbolAttribute()
|
||||
{
|
||||
return match($this->type) {
|
||||
'music' => '🎵',
|
||||
'match' => '🏆',
|
||||
default => '🎬',
|
||||
};
|
||||
}
|
||||
|
||||
public function isGeneric()
|
||||
{
|
||||
return $this->type === 'generic';
|
||||
@@ -163,5 +174,15 @@ class Video extends Model
|
||||
{
|
||||
return $this->type === 'match';
|
||||
}
|
||||
}
|
||||
|
||||
// Comments relationship
|
||||
public function comments()
|
||||
{
|
||||
return $this->hasMany(Comment::class)->latest();
|
||||
}
|
||||
|
||||
public function getCommentCountAttribute()
|
||||
{
|
||||
return $this->comments()->count();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user