dptxa-proxy / src /pages /api /movie /comments.js
TXAVLOG
Deploy DPTXA to Hugging Face Spaces
4bea261
Raw
History Blame Contribute Delete
12.7 kB
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' }
});
}
}