import { GraphQLError } from "graphql"; import { builder } from "../builder"; import { CommentType, AdminCommentListType } from "../types/index"; import { comments, users } from "../../db/schema/index"; import { eq, and, desc, ilike, count } from "drizzle-orm"; import { awardPoints, checkAchievements } from "../../lib/gamification"; import { requireOwnerOrAdmin, requireAdmin } from "../../lib/acl"; builder.queryField("commentsForVideo", (t) => t.field({ type: [CommentType], args: { videoId: t.arg.string({ required: true }), }, resolve: async (_root, args, ctx) => { const commentList = await ctx.db .select() .from(comments) .where(and(eq(comments.collection, "videos"), eq(comments.item_id, args.videoId))) .orderBy(desc(comments.date_created)); return Promise.all( commentList.map(async (c) => { const user = await ctx.db .select({ id: users.id, first_name: users.first_name, last_name: users.last_name, artist_name: users.artist_name, avatar: users.avatar, }) .from(users) .where(eq(users.id, c.user_id)) .limit(1); return { ...c, user: user[0] || null }; }), ); }, }), ); builder.mutationField("createCommentForVideo", (t) => t.field({ type: CommentType, args: { videoId: t.arg.string({ required: true }), comment: t.arg.string({ required: true }), }, resolve: async (_root, args, ctx) => { if (!ctx.currentUser) throw new GraphQLError("Unauthorized"); const newComment = await ctx.db .insert(comments) .values({ collection: "videos", item_id: args.videoId, comment: args.comment, user_id: ctx.currentUser.id, }) .returning(); // Gamification (non-blocking) awardPoints(ctx.db, ctx.currentUser.id, "COMMENT_CREATE") .then(() => checkAchievements(ctx.db, ctx.currentUser!.id, "social")) .catch((e) => console.error("Gamification error on comment:", e)); const user = await ctx.db .select({ id: users.id, first_name: users.first_name, last_name: users.last_name, artist_name: users.artist_name, avatar: users.avatar, }) .from(users) .where(eq(users.id, ctx.currentUser.id)) .limit(1); return { ...newComment[0], user: user[0] || null }; }, }), ); builder.mutationField("deleteComment", (t) => t.field({ type: "Boolean", args: { id: t.arg.int({ required: true }), }, resolve: async (_root, args, ctx) => { const comment = await ctx.db.select().from(comments).where(eq(comments.id, args.id)).limit(1); if (!comment[0]) throw new GraphQLError("Comment not found"); requireOwnerOrAdmin(ctx, comment[0].user_id); await ctx.db.delete(comments).where(eq(comments.id, args.id)); return true; }, }), ); builder.queryField("adminListComments", (t) => t.field({ type: AdminCommentListType, args: { search: t.arg.string(), limit: t.arg.int(), offset: t.arg.int(), }, resolve: async (_root, args, ctx) => { requireAdmin(ctx); const limit = args.limit ?? 50; const offset = args.offset ?? 0; const conditions = args.search ? [ilike(comments.comment, `%${args.search}%`)] : []; const where = conditions.length > 0 ? and(...conditions) : undefined; const [commentList, totalRows] = await Promise.all([ ctx.db .select() .from(comments) .where(where) .orderBy(desc(comments.date_created)) .limit(limit) .offset(offset), ctx.db.select({ total: count() }).from(comments).where(where), ]); const items = await Promise.all( commentList.map(async (c) => { const user = await ctx.db .select({ id: users.id, first_name: users.first_name, last_name: users.last_name, artist_name: users.artist_name, avatar: users.avatar, }) .from(users) .where(eq(users.id, c.user_id)) .limit(1); return { ...c, user: user[0] || null }; }), ); return { items, total: totalRows[0]?.total ?? 0 }; }, }), );