diff --git a/app/Http/Controllers/StudioController.php b/app/Http/Controllers/StudioController.php new file mode 100644 index 0000000..702629d --- /dev/null +++ b/app/Http/Controllers/StudioController.php @@ -0,0 +1,98 @@ +middleware('auth'); + } + + /** + * The studio dashboard — defaults to the content tab. + */ + public function index(Request $request) + { + $user = Auth::user(); + $filter = $request->get('filter', 'all'); + + $query = Video::where('user_id', $user->id)->latest(); + + switch ($filter) { + case 'public': $query->where('visibility', 'public'); break; + case 'unlisted': $query->where('visibility', 'unlisted'); break; + case 'private': $query->where('visibility', 'private'); break; + case 'shorts': $query->where('is_shorts', true); break; + case 'draft': $query->whereIn('status', ['pending', 'processing', 'failed']); break; + } + + $videos = $query->paginate(24)->withQueryString(); + + // Headline numbers for the top bar. + $totals = [ + 'total' => Video::where('user_id', $user->id)->count(), + 'public' => Video::where('user_id', $user->id)->where('visibility', 'public')->count(), + 'unlisted' => Video::where('user_id', $user->id)->where('visibility', 'unlisted')->count(), + 'private' => Video::where('user_id', $user->id)->where('visibility', 'private')->count(), + 'processing' => Video::where('user_id', $user->id)->whereIn('status', ['pending', 'processing'])->count(), + 'failed' => Video::where('user_id', $user->id)->where('status', 'failed')->count(), + 'total_views' => \DB::table('video_views') + ->whereIn('video_id', Video::where('user_id', $user->id)->pluck('id')) + ->count(), + ]; + + return view('studio.index', [ + 'videos' => $videos, + 'filter' => $filter, + 'totals' => $totals, + ]); + } + + /** + * Bulk visibility change. POSTs { video_ids: [...], visibility: '...' }. + * Guards against setting visibility on videos the user doesn't own, so a + * crafted request can't flip anyone else's content. + */ + public function bulkVisibility(Request $request) + { + $request->validate([ + 'video_ids' => ['required', 'array', 'min:1'], + 'video_ids.*' => ['integer'], + 'visibility' => ['required', 'in:public,unlisted,private'], + ]); + + $updated = Video::whereIn('id', $request->video_ids) + ->where('user_id', Auth::id()) + ->update(['visibility' => $request->visibility]); + + return response()->json(['updated' => $updated]); + } + + /** + * Bulk delete. Same owner-only guard as above. + */ + public function bulkDelete(Request $request) + { + $request->validate([ + 'video_ids' => ['required', 'array', 'min:1'], + 'video_ids.*' => ['integer'], + ]); + + $deleted = Video::whereIn('id', $request->video_ids) + ->where('user_id', Auth::id()) + ->delete(); + + return response()->json(['deleted' => $deleted]); + } +} diff --git a/resources/views/layouts/partials/sidebar.blade.php b/resources/views/layouts/partials/sidebar.blade.php index 872bdf4..05dd89b 100644 --- a/resources/views/layouts/partials/sidebar.blade.php +++ b/resources/views/layouts/partials/sidebar.blade.php @@ -57,6 +57,10 @@ Liked Videos + + + Studio + @endauth diff --git a/resources/views/studio/index.blade.php b/resources/views/studio/index.blade.php new file mode 100644 index 0000000..3bf4314 --- /dev/null +++ b/resources/views/studio/index.blade.php @@ -0,0 +1,238 @@ +@extends('layouts.app') + +@section('title', 'Studio | ' . config('app.name')) + +@section('content') + + +
+

Studio

+

Manage every video you've uploaded. Select rows for bulk visibility changes.

+ +
+
Total videos {{ number_format($totals['total']) }}
+
Public {{ number_format($totals['public']) }}
+
Unlisted {{ number_format($totals['unlisted']) }}
+
Private {{ number_format($totals['private']) }}
+
Processing {{ number_format($totals['processing']) }}
+
Failed {{ number_format($totals['failed']) }}
+
Total views {{ number_format($totals['total_views']) }}
+
+ + +
+ +
+ + + @if ($videos->isEmpty()) +
+ +

No videos in this filter

+

Switch filters above or upload a new video from the sidebar.

+
+ @else + + + + + + + + + + + + + + @foreach ($videos as $v) + + + + + + + + + + @endforeach + +
+ + VideoVisibilityViewsLikesCommentsUploaded
+
+ @if ($v->thumbnail) + + @else +
+ @endif +
+ +
{{ $v->title }}
+
+ @if ($v->description) + @php $plain = trim(preg_replace('/\s+/', ' ', strip_tags((string) $v->description))); @endphp +
{{ Str::limit($plain, 70) }}
+ @endif +
+
+
+ @switch($v->visibility) + @case('public') Public @break + @case('unlisted') Unlisted @break + @case('private') Private @break + @endswitch + @if ($v->status !== 'ready') +
{{ ucfirst($v->status) }}
+ @endif +
{{ number_format($v->view_count) }}{{ number_format($v->likes()->count()) }}{{ number_format($v->comments()->count()) }}{{ $v->created_at->format('M j, Y') }}
+ +
{{ $videos->links() }}
+ @endif +
+ + +@endsection diff --git a/routes/web.php b/routes/web.php index 6d75028..92d5164 100644 --- a/routes/web.php +++ b/routes/web.php @@ -177,6 +177,11 @@ Route::middleware(['auth', 'verified'])->group(function () { Route::get('/liked', [UserController::class, 'liked'])->name('liked'); Route::get('/feed/subscriptions', [UserController::class, 'subscriptionsFeed'])->name('subscriptions.feed'); + // Creator studio + Route::get('/studio', [\App\Http\Controllers\StudioController::class, 'index'])->name('studio'); + Route::post('/studio/bulk/visibility', [\App\Http\Controllers\StudioController::class, 'bulkVisibility'])->name('studio.bulkVisibility'); + Route::post('/studio/bulk/delete', [\App\Http\Controllers\StudioController::class, 'bulkDelete'])->name('studio.bulkDelete'); + // Logout all other devices Route::post('/settings/logout-all-devices', [UserController::class, 'logoutAllDevices'])->name('settings.logoutAllDevices');