diff --git a/src/components/Bars.tsx b/src/components/Bars.tsx index 11b57bc..4fdc023 100644 --- a/src/components/Bars.tsx +++ b/src/components/Bars.tsx @@ -4,6 +4,7 @@ import { onMount, createEffect, createSignal, + createMemo, Show, For, onCleanup @@ -18,6 +19,12 @@ import { DarkModeToggle } from "./DarkModeToggle"; import { SkeletonBox, SkeletonText } from "./SkeletonLoader"; import { env } from "~/env/client"; +function formatDomainName(url: string): string { + const domain = url.split("://")[1]?.split(":")[0] ?? url; + const withoutWww = domain.replace(/^www\./i, ""); + return withoutWww.charAt(0).toUpperCase() + withoutWww.slice(1); +} + interface GitCommit { sha: string; message: string; @@ -385,10 +392,7 @@ export function LeftBar() {

- - {env.VITE_DOMAIN.split("://")[1].charAt(0).toUpperCase() + - env.VITE_DOMAIN.split("://")[1].slice(1)} - + {formatDomainName(env.VITE_DOMAIN)}

diff --git a/src/components/blog/CommentDeletionPrompt.tsx b/src/components/blog/CommentDeletionPrompt.tsx index 875807d..3591313 100644 --- a/src/components/blog/CommentDeletionPrompt.tsx +++ b/src/components/blog/CommentDeletionPrompt.tsx @@ -37,6 +37,11 @@ export default function CommentDeletionPrompt( } else if (fullDeleteChecked()) { deleteType = "database"; } + console.log("[CommentDeletionPrompt] Calling deleteComment:", { + commentID: props.commentID, + commenterID: props.commenterID, + deleteType + }); props.deleteComment(props.commentID, props.commenterID, deleteType); }; @@ -45,7 +50,7 @@ export default function CommentDeletionPrompt( return (
-
+
{ + console.log("[deleteComment] Starting deletion:", { + commentID, + commenterID, + deletionType, + currentUserID: props.currentUserID, + socketState: socket?.readyState + }); + setCommentDeletionLoading(true); if (!props.currentUserID) { - console.warn("Cannot delete comment: user not authenticated"); + console.warn( + "[deleteComment] Cannot delete comment: user not authenticated" + ); setCommentDeletionLoading(false); return; } - if (socket) { + if (socket && socket.readyState === WebSocket.OPEN) { + console.log("[deleteComment] Using WebSocket"); try { socket.send( JSON.stringify({ @@ -358,9 +370,53 @@ export default function CommentSectionWrapper( }) ); } catch (error) { - console.error("Error sending comment deletion:", error); - setCommentDeletionLoading(false); + console.error( + "[deleteComment] WebSocket error, falling back to HTTP:", + error + ); + // Fallback to HTTP API on WebSocket error + await fallbackCommentDeletion(commentID, commenterID, deletionType); } + } else { + console.log( + "[deleteComment] WebSocket not available, using HTTP fallback" + ); + // Fallback to HTTP API if WebSocket unavailable + await fallbackCommentDeletion(commentID, commenterID, deletionType); + } + }; + + const fallbackCommentDeletion = async ( + commentID: number, + commenterID: string, + deletionType: DeletionType + ) => { + console.log("[fallbackCommentDeletion] Calling tRPC endpoint:", { + commentID, + commenterID, + deletionType + }); + + try { + const result = await api.database.deleteComment.mutate({ + commentID, + commenterID, + deletionType + }); + + console.log("[fallbackCommentDeletion] Success:", result); + + // Handle the deletion response + deleteCommentHandler({ + action: "commentDeletionBroadcast", + commentID: commentID, + commentBody: result.commentBody || undefined, + commenterID: commenterID, + deletionType: deletionType + }); + } catch (error) { + console.error("[fallbackCommentDeletion] Error:", error); + setCommentDeletionLoading(false); } }; diff --git a/src/components/blog/CommentSorting.tsx b/src/components/blog/CommentSorting.tsx index 16b6de6..ddff6a2 100644 --- a/src/components/blog/CommentSorting.tsx +++ b/src/components/blog/CommentSorting.tsx @@ -40,7 +40,7 @@ export default function CommentSorting(props: CommentSortingProps) { {(topLevelComment) => (
checkForDoubleClick(topLevelComment.id)} - class="bg-crust mt-4 max-w-full rounded py-2 pl-2 shadow-xl select-none sm:pl-4 md:pl-8 lg:pl-12" + class="bg-mantle mt-4 max-w-full rounded-lg py-2 pl-2 shadow-xl select-none sm:pl-4 md:pl-8 lg:pl-12" > { + try { + const conn = ConnectionFactory(); + + console.log("[deleteComment] Starting deletion:", { + commentID: input.commentID, + deletionType: input.deletionType, + userId: ctx.userId, + privilegeLevel: ctx.privilegeLevel + }); + + // Get the comment to check ownership + const commentQuery = await conn.execute({ + sql: "SELECT * FROM Comment WHERE id = ?", + args: [input.commentID] + }); + + const comment = commentQuery.rows[0] as any; + if (!comment) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Comment not found" + }); + } + + // Authorization checks + const isOwner = comment.commenter_id === ctx.userId; + const isAdmin = ctx.privilegeLevel === "admin"; + + console.log("[deleteComment] Authorization check:", { + isOwner, + isAdmin, + commentOwner: comment.commenter_id, + requestingUser: ctx.userId + }); + + // User can only delete their own comments with "user" type + if (input.deletionType === "user" && !isOwner && !isAdmin) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "You can only delete your own comments" + }); + } + + // Only admins can do admin or database deletion + if ( + (input.deletionType === "admin" || + input.deletionType === "database") && + !isAdmin + ) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "Admin access required for this deletion type" + }); + } + + if (input.deletionType === "database") { + console.log("[deleteComment] Performing database deletion"); + // Full deletion - remove from database + // First delete reactions + await conn.execute({ + sql: "DELETE FROM CommentReaction WHERE comment_id = ?", + args: [input.commentID] + }); + + // Then delete the comment + await conn.execute({ + sql: "DELETE FROM Comment WHERE id = ?", + args: [input.commentID] + }); + + console.log("[deleteComment] Database deletion successful"); + return { + success: true, + deletionType: "database", + commentBody: null + }; + } else if (input.deletionType === "admin") { + console.log("[deleteComment] Performing admin deletion"); + // Admin delete - replace body with admin message + await conn.execute({ + sql: "UPDATE Comment SET body = ?, commenter_id = ? WHERE id = ?", + args: ["[deleted by admin]", "", input.commentID] + }); + + console.log("[deleteComment] Admin deletion successful"); + return { + success: true, + deletionType: "admin", + commentBody: "[deleted by admin]" + }; + } else { + console.log("[deleteComment] Performing user deletion"); + // User delete - replace body with user message + await conn.execute({ + sql: "UPDATE Comment SET body = ?, commenter_id = ? WHERE id = ?", + args: ["[deleted]", "", input.commentID] + }); + + console.log("[deleteComment] User deletion successful"); + return { + success: true, + deletionType: "user", + commentBody: "[deleted]" + }; + } + } catch (error) { + console.error("[deleteComment] Failed to delete comment:", error); + if (error instanceof TRPCError) { + throw error; + } + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to delete comment" + }); + } + }), + getCommentsByPostId: publicProcedure .input(z.object({ post_id: z.string() })) .query(async ({ input }) => {