some fixes
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
|||||||
onMount,
|
onMount,
|
||||||
createEffect,
|
createEffect,
|
||||||
createSignal,
|
createSignal,
|
||||||
|
createMemo,
|
||||||
Show,
|
Show,
|
||||||
For,
|
For,
|
||||||
onCleanup
|
onCleanup
|
||||||
@@ -18,6 +19,12 @@ import { DarkModeToggle } from "./DarkModeToggle";
|
|||||||
import { SkeletonBox, SkeletonText } from "./SkeletonLoader";
|
import { SkeletonBox, SkeletonText } from "./SkeletonLoader";
|
||||||
import { env } from "~/env/client";
|
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 {
|
interface GitCommit {
|
||||||
sha: string;
|
sha: string;
|
||||||
message: string;
|
message: string;
|
||||||
@@ -385,10 +392,7 @@ export function LeftBar() {
|
|||||||
<div class="flex h-full min-h-full flex-col overflow-y-auto">
|
<div class="flex h-full min-h-full flex-col overflow-y-auto">
|
||||||
<Typewriter speed={10} keepAlive={10000} class="z-50 pr-8 pl-4">
|
<Typewriter speed={10} keepAlive={10000} class="z-50 pr-8 pl-4">
|
||||||
<h3 class="hover:text-subtext0 w-fit pt-6 text-center text-3xl underline transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-105">
|
<h3 class="hover:text-subtext0 w-fit pt-6 text-center text-3xl underline transition-transform duration-200 ease-in-out hover:-translate-y-0.5 hover:scale-105">
|
||||||
<a href="/">
|
<a href="/">{formatDomainName(env.VITE_DOMAIN)}</a>
|
||||||
{env.VITE_DOMAIN.split("://")[1].charAt(0).toUpperCase() +
|
|
||||||
env.VITE_DOMAIN.split("://")[1].slice(1)}
|
|
||||||
</a>
|
|
||||||
</h3>
|
</h3>
|
||||||
</Typewriter>
|
</Typewriter>
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ export default function CommentDeletionPrompt(
|
|||||||
} else if (fullDeleteChecked()) {
|
} else if (fullDeleteChecked()) {
|
||||||
deleteType = "database";
|
deleteType = "database";
|
||||||
}
|
}
|
||||||
|
console.log("[CommentDeletionPrompt] Calling deleteComment:", {
|
||||||
|
commentID: props.commentID,
|
||||||
|
commenterID: props.commenterID,
|
||||||
|
deleteType
|
||||||
|
});
|
||||||
props.deleteComment(props.commentID, props.commenterID, deleteType);
|
props.deleteComment(props.commentID, props.commenterID, deleteType);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -45,7 +50,7 @@ export default function CommentDeletionPrompt(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<div class="fixed top-48 z-100 h-fit w-11/12 sm:w-4/5 md:w-2/3">
|
<div class="fixed top-48 z-100 h-fit">
|
||||||
<div
|
<div
|
||||||
id="delete_prompt"
|
id="delete_prompt"
|
||||||
class="fade-in bg-red rounded-md px-8 py-4 shadow-lg brightness-110"
|
class="fade-in bg-red rounded-md px-8 py-4 shadow-lg brightness-110"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type {
|
|||||||
DeletionType
|
DeletionType
|
||||||
} from "~/types/comment";
|
} from "~/types/comment";
|
||||||
import { getSQLFormattedDate } from "~/lib/comment-utils";
|
import { getSQLFormattedDate } from "~/lib/comment-utils";
|
||||||
|
import { api } from "~/lib/api";
|
||||||
import CommentSection from "./CommentSection";
|
import CommentSection from "./CommentSection";
|
||||||
import CommentDeletionPrompt from "./CommentDeletionPrompt";
|
import CommentDeletionPrompt from "./CommentDeletionPrompt";
|
||||||
import EditCommentModal from "./EditCommentModal";
|
import EditCommentModal from "./EditCommentModal";
|
||||||
@@ -332,20 +333,31 @@ export default function CommentSectionWrapper(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Comment deletion
|
// Comment deletion
|
||||||
const deleteComment = (
|
const deleteComment = async (
|
||||||
commentID: number,
|
commentID: number,
|
||||||
commenterID: string,
|
commenterID: string,
|
||||||
deletionType: DeletionType
|
deletionType: DeletionType
|
||||||
) => {
|
) => {
|
||||||
|
console.log("[deleteComment] Starting deletion:", {
|
||||||
|
commentID,
|
||||||
|
commenterID,
|
||||||
|
deletionType,
|
||||||
|
currentUserID: props.currentUserID,
|
||||||
|
socketState: socket?.readyState
|
||||||
|
});
|
||||||
|
|
||||||
setCommentDeletionLoading(true);
|
setCommentDeletionLoading(true);
|
||||||
|
|
||||||
if (!props.currentUserID) {
|
if (!props.currentUserID) {
|
||||||
console.warn("Cannot delete comment: user not authenticated");
|
console.warn(
|
||||||
|
"[deleteComment] Cannot delete comment: user not authenticated"
|
||||||
|
);
|
||||||
setCommentDeletionLoading(false);
|
setCommentDeletionLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (socket) {
|
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||||
|
console.log("[deleteComment] Using WebSocket");
|
||||||
try {
|
try {
|
||||||
socket.send(
|
socket.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -358,9 +370,53 @@ export default function CommentSectionWrapper(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error sending comment deletion:", error);
|
console.error(
|
||||||
setCommentDeletionLoading(false);
|
"[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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export default function CommentSorting(props: CommentSortingProps) {
|
|||||||
{(topLevelComment) => (
|
{(topLevelComment) => (
|
||||||
<div
|
<div
|
||||||
onClick={() => checkForDoubleClick(topLevelComment.id)}
|
onClick={() => 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"
|
||||||
>
|
>
|
||||||
<Show
|
<Show
|
||||||
when={showingBlock().get(topLevelComment.id)}
|
when={showingBlock().get(topLevelComment.id)}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { createTRPCRouter, publicProcedure } from "../utils";
|
import {
|
||||||
|
createTRPCRouter,
|
||||||
|
publicProcedure,
|
||||||
|
protectedProcedure
|
||||||
|
} from "../utils";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ConnectionFactory } from "~/server/utils";
|
import { ConnectionFactory } from "~/server/utils";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
@@ -122,6 +126,132 @@ export const databaseRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
deleteComment: protectedProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
commentID: z.number(),
|
||||||
|
commenterID: z.string(),
|
||||||
|
deletionType: z.enum(["user", "admin", "database"])
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
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
|
getCommentsByPostId: publicProcedure
|
||||||
.input(z.object({ post_id: z.string() }))
|
.input(z.object({ post_id: z.string() }))
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user