Spaces:
Sleeping
Sleeping
| import { txasupabase } from '../../../lib/txasupabase.js'; | |
| // 1. TẢI DANH SÁCH BÌNH LUẬN + DANH SÁCH BỊ CHẶN | |
| export async function GET({ request, cookies }) { | |
| const url = new URL(request.url); | |
| const slug = url.searchParams.get('slug'); | |
| const token = cookies.get('auth_token')?.value; | |
| let user = null; | |
| if (token) { | |
| try { | |
| user = JSON.parse(Buffer.from(token, 'base64').toString('utf-8')); | |
| } catch (e) {} | |
| } | |
| if (slug === 'all') { | |
| if (!user || user.role !== 'admin') { | |
| return new Response(JSON.stringify({ error: 'Bạn không có quyền truy cập dữ liệu này!' }), { | |
| status: 403, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| } else if (!slug) { | |
| return new Response(JSON.stringify({ error: 'Thiếu slug phim!' }), { | |
| status: 400, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| try { | |
| let query = txasupabase.supabase | |
| .from('comments') | |
| .select('*') | |
| .order('created_at', { ascending: false }); | |
| if (slug !== 'all') { | |
| query = query.eq('movie_slug', slug); | |
| } | |
| const { data: comments, error } = await query; | |
| if (error) throw error; | |
| const bannedUsers = await txasupabase.getSetting('banned_commenters') || []; | |
| // Nạp thông tin hồ sơ người dùng (profile) phục vụ cho popup khi hover | |
| const usernames = comments ? [...new Set(comments.map(c => c.username).filter(Boolean))] : []; | |
| const userProfiles = {}; | |
| if (usernames.length > 0) { | |
| // 1. Tải danh sách user (tỉnh, xã, phim yêu thích, avatar_url) | |
| const { data: users } = await txasupabase.supabase | |
| .from('users') | |
| .select('username, province, ward, favorites, avatar_url') | |
| .in('username', usernames); | |
| // 2. Tải số lượng bình luận của các user này trên toàn hệ thống | |
| const { data: allUserComments } = await txasupabase.supabase | |
| .from('comments') | |
| .select('username') | |
| .in('username', usernames); | |
| const userCommentsCounts = {}; | |
| if (allUserComments) { | |
| allUserComments.forEach(item => { | |
| userCommentsCounts[item.username] = (userCommentsCounts[item.username] || 0) + 1; | |
| }); | |
| } | |
| if (users) { | |
| users.forEach(u => { | |
| userProfiles[u.username] = { | |
| username: u.username, | |
| province: u.province || '', | |
| ward: u.ward || '', | |
| avatar_url: u.avatar_url || '', | |
| likesCount: Array.isArray(u.favorites) ? u.favorites.length : 0, | |
| commentsCount: userCommentsCounts[u.username] || 0 | |
| }; | |
| }); | |
| } | |
| } | |
| // Nếu tải cho admin, lấy thêm tên các phim để map hiển thị | |
| let movieMap = {}; | |
| if (slug === 'all') { | |
| const { data: dbMovies } = await txasupabase.supabase | |
| .from('movies') | |
| .select('slug, movie'); | |
| if (dbMovies) { | |
| dbMovies.forEach(m => { | |
| movieMap[m.slug] = m.movie?.name || m.slug; | |
| }); | |
| } | |
| } | |
| return new Response(JSON.stringify({ | |
| comments: comments || [], | |
| bannedUsers, | |
| userProfiles, | |
| movieMap | |
| }), { | |
| status: 200, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } catch (error) { | |
| return new Response(JSON.stringify({ error: 'Lỗi tải bình luận: ' + error.message }), { | |
| status: 500, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| } | |
| // 2. GỬI BÌNH LUẬN MỚI | |
| export async function POST({ request, cookies }) { | |
| // Kiểm tra đăng nhập | |
| const token = cookies.get('auth_token')?.value; | |
| let user = null; | |
| if (token) { | |
| try { | |
| user = JSON.parse(Buffer.from(token, 'base64').toString('utf-8')); | |
| } catch (e) {} | |
| } | |
| if (!user) { | |
| return new Response(JSON.stringify({ error: 'Vui lòng đăng nhập trước khi bình luận!' }), { | |
| status: 401, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| try { | |
| // Kiểm tra xem người dùng có bị Admin chặn bình luận không | |
| const bannedUsers = await txasupabase.getSetting('banned_commenters') || []; | |
| if (Array.isArray(bannedUsers) && bannedUsers.includes(user.username)) { | |
| return new Response( | |
| JSON.stringify({ error: 'Tài khoản của bạn đã bị quản trị viên chặn bình luận trên hệ thống!' }), | |
| { status: 403, headers: { 'Content-Type': 'application/json' } } | |
| ); | |
| } | |
| const { movieSlug, content, isSpoiler, episodeName, serverName, movieType } = await request.json(); | |
| if (!movieSlug || !content) { | |
| return new Response(JSON.stringify({ error: 'Thiếu thông tin bình luận!' }), { | |
| status: 400, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| // Tự động làm sạch & chuẩn hóa tên tập phim trước khi lưu | |
| let finalEpisodeName = null; | |
| if (episodeName) { | |
| const isSingle = movieType === 'single' || movieType === 'movie'; | |
| if (isSingle) { | |
| finalEpisodeName = 'Full'; | |
| } else { | |
| const numMatch = String(episodeName).match(/\d+/); | |
| if (numMatch) { | |
| const num = parseInt(numMatch[0], 10); | |
| finalEpisodeName = `Tập ${num}`; | |
| } else { | |
| let cleaned = String(episodeName).trim().replace(/^tập\s*/i, ''); | |
| finalEpisodeName = `Tập ${cleaned}`; | |
| } | |
| } | |
| } | |
| const { data, error } = await txasupabase.supabase | |
| .from('comments') | |
| .insert({ | |
| movie_slug: movieSlug, | |
| username: user.username, | |
| content: content.trim(), | |
| is_spoiler: !!isSpoiler, | |
| episode_name: finalEpisodeName, | |
| server_name: serverName || null, | |
| likes: [], | |
| dislikes: [] | |
| }) | |
| .select(); | |
| if (error) throw error; | |
| return new Response(JSON.stringify({ message: 'Gửi bình luận thành công!', data: data?.[0] }), { | |
| status: 200, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } catch (error) { | |
| return new Response(JSON.stringify({ error: 'Lỗi gửi bình luận: ' + error.message }), { | |
| status: 500, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| } | |
| // 3. TƯƠNG TÁC THÍCH / GHÉT & CẤM BÌNH LUẬN | |
| export async function PUT({ request, cookies }) { | |
| const token = cookies.get('auth_token')?.value; | |
| let user = null; | |
| if (token) { | |
| try { | |
| user = JSON.parse(Buffer.from(token, 'base64').toString('utf-8')); | |
| } catch (e) {} | |
| } | |
| if (!user) { | |
| return new Response(JSON.stringify({ error: 'Vui lòng đăng nhập!' }), { | |
| status: 401, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| try { | |
| const body = await request.json(); | |
| const { commentId, action } = body; | |
| if (!action) { | |
| return new Response(JSON.stringify({ error: 'Thiếu hành động tương tác!' }), { | |
| status: 400, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| // A. XỬ LÝ ADMIN CẤM / MỞ CHẶN NGƯỜI DÙNG | |
| if (action === 'ban' || action === 'unban') { | |
| if (user.role !== 'admin') { | |
| return new Response(JSON.stringify({ error: 'Bạn không có quyền quản trị để thực hiện!' }), { | |
| status: 403, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| const { targetUsername } = body; | |
| if (!targetUsername) { | |
| return new Response(JSON.stringify({ error: 'Thiếu tên tài khoản cần xử lý!' }), { | |
| status: 400, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| let bannedUsers = await txasupabase.getSetting('banned_commenters') || []; | |
| if (!Array.isArray(bannedUsers)) bannedUsers = []; | |
| if (action === 'ban') { | |
| if (!bannedUsers.includes(targetUsername)) { | |
| bannedUsers.push(targetUsername); | |
| } | |
| } else { | |
| bannedUsers = bannedUsers.filter(u => u !== targetUsername); | |
| } | |
| await txasupabase.saveSetting('banned_commenters', bannedUsers); | |
| return new Response(JSON.stringify({ | |
| message: action === 'ban' ? `Đã chặn bình luận của @${targetUsername} thành công!` : `Đã mở chặn bình luận cho @${targetUsername}!`, | |
| bannedUsers | |
| }), { | |
| status: 200, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| // B. XỬ LÝ THÍCH / GHÉT BÌNH LUẬN | |
| if (!commentId || !['like', 'dislike'].includes(action)) { | |
| return new Response(JSON.stringify({ error: 'Hành động không hợp lệ!' }), { | |
| status: 400, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| const { data: comment, error: fetchError } = await txasupabase.supabase | |
| .from('comments') | |
| .select('*') | |
| .eq('id', commentId) | |
| .maybeSingle(); | |
| if (fetchError || !comment) { | |
| return new Response(JSON.stringify({ error: 'Bình luận không tồn tại!' }), { | |
| status: 404, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| let likes = comment.likes || []; | |
| let dislikes = comment.dislikes || []; | |
| const username = user.username; | |
| if (action === 'like') { | |
| if (likes.includes(username)) { | |
| // Tắt like | |
| likes = likes.filter(u => u !== username); | |
| } else { | |
| // Bật like, tắt dislike | |
| likes.push(username); | |
| dislikes = dislikes.filter(u => u !== username); | |
| } | |
| } else if (action === 'dislike') { | |
| if (dislikes.includes(username)) { | |
| // Tắt dislike | |
| dislikes = dislikes.filter(u => u !== username); | |
| } else { | |
| // Bật dislike, tắt like | |
| dislikes.push(username); | |
| likes = likes.filter(u => u !== username); | |
| } | |
| } | |
| const { data: updatedComment, error: updateError } = await txasupabase.supabase | |
| .from('comments') | |
| .update({ likes, dislikes }) | |
| .eq('id', commentId) | |
| .select() | |
| .single(); | |
| if (updateError) throw updateError; | |
| return new Response(JSON.stringify({ | |
| message: 'Tương tác thành công!', | |
| likesCount: updatedComment.likes?.length || 0, | |
| dislikesCount: updatedComment.dislikes?.length || 0, | |
| likes: updatedComment.likes || [], | |
| dislikes: updatedComment.dislikes || [] | |
| }), { | |
| status: 200, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } catch (error) { | |
| return new Response(JSON.stringify({ error: 'Lỗi xử lý tương tác: ' + error.message }), { | |
| status: 500, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| } | |
| // 4. XÓA BÌNH LUẬN | |
| export async function DELETE({ request, cookies }) { | |
| const token = cookies.get('auth_token')?.value; | |
| let user = null; | |
| if (token) { | |
| try { | |
| user = JSON.parse(Buffer.from(token, 'base64').toString('utf-8')); | |
| } catch (e) {} | |
| } | |
| if (!user) { | |
| return new Response(JSON.stringify({ error: 'Vui lòng đăng nhập!' }), { | |
| status: 401, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| try { | |
| const { commentId } = await request.json(); | |
| if (!commentId) { | |
| return new Response(JSON.stringify({ error: 'Thiếu ID bình luận cần xóa!' }), { | |
| status: 400, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| // Lấy thông tin bình luận | |
| const { data: comment, error: fetchError } = await txasupabase.supabase | |
| .from('comments') | |
| .select('*') | |
| .eq('id', commentId) | |
| .maybeSingle(); | |
| if (fetchError || !comment) { | |
| return new Response(JSON.stringify({ error: 'Bình luận không tồn tại!' }), { | |
| status: 404, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| // Kiểm tra quyền xóa: Phải là chủ nhân bình luận hoặc là Admin | |
| if (comment.username !== user.username && user.role !== 'admin') { | |
| return new Response(JSON.stringify({ error: 'Bạn không có quyền xóa bình luận này!' }), { | |
| status: 403, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| const { error: deleteError = null } = await txasupabase.supabase | |
| .from('comments') | |
| .delete() | |
| .eq('id', commentId); | |
| if (deleteError) throw deleteError; | |
| return new Response(JSON.stringify({ message: 'Xóa bình luận thành công!' }), { | |
| status: 200, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } catch (error) { | |
| return new Response(JSON.stringify({ error: 'Lỗi xóa bình luận: ' + error.message }), { | |
| status: 500, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } | |
| } | |