"use server"; import { desc, eq, inArray, sql } from "drizzle-orm"; import { revalidatePath } from "next/cache"; import { headers } from "next/headers"; import { auth } from "@/lib/auth"; import { db } from "@/lib/db"; import * as schema from "@/lib/db/schema"; export type CommentWithUser = typeof schema.comment.$inferSelect & { user: Pick; }; export const getComments = async (pageSlug: string): Promise => { try { // Fetch all comments for the page with user details const commentsWithUsers = await db .select() .from(schema.comment) .innerJoin(schema.user, eq(schema.comment.userId, schema.user.id)) .where(eq(schema.comment.pageSlug, pageSlug)) .orderBy(desc(schema.comment.createdAt)); return commentsWithUsers.map(({ comment, user }) => Object.assign(comment, { user: { // we're namely worried about keeping the user's email private here, but nothing sensitive is stored in the db id: user.id, name: user.name, image: user.image, }, }), ); } catch (error) { console.error("[server/comments] error fetching comments:", error); // Return empty array instead of throwing during prerendering return []; } }; /** * Retrieves the number of comments for a given slug */ export const getCommentCount = async (slug: string): Promise => { try { const result = await db .select({ count: sql`cast(count(${schema.comment.id}) as int)`, }) .from(schema.comment) .where(eq(schema.comment.pageSlug, slug)); return Number(result[0]?.count ?? 0); } catch (error) { console.error("[server/comments] error fetching comment count:", error); return 0; } }; /** * Retrieves the numbers of comments for an array of slugs */ export const getCommentCountsForSlugs = async ( slugs: string[], ): Promise> => { try { const rows = await db .select({ pageSlug: schema.comment.pageSlug, count: sql`cast(count(${schema.comment.id}) as int)`, }) .from(schema.comment) .where(inArray(schema.comment.pageSlug, slugs)) .groupBy(schema.comment.pageSlug); const map: Record = Object.fromEntries(slugs.map((s) => [s, 0])); for (const row of rows) { map[row.pageSlug] = Number(row.count ?? 0); } return map; } catch (error) { console.error("[server/comments] error fetching comment counts:", error); return Object.fromEntries(slugs.map((s) => [s, 0])); } }; /** * Retrieves the numbers of comments for ALL slugs */ export const getAllCommentCounts = async (): Promise> => { try { const rows = await db .select({ pageSlug: schema.comment.pageSlug, count: sql`cast(count(${schema.comment.id}) as int)`, }) .from(schema.comment) .groupBy(schema.comment.pageSlug); const map: Record = {}; for (const row of rows) { map[row.pageSlug] = Number(row.count ?? 0); } return map; } catch (error) { console.error("[server/comments] error fetching comment counts:", error); return {}; } }; export const createComment = async (data: { content: string; pageSlug: string; parentId?: string; }) => { const session = await auth.api.getSession({ headers: await headers(), }); if (!session || !session.user) { throw new Error("You must be logged in to comment"); } try { // Insert the comment await db.insert(schema.comment).values({ content: data.content, pageSlug: data.pageSlug, parentId: data.parentId || null, userId: session.user.id, }); // Revalidate page revalidatePath(`/${data.pageSlug}`); } catch (error) { console.error("[server/comments] error creating comment:", error); throw new Error("Failed to create comment", { cause: error }); } }; export const updateComment = async (commentId: string, content: string) => { const session = await auth.api.getSession({ headers: await headers(), }); if (!session || !session.user) { throw new Error("You must be logged in to update a comment"); } try { // Get the comment to verify ownership const comment = await db .select({ userId: schema.comment.userId, pageSlug: schema.comment.pageSlug, }) .from(schema.comment) .where(eq(schema.comment.id, commentId)) .then((results) => results[0]); if (!comment) { throw new Error("Comment not found"); } // Verify ownership if (comment.userId !== session.user.id) { throw new Error("You can only edit your own comments"); } // Update the comment await db .update(schema.comment) .set({ content, updatedAt: new Date(), }) .where(eq(schema.comment.id, commentId)); // Revalidate page revalidatePath(`/${comment.pageSlug}`); } catch (error) { console.error("[server/comments] error updating comment:", error); throw new Error("Failed to update comment", { cause: error }); } }; export const deleteComment = async (commentId: string) => { const session = await auth.api.getSession({ headers: await headers(), }); if (!session || !session.user) { throw new Error("You must be logged in to delete a comment"); } try { // Get the comment to verify ownership and get the page_slug for revalidation const comment = await db .select({ userId: schema.comment.userId, pageSlug: schema.comment.pageSlug, }) .from(schema.comment) .where(eq(schema.comment.id, commentId)) .then((results) => results[0]); if (!comment) { throw new Error("Comment not found"); } // Verify ownership if (comment.userId !== session.user.id) { throw new Error("You can only delete your own comments"); } // Delete the comment await db.delete(schema.comment).where(eq(schema.comment.id, commentId)); // Revalidate page revalidatePath(`/${comment.pageSlug}`); } catch (error) { console.error("[server/comments] error deleting comment:", error); throw new Error("Failed to delete comment", { cause: error }); } };