Bundled pre-existing changes on the branch: - Video merge: controller, service, admin merge modal, and a repair console command; new video_redirects table (with source_folder) so merged/moved videos keep resolving under their old keys. - Default avatar SVG for users without a profile picture. - Assorted view tweaks across profile, video actions/comments/insights, admin layout, video-details, and the app layout. - Small touch-ups in SuperAdminController, User model, NasSyncService. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
99 lines
3.2 KiB
PHP
99 lines
3.2 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Video;
|
|
use App\Services\VideoMergeService;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
class VideoMergeController extends Controller
|
|
{
|
|
public function __construct(private VideoMergeService $merger) {}
|
|
|
|
/**
|
|
* List the current user's other music videos that $target can be merged with.
|
|
*/
|
|
public function candidates(Request $request, Video $target)
|
|
{
|
|
$this->authorizeOwner($target);
|
|
if ($target->type !== 'music') {
|
|
return response()->json(['error' => 'Only music videos can be merged.'], 422);
|
|
}
|
|
|
|
$q = trim((string) $request->query('q', ''));
|
|
$query = Video::where('user_id', Auth::id())
|
|
->where('type', 'music')
|
|
->where('id', '!=', $target->id)
|
|
->orderByDesc('created_at');
|
|
if ($q !== '') {
|
|
$query->where('title', 'like', '%' . $q . '%');
|
|
}
|
|
$items = $query->limit(30)->get(['id', 'title', 'thumbnail', 'created_at'])->map(function ($v) {
|
|
return [
|
|
'id' => $v->getRouteKey(),
|
|
'title' => $v->title,
|
|
'thumbnail' => $v->thumbnail_url,
|
|
'created' => $v->created_at?->diffForHumans(),
|
|
];
|
|
});
|
|
|
|
return response()->json(['items' => $items]);
|
|
}
|
|
|
|
public function preview(Video $target, Video $source)
|
|
{
|
|
$this->authorizeOwner($target);
|
|
$this->authorizeOwner($source);
|
|
if ($target->type !== 'music' || $source->type !== 'music') {
|
|
return response()->json(['error' => 'Only music videos can be merged.'], 422);
|
|
}
|
|
if ($target->id === $source->id) {
|
|
return response()->json(['error' => 'Cannot merge a video with itself.'], 422);
|
|
}
|
|
|
|
return response()->json([
|
|
'source' => [
|
|
'id' => $source->getRouteKey(),
|
|
'title' => $source->title,
|
|
'thumb' => $source->thumbnail_url,
|
|
],
|
|
'target' => [
|
|
'id' => $target->getRouteKey(),
|
|
'title' => $target->title,
|
|
'thumb' => $target->thumbnail_url,
|
|
],
|
|
'counts' => $this->merger->preview($source, $target),
|
|
]);
|
|
}
|
|
|
|
public function merge(Request $request, Video $target, Video $source)
|
|
{
|
|
$this->authorizeOwner($target);
|
|
$this->authorizeOwner($source);
|
|
|
|
try {
|
|
$this->merger->merge($source, $target);
|
|
} catch (\Throwable $e) {
|
|
\Log::error('Video merge failed', [
|
|
'source' => $source->id,
|
|
'target' => $target->id,
|
|
'err' => $e->getMessage(),
|
|
]);
|
|
return response()->json(['error' => 'Merge failed: ' . $e->getMessage()], 500);
|
|
}
|
|
|
|
return response()->json([
|
|
'ok' => true,
|
|
'redirect' => route('videos.show', $target),
|
|
]);
|
|
}
|
|
|
|
private function authorizeOwner(Video $video): void
|
|
{
|
|
$user = Auth::user();
|
|
if (!$user) abort(401);
|
|
if ($user->id !== $video->user_id && !$user->isSuperAdmin()) abort(403);
|
|
}
|
|
}
|