1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2026-06-19 12:55:28 -04:00
Files
jarv.is/lib/server/comments.ts
T
jake 5a1636baa3 refactor: migrate from Biome to oxlint/oxfmt, remove contact form
- Replace Biome with oxlint + oxfmt (OXC toolchain) for linting and formatting
- Add .oxlintrc.json and .oxfmtrc.json configuration files
- Update VS Code settings and devcontainer to use oxc-vscode extension
- Remove contact form, Resend email integration, and related server action/schema
- Remove unused UI components (accordion, alert, card, tabs, toggle, etc.)
2026-04-05 19:45:18 -04:00

227 lines
6.2 KiB
TypeScript

"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<typeof schema.user.$inferSelect, "id" | "name" | "image">;
};
export const getComments = async (pageSlug: string): Promise<CommentWithUser[]> => {
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<number> => {
try {
const result = await db
.select({
count: sql<number>`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<Record<string, number>> => {
try {
const rows = await db
.select({
pageSlug: schema.comment.pageSlug,
count: sql<number>`cast(count(${schema.comment.id}) as int)`,
})
.from(schema.comment)
.where(inArray(schema.comment.pageSlug, slugs))
.groupBy(schema.comment.pageSlug);
const map: Record<string, number> = 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<Record<string, number>> => {
try {
const rows = await db
.select({
pageSlug: schema.comment.pageSlug,
count: sql<number>`cast(count(${schema.comment.id}) as int)`,
})
.from(schema.comment)
.groupBy(schema.comment.pageSlug);
const map: Record<string, number> = {};
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 });
}
};