middleware('auth')->except(['index', 'translate']); } 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', ]); $commenter = Auth::user(); // Idempotency guard: a client that retries (duplicate listeners after an // SPA swap, fast double-click, a flaky connection) can POST the identical // body twice. Treat an identical comment from the same user to the same // video within 5 seconds as the same submission and return the first one. $existing = $video->comments() ->where('user_id', $commenter->id) ->where('body', $request->body) ->where(function ($q) use ($request) { $request->parent_id ? $q->where('parent_id', $request->parent_id) : $q->whereNull('parent_id'); }) ->where('created_at', '>=', now()->subSeconds(5)) ->latest('id') ->first(); if ($existing) { return response()->json([ 'success' => true, 'comment' => $existing->load('user:id,name,avatar'), ]); } $comment = $video->comments()->create([ 'user_id' => $commenter->id, 'body' => $request->body, 'parent_id' => $request->parent_id, ]); $comment->load('user:id,name,avatar'); // Fire notifications (never notify yourself) if ($request->parent_id) { // Reply — notify the parent comment's author $parent = Comment::find($request->parent_id); if ($parent && $parent->user_id !== $commenter->id) { $parent->user->notify(new NewReplyNotification($video, $comment, $commenter)); } } else { // Top-level comment — notify the video owner if ($video->user_id !== $commenter->id) { $video->user->notify(new NewCommentNotification($video, $comment, $commenter)); } } return response()->json([ 'success' => true, 'comment' => $comment->load('user:id,name,avatar'), ]); } 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]); $comment->load('user:id,name,avatar'); return response()->json($comment->load('user:id,name,avatar')); } public function destroy(Comment $comment) { if ($comment->user_id !== Auth::id()) { return response()->json(['error' => 'Unauthorized'], 403); } $comment->delete(); return response()->json(['success' => true]); } public function translate(Request $request, Comment $comment, LlmLyricsService $llm) { if (! $llm->isEnabled()) { return response()->json(['error' => 'Translation is not configured'], 503); } $target = (string) $request->input('target', 'en'); // Derive a source-language hint from the comment author's most-frequent // access country (video_views + profile_visits, falling back to their // nationality setting). Never trust the client for this — it's used // to disambiguate scripts the LLM might otherwise misidentify. $topCountries = \App\Models\User::topCountriesFor([$comment->user_id]); $sourceCountry = $topCountries[$comment->user_id] ?? null; $sourceLang = \App\Data\CountryLanguages::primary($sourceCountry); $translated = $llm->translate((string) $comment->body, $target, $sourceLang); if ($translated === '') { return response()->json(['error' => 'Translation failed'], 502); } return response()->json([ 'success' => true, 'translated' => $translated, 'target' => $target, 'source_hint' => $sourceLang, 'author_country'=> $sourceCountry, ]); } public function like(Comment $comment) { $userId = Auth::id(); $existing = CommentLike::where('comment_id', $comment->id) ->where('user_id', $userId) ->first(); if ($existing) { $existing->delete(); $liked = false; } else { CommentLike::create(['comment_id' => $comment->id, 'user_id' => $userId]); $liked = true; // Notify the comment author (not self-likes) if ($comment->user_id !== $userId) { $video = $comment->video; if ($video) { $comment->user->notify( new NewCommentLikeNotification($video, $comment, Auth::user()) ); } } } $count = CommentLike::where('comment_id', $comment->id)->count(); return response()->json(['liked' => $liked, 'count' => $count]); } }