feat(studio): /studio content table + bulk visibility / bulk delete
Skeleton for the creator studio surface deferred since Phase 6. The
/studio route lands on a filterable table of the signed-in user's
videos — All / Public / Unlisted / Private / Shorts / Processing-or-
Failed — with headline totals (public / unlisted / private counts,
processing + failed, total views) in a stat strip above.
Each row shows thumbnail + title + stripped description preview +
visibility badge + per-video totals. Row-level checkboxes raise a
toolbar when any row is selected; the toolbar supports:
- Change visibility across the selection (any of the three visibility
enums).
- Delete the selection with a confirm prompt.
Server-side the bulk endpoints owner-filter by Auth::id() so a crafted
request body can't flip or delete anyone else's videos. Response is
JSON { updated | deleted : N } so the page can reload / toast cleanly.
Sidebar now carries a persistent "Studio" link under the You section
alongside History / Liked Videos.
Analytics, stepped upload (Details → Visibility), and schedule remain
future work — this ships the content management surface, nothing else.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
45505e6419
commit
8c6746e547
4 files changed
+345
No files matched your search
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Video;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
/**
|
||||
* Creator studio — the content management surface YouTube calls "YouTube
|
||||
* Studio". A skeleton for now: a filterable table of the signed-in user's
|
||||
* videos plus a bulk visibility change endpoint. Analytics, stepped upload
|
||||
* and schedule are future work.
|
||||
*/
|
||||
class StudioController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->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]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user