Add database migration snapshots for comment_votes, comments, movies, and users tables

- Created 0009_snapshot.json with initial structure for comment_votes, comments, movies, and users tables.
- Created 0010_snapshot.json with updated structure for comments, including new fields: pinned and spoiler.
- Created 0011_snapshot.json with further updates to comments, adding anonymous field and refining existing structures.
This commit is contained in:
ngthanhvu
2026-07-24 00:06:03 -04:00
parent 70d8fc5bfa
commit 3d075f320c
22 changed files with 2832 additions and 74 deletions
+41
View File
@@ -0,0 +1,41 @@
import { eq } from 'drizzle-orm'
import { comments } from '../../database/schema'
import { getTokenFromEvent, verifyToken } from '../../utils/auth'
export default defineEventHandler(async (event) => {
const token = getTokenFromEvent(event)
if (!token) {
throw createError({ statusCode: 401, message: 'Chưa đăng nhập' })
}
const payload = verifyToken(token)
if (!payload) {
throw createError({ statusCode: 401, message: 'Phiên đăng nhập hết hạn' })
}
const id = Number(getRouterParam(event, 'id'))
if (!id) {
throw createError({ statusCode: 400, message: 'Thiếu ID bình luận' })
}
const db = useDb()
const [comment] = await db
.select()
.from(comments)
.where(eq(comments.id, id))
if (!comment) {
throw createError({ statusCode: 404, message: 'Không tìm thấy bình luận' })
}
if (comment.userId !== payload.id && payload.role !== 'admin') {
throw createError({ statusCode: 403, message: 'Không có quyền xóa bình luận này' })
}
await db
.delete(comments)
.where(eq(comments.id, id))
return { success: true }
})
+45
View File
@@ -0,0 +1,45 @@
import { eq } from 'drizzle-orm'
import { comments } from '../../../database/schema'
import { getTokenFromEvent, verifyToken } from '../../../utils/auth'
export default defineEventHandler(async (event) => {
const token = getTokenFromEvent(event)
if (!token) {
throw createError({ statusCode: 401, message: 'Chưa đăng nhập' })
}
const payload = verifyToken(token)
if (!payload) {
throw createError({ statusCode: 401, message: 'Phiên đăng nhập hết hạn' })
}
// Only admins can pin/unpin
if (payload.role !== 'admin') {
throw createError({ statusCode: 403, message: 'Chỉ admin mới có quyền ghim bình luận' })
}
const id = Number(getRouterParam(event, 'id'))
if (!id) {
throw createError({ statusCode: 400, message: 'Thiếu ID bình luận' })
}
const db = useDb()
const [comment] = await db
.select()
.from(comments)
.where(eq(comments.id, id))
if (!comment) {
throw createError({ statusCode: 404, message: 'Không tìm thấy bình luận' })
}
// Toggle pinned state
const newPinned = !comment.pinned
await db
.update(comments)
.set({ pinned: newPinned })
.where(eq(comments.id, id))
return { success: true, pinned: newPinned }
})
+135
View File
@@ -0,0 +1,135 @@
import { eq, and, desc, sql, or, isNull } from 'drizzle-orm'
import { comments, users, commentVotes } from '../../database/schema'
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const { source = '', slug, userId } = query
if (!slug) {
throw createError({ statusCode: 400, message: 'Thiếu slug phim' })
}
const db = useDb()
const whereConditions = [
eq(comments.slug, String(slug)),
isNull(comments.parentId),
]
if (source) {
whereConditions.push(eq(comments.source, String(source)))
}
// Fetch pinned comments first, then regular ones
const results = await db
.select({
id: comments.id,
userId: comments.userId,
userName: users.name,
userAvatar: users.avatar,
userRole: users.role,
source: comments.source,
slug: comments.slug,
movieName: comments.movieName,
content: comments.content,
pinned: comments.pinned,
spoiler: comments.spoiler,
anonymous: comments.anonymous,
likeCount: comments.likeCount,
dislikeCount: comments.dislikeCount,
createdAt: comments.createdAt,
})
.from(comments)
.leftJoin(users, eq(comments.userId, users.id))
.where(and(...whereConditions))
.orderBy(sql`${comments.pinned} DESC, ${comments.createdAt} DESC`)
.limit(100)
const commentIds = results.map(r => r.id)
let userVotes: Record<number, number> = {}
if (userId && commentIds.length > 0) {
const votes = await db
.select()
.from(commentVotes)
.where(and(
eq(commentVotes.userId, Number(userId)),
or(...commentIds.map(id => eq(commentVotes.commentId, id)))
))
userVotes = Object.fromEntries(votes.map(v => [v.commentId, v.vote]))
}
let replies: any[] = []
if (commentIds.length > 0) {
replies = await db
.select({
id: comments.id,
userId: comments.userId,
userName: users.name,
userAvatar: users.avatar,
userRole: users.role,
parentId: comments.parentId,
content: comments.content,
pinned: comments.pinned,
spoiler: comments.spoiler,
anonymous: comments.anonymous,
likeCount: comments.likeCount,
dislikeCount: comments.dislikeCount,
createdAt: comments.createdAt,
})
.from(comments)
.leftJoin(users, eq(comments.userId, users.id))
.where(
and(
or(...commentIds.map(id => eq(comments.parentId, id)))
)
)
.orderBy(comments.createdAt)
}
const repliesByParent: Record<number, typeof replies> = {}
for (const reply of replies) {
const parentId = reply.parentId!
if (!repliesByParent[parentId]) {
repliesByParent[parentId] = []
}
repliesByParent[parentId].push(reply)
}
return {
items: results.map(r => ({
id: r.id,
userId: r.userId,
userName: r.anonymous ? 'Ẩn danh' : (r.userName || 'Ẩn danh'),
userAvatar: r.anonymous ? null : r.userAvatar,
userRole: r.userRole,
source: r.source,
slug: r.slug,
movieName: r.movieName,
content: r.content,
pinned: r.pinned || false,
spoiler: r.spoiler || false,
anonymous: r.anonymous || false,
likeCount: r.likeCount,
dislikeCount: r.dislikeCount,
createdAt: r.createdAt,
userVote: userVotes[r.id] || 0,
replies: (repliesByParent[r.id] || []).map(rep => ({
id: rep.id,
userId: rep.userId,
userName: rep.anonymous ? 'Ẩn danh' : (rep.userName || 'Ẩn danh'),
userAvatar: rep.anonymous ? null : rep.userAvatar,
userRole: rep.userRole,
parentId: rep.parentId,
content: rep.content,
pinned: rep.pinned || false,
spoiler: rep.spoiler || false,
anonymous: rep.anonymous || false,
likeCount: rep.likeCount,
dislikeCount: rep.dislikeCount,
createdAt: rep.createdAt,
userVote: userVotes[rep.id] || 0,
})),
})),
}
})
+57
View File
@@ -0,0 +1,57 @@
import { eq } from 'drizzle-orm'
import { comments } from '../../database/schema'
import { getTokenFromEvent, verifyToken } from '../../utils/auth'
export default defineEventHandler(async (event) => {
const token = getTokenFromEvent(event)
if (!token) {
throw createError({ statusCode: 401, message: 'Chưa đăng nhập' })
}
const payload = verifyToken(token)
if (!payload) {
throw createError({ statusCode: 401, message: 'Phiên đăng nhập hết hạn' })
}
const body = await readBody(event)
const { source, slug, content, movieName, parentId, spoiler, anonymous } = body
if (!slug || !content?.trim()) {
throw createError({ statusCode: 400, message: 'Thiếu thông tin bình luận' })
}
if (parentId) {
const db = useDb()
const [parent] = await db.select().from(comments).where(eq(comments.id, Number(parentId)))
if (!parent) {
throw createError({ statusCode: 404, message: 'Không tìm thấy bình luận gốc' })
}
}
const db = useDb()
const result = await db.insert(comments).values({
userId: payload.id,
source: source || '',
slug,
movieName: movieName || null,
content: content.trim(),
parentId: parentId ? Number(parentId) : null,
spoiler: spoiler ? true : false,
anonymous: anonymous ? true : false,
})
return {
id: result.insertId,
userId: payload.id,
source: source || '',
slug,
movieName: movieName || null,
content: content.trim(),
spoiler: spoiler ? true : false,
anonymous: anonymous ? true : false,
parentId: parentId ? Number(parentId) : null,
likeCount: 0,
dislikeCount: 0,
}
})
+81
View File
@@ -0,0 +1,81 @@
import { eq, and, sql } from 'drizzle-orm'
import { comments, commentVotes } from '../../database/schema'
import { getTokenFromEvent, verifyToken } from '../../utils/auth'
export default defineEventHandler(async (event) => {
const token = getTokenFromEvent(event)
if (!token) {
throw createError({ statusCode: 401, message: 'Chưa đăng nhập' })
}
const payload = verifyToken(token)
if (!payload) {
throw createError({ statusCode: 401, message: 'Phiên đăng nhập hết hạn' })
}
const body = await readBody(event)
const { commentId, vote } = body
if (!commentId || ![-1, 0, 1].includes(vote)) {
throw createError({ statusCode: 400, message: 'Thiếu thông tin vote' })
}
const db = useDb()
const [comment] = await db.select().from(comments).where(eq(comments.id, Number(commentId)))
if (!comment) {
throw createError({ statusCode: 404, message: 'Không tìm thấy bình luận' })
}
const [existingVote] = await db
.select()
.from(commentVotes)
.where(and(
eq(commentVotes.userId, payload.id),
eq(commentVotes.commentId, Number(commentId))
))
if (existingVote) {
if (existingVote.vote === vote) {
await db.delete(commentVotes).where(eq(commentVotes.id, existingVote.id))
if (vote === 1) {
await db.update(comments).set({ likeCount: sql`${comments.likeCount} - 1` }).where(eq(comments.id, Number(commentId)))
} else if (vote === -1) {
await db.update(comments).set({ dislikeCount: sql`${comments.dislikeCount} - 1` }).where(eq(comments.id, Number(commentId)))
}
return { vote: 0, likeCount: comment.likeCount + (vote === 1 ? -1 : 0), dislikeCount: comment.dislikeCount + (vote === -1 ? -1 : 0) }
} else {
if (existingVote.vote === 1) {
await db.update(comments).set({ likeCount: sql`${comments.likeCount} - 1` }).where(eq(comments.id, Number(commentId)))
} else if (existingVote.vote === -1) {
await db.update(comments).set({ dislikeCount: sql`${comments.dislikeCount} - 1` }).where(eq(comments.id, Number(commentId)))
}
await db.update(commentVotes).set({ vote }).where(eq(commentVotes.id, existingVote.id))
if (vote === 1) {
await db.update(comments).set({ likeCount: sql`${comments.likeCount} + 1` }).where(eq(comments.id, Number(commentId)))
} else if (vote === -1) {
await db.update(comments).set({ dislikeCount: sql`${comments.dislikeCount} + 1` }).where(eq(comments.id, Number(commentId)))
}
return { vote, likeCount: comment.likeCount + (vote === 1 ? 1 : -1), dislikeCount: comment.dislikeCount + (vote === -1 ? 1 : -1) }
}
} else {
if (vote !== 0) {
await db.insert(commentVotes).values({
userId: payload.id,
commentId: Number(commentId),
vote,
})
if (vote === 1) {
await db.update(comments).set({ likeCount: sql`${comments.likeCount} + 1` }).where(eq(comments.id, Number(commentId)))
} else if (vote === -1) {
await db.update(comments).set({ dislikeCount: sql`${comments.dislikeCount} + 1` }).where(eq(comments.id, Number(commentId)))
}
}
return { vote, likeCount: comment.likeCount + (vote === 1 ? 1 : 0), dislikeCount: comment.dislikeCount + (vote === -1 ? 1 : 0) }
}
})