chore: remediate pygienium audit findings
Dead code: 77 verified-unused exports, files (BackArrow, MenuBars, cookies.ts, db/create.ts, schemas/comment.ts, security-headers.ts) and 12 unused dependencies removed Comments: ~370 RESTATE comments stripped across 53 files; 2 verbose blocks tightened; dead commented-out config removed Complexity: bulkUpsert extracted into 11 per-entity helpers (CCN 156->~10); login formHandler split into 3 submitters (CCN 63->~5); account page render split into 8 section components (CCN 40); updatePost SQL builder rebuilt; assert*Owned consolidated behind generic assertOwnedBy Defensive guards: 4 redundant rethrow/nullish guards removed
This commit is contained in:
@@ -460,7 +460,7 @@ function MainRightBarContent() {
|
||||
);
|
||||
}
|
||||
|
||||
export function RightBarContent() {
|
||||
function RightBarContent() {
|
||||
const site = useSite();
|
||||
return (
|
||||
<Show when={site().id === "main"} fallback={<SubdomainRightBarContent />}>
|
||||
|
||||
@@ -144,7 +144,6 @@ const sendContactEmail = action(async (formData: FormData) => {
|
||||
|
||||
const { env } = await import("~/env/server");
|
||||
|
||||
// Verify Cloudflare Turnstile token
|
||||
const turnstileValid = await verifyTurnstileToken(
|
||||
turnstileToken,
|
||||
env.TURNSTILE_SECRET_KEY,
|
||||
@@ -252,7 +251,6 @@ export function ContactForm(props: ContactFormProps) {
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
// Load server data using createAsync
|
||||
const contactData = createAsync(() => getContactData(), {
|
||||
deferStream: true
|
||||
});
|
||||
@@ -350,7 +348,6 @@ export function ContactForm(props: ContactFormProps) {
|
||||
const message = formData.get("message") as string;
|
||||
|
||||
if (name && email && message) {
|
||||
// Get fresh Turnstile token
|
||||
let currentToken = turnstileToken();
|
||||
if (
|
||||
!currentToken &&
|
||||
@@ -388,7 +385,6 @@ export function ContactForm(props: ContactFormProps) {
|
||||
setError("");
|
||||
form.reset();
|
||||
|
||||
// Reset Turnstile widget
|
||||
if (typeof window !== "undefined" && (window as any).turnstile) {
|
||||
const widgetEl = document.getElementById("turnstile-widget-1");
|
||||
if (widgetEl) {
|
||||
|
||||
@@ -138,9 +138,6 @@ export function Typewriter(props: {
|
||||
entries.forEach((entry) => {
|
||||
// If component leaves viewport while animating, we could pause
|
||||
// For now, we just ensure it starts when visible
|
||||
if (!entry.isIntersecting && cleanupAnimation) {
|
||||
// Component is off-screen - could add pause logic here if needed
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
|
||||
@@ -63,7 +63,6 @@ export default function AddAttachmentSection(props: AddAttachmentSectionProps) {
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
// Refresh the S3 file list
|
||||
await loadAttachments();
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -81,7 +80,6 @@ export default function AddAttachmentSection(props: AddAttachmentSectionProps) {
|
||||
body: JSON.stringify({ key })
|
||||
});
|
||||
|
||||
// Refresh the S3 file list
|
||||
await loadAttachments();
|
||||
} catch (err) {
|
||||
console.error("Failed to delete file:", err);
|
||||
|
||||
@@ -259,7 +259,6 @@ export default function CommentSectionWrapper(
|
||||
const newComment = async (commentBody: string, parentCommentID?: number) => {
|
||||
setCommentSubmitLoading(true);
|
||||
|
||||
// Clear any existing timeout
|
||||
if (commentSubmitTimeoutId) {
|
||||
clearTimeout(commentSubmitTimeoutId);
|
||||
}
|
||||
@@ -428,7 +427,6 @@ export default function CommentSectionWrapper(
|
||||
const editComment = async (body: string, comment_id: number) => {
|
||||
setCommentEditLoading(true);
|
||||
|
||||
// Clear any existing timeout
|
||||
if (editCommentTimeoutId) {
|
||||
clearTimeout(editCommentTimeoutId);
|
||||
}
|
||||
@@ -527,7 +525,6 @@ export default function CommentSectionWrapper(
|
||||
|
||||
setCommentDeletionLoading(true);
|
||||
|
||||
// Clear any existing timeout
|
||||
if (deleteCommentTimeoutId) {
|
||||
clearTimeout(deleteCommentTimeoutId);
|
||||
}
|
||||
@@ -623,7 +620,6 @@ export default function CommentSectionWrapper(
|
||||
setOperationError("");
|
||||
|
||||
if (data.commentBody) {
|
||||
// Soft delete (replace body with deletion message)
|
||||
setAllComments((prev) =>
|
||||
prev.map((comment) => {
|
||||
if (comment.id === data.commentID) {
|
||||
@@ -652,7 +648,6 @@ export default function CommentSectionWrapper(
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// Hard delete (remove from list)
|
||||
setAllComments((prev) =>
|
||||
prev.filter((comment) => comment.id !== data.commentID)
|
||||
);
|
||||
@@ -667,7 +662,6 @@ export default function CommentSectionWrapper(
|
||||
}, 300);
|
||||
};
|
||||
|
||||
// Deletion/edit prompt toggle
|
||||
const toggleModification = (
|
||||
commentID: number,
|
||||
commenterID: string,
|
||||
@@ -708,7 +702,6 @@ export default function CommentSectionWrapper(
|
||||
setCommentBodyForModification("");
|
||||
};
|
||||
|
||||
// Reaction handling
|
||||
const commentReaction = (reactionType: ReactionType, commentID: number) => {
|
||||
if (!props.currentUserID) {
|
||||
console.warn("Cannot react to comment: user not authenticated");
|
||||
@@ -800,7 +793,6 @@ export default function CommentSectionWrapper(
|
||||
}
|
||||
};
|
||||
|
||||
// Click outside handlers (SolidJS version)
|
||||
createEffect(() => {
|
||||
const handleClickOutsideDelete = (e: MouseEvent) => {
|
||||
if (
|
||||
|
||||
@@ -8,12 +8,10 @@ function sanitizeMermaidSvg(svgString: string): string {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(svgString, "text/html");
|
||||
|
||||
// Remove dangerous elements
|
||||
doc.querySelectorAll("script, iframe, object, embed, form, link, meta, base").forEach((el) => {
|
||||
el.remove();
|
||||
});
|
||||
|
||||
// Remove event handlers and dangerous attributes from all elements
|
||||
doc.querySelectorAll("[on*], [href*='javascript:'], [style*='expression(']").forEach((el) => {
|
||||
const attrs = Array.from(el.attributes);
|
||||
attrs.forEach((attr) => {
|
||||
|
||||
@@ -11,7 +11,6 @@ function sanitizeHtml(html: string): string {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, "text/html");
|
||||
|
||||
// Remove dangerous elements
|
||||
doc
|
||||
.querySelectorAll(
|
||||
"script, iframe, object, embed, form, link, meta, base, svg script"
|
||||
@@ -131,7 +130,6 @@ export default function PostBodyClient(props: PostBodyClientProps) {
|
||||
const processVideos = () => {
|
||||
if (!contentRef) return;
|
||||
|
||||
// Handle direct video elements
|
||||
const videoElements = contentRef.querySelectorAll("video");
|
||||
|
||||
videoElements.forEach((video) => {
|
||||
@@ -139,18 +137,14 @@ export default function PostBodyClient(props: PostBodyClientProps) {
|
||||
video.setAttribute("playsinline", "");
|
||||
video.setAttribute("controls", "");
|
||||
|
||||
// Remove download attribute if present
|
||||
video.removeAttribute("download");
|
||||
|
||||
// Ensure proper MIME types on source elements
|
||||
const sources = video.querySelectorAll("source");
|
||||
sources.forEach((source) => {
|
||||
const src = source.getAttribute("src");
|
||||
if (src) {
|
||||
// Remove download attribute from sources
|
||||
source.removeAttribute("download");
|
||||
|
||||
// Set correct type attribute if missing
|
||||
if (!source.hasAttribute("type")) {
|
||||
if (src.endsWith(".mp4")) {
|
||||
source.setAttribute("type", "video/mp4");
|
||||
@@ -163,7 +157,6 @@ export default function PostBodyClient(props: PostBodyClientProps) {
|
||||
}
|
||||
});
|
||||
|
||||
// If video has direct src attribute, ensure type is set
|
||||
const videoSrc = video.getAttribute("src");
|
||||
if (videoSrc && !video.hasAttribute("type")) {
|
||||
if (videoSrc.endsWith(".mp4")) {
|
||||
@@ -176,7 +169,6 @@ export default function PostBodyClient(props: PostBodyClientProps) {
|
||||
}
|
||||
});
|
||||
|
||||
// Handle iframes with video sources - replace with proper video tags
|
||||
const iframes = contentRef.querySelectorAll("iframe");
|
||||
iframes.forEach((iframe) => {
|
||||
const src = iframe.getAttribute("src");
|
||||
@@ -187,7 +179,6 @@ export default function PostBodyClient(props: PostBodyClientProps) {
|
||||
src.endsWith(".webm") ||
|
||||
src.endsWith(".ogg"))
|
||||
) {
|
||||
// Create a proper video element
|
||||
const video = document.createElement("video");
|
||||
video.setAttribute("controls", "");
|
||||
video.setAttribute("playsinline", "");
|
||||
@@ -195,7 +186,6 @@ export default function PostBodyClient(props: PostBodyClientProps) {
|
||||
video.style.maxWidth = "100%";
|
||||
video.style.height = "auto";
|
||||
|
||||
// Set appropriate type based on file extension
|
||||
let videoType = "video/mp4";
|
||||
if (src.endsWith(".mov")) {
|
||||
videoType = "video/mp4"; // MOV files are typically H.264 which plays as mp4
|
||||
@@ -208,7 +198,6 @@ export default function PostBodyClient(props: PostBodyClientProps) {
|
||||
video.setAttribute("type", videoType);
|
||||
video.src = src;
|
||||
|
||||
// Replace the iframe with the video element
|
||||
const parent = iframe.parentElement;
|
||||
if (parent) {
|
||||
parent.replaceChild(video, iframe);
|
||||
@@ -216,7 +205,6 @@ export default function PostBodyClient(props: PostBodyClientProps) {
|
||||
}
|
||||
});
|
||||
|
||||
// Also check for any anchor tags wrapping videos that might have download attribute
|
||||
const videoLinks = contentRef.querySelectorAll("a");
|
||||
videoLinks.forEach((link) => {
|
||||
const hasVideo = link.querySelector("video");
|
||||
|
||||
@@ -58,7 +58,6 @@ export default function PostForm(props: PostFormProps) {
|
||||
props.postId
|
||||
);
|
||||
|
||||
// Mark initial load as complete after data is loaded (for edit mode)
|
||||
// Use setTimeout to ensure this runs after all signals are initialized
|
||||
createEffect(() => {
|
||||
if (props.mode === "edit" && props.initialData) {
|
||||
@@ -73,12 +72,10 @@ export default function PostForm(props: PostFormProps) {
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
// Helper to ensure post exists (create if needed)
|
||||
const ensurePostExists = async (): Promise<number> => {
|
||||
const existingId = createdPostId() || props.postId;
|
||||
if (existingId) return existingId;
|
||||
|
||||
// Create minimal post if it doesn't exist yet
|
||||
const result = await api.database.createPost.mutate({
|
||||
category: "blog",
|
||||
title: title().replaceAll(" ", "_") || "Untitled",
|
||||
@@ -95,7 +92,6 @@ export default function PostForm(props: PostFormProps) {
|
||||
return newId;
|
||||
};
|
||||
|
||||
// Individual autosave functions for each field
|
||||
const autoSaveTitle = async () => {
|
||||
const currentTitle = title();
|
||||
if (!currentTitle || currentTitle === props.initialData?.title) return;
|
||||
@@ -248,7 +244,6 @@ export default function PostForm(props: PostFormProps) {
|
||||
}
|
||||
};
|
||||
|
||||
// Debounced versions
|
||||
const debouncedAutoSaveTitle = debounce(autoSaveTitle, 2500);
|
||||
const debouncedAutoSaveSubtitle = debounce(autoSaveSubtitle, 2500);
|
||||
const debouncedAutoSaveBody = debounce(autoSaveBody, 2500);
|
||||
@@ -256,7 +251,6 @@ export default function PostForm(props: PostFormProps) {
|
||||
const debouncedAutoSavePublished = debounce(autoSavePublished, 1000);
|
||||
const debouncedAutoSaveBanner = debounce(autoSaveBanner, 2500);
|
||||
|
||||
// Individual effects for each field
|
||||
createEffect(() => {
|
||||
const titleVal = title();
|
||||
if (isInitialLoad()) return;
|
||||
@@ -405,7 +399,6 @@ export default function PostForm(props: PostFormProps) {
|
||||
author_id: props.userID
|
||||
});
|
||||
} else {
|
||||
// Create new post
|
||||
const result = await api.database.createPost.mutate({
|
||||
category: "blog",
|
||||
title: title().replaceAll(" ", "_"),
|
||||
|
||||
@@ -100,7 +100,7 @@ export default function PostSorting(props: PostSortingProps) {
|
||||
case "newest":
|
||||
break; // Posts already come newest first from DB (DESC order)
|
||||
case "oldest":
|
||||
sorted.reverse(); // Reverse to get oldest first
|
||||
sorted.reverse();
|
||||
break;
|
||||
case "most_liked":
|
||||
sorted.sort((a, b) => (b.total_likes || 0) - (a.total_likes || 0));
|
||||
|
||||
@@ -1547,7 +1547,6 @@ export default function TextEditor(props: TextEditorProps) {
|
||||
},
|
||||
handleDOMEvents: {
|
||||
touchstart: (view, event) => {
|
||||
// Only handle touch events on mobile in fullscreen with active suggestion
|
||||
if (
|
||||
!hasSuggestion() ||
|
||||
!isFullscreen() ||
|
||||
@@ -1562,7 +1561,6 @@ export default function TextEditor(props: TextEditorProps) {
|
||||
return false;
|
||||
},
|
||||
touchend: (view, event) => {
|
||||
// Only handle touch events on mobile in fullscreen with active suggestion
|
||||
if (
|
||||
!hasSuggestion() ||
|
||||
!isFullscreen() ||
|
||||
@@ -1860,7 +1858,6 @@ export default function TextEditor(props: TextEditorProps) {
|
||||
const node = allSuperscriptNodes[i];
|
||||
const text = node.text;
|
||||
|
||||
// Check if this is a complete reference (with optional whitespace)
|
||||
const completeMatch = text.match(/^\s*\[(\d+)\]\s*$/);
|
||||
if (completeMatch) {
|
||||
const hasOtherMarks = node.marks.some(
|
||||
@@ -1877,7 +1874,6 @@ export default function TextEditor(props: TextEditorProps) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this might be the start of a split reference
|
||||
if (text === "[" && i + 2 < allSuperscriptNodes.length) {
|
||||
const nextNode = allSuperscriptNodes[i + 1];
|
||||
const afterNode = allSuperscriptNodes[i + 2];
|
||||
@@ -1958,7 +1954,6 @@ export default function TextEditor(props: TextEditorProps) {
|
||||
|
||||
allRefs.sort((a, b) => a.pos - b.pos);
|
||||
|
||||
// Check if renumbering is needed (if any ref doesn't match its expected number)
|
||||
let needsRenumbering = false;
|
||||
for (let i = 0; i < allRefs.length; i++) {
|
||||
if (allRefs[i].refNum !== i + 1) {
|
||||
|
||||
@@ -69,7 +69,6 @@ export const Mermaid = Node.create({
|
||||
getAttrs: (element) => {
|
||||
if (typeof element === "string") return false;
|
||||
|
||||
// Skip if already has data-type or data-mermaid-diagram attribute
|
||||
if (
|
||||
element.hasAttribute("data-type") ||
|
||||
element.hasAttribute("data-mermaid-diagram")
|
||||
@@ -83,7 +82,6 @@ export const Mermaid = Node.create({
|
||||
const content = code.textContent || "";
|
||||
const trimmedContent = content.trim();
|
||||
|
||||
// Check if this looks like a mermaid diagram
|
||||
const mermaidKeywords = [
|
||||
"graph ",
|
||||
"sequenceDiagram",
|
||||
@@ -174,12 +172,10 @@ export const Mermaid = Node.create({
|
||||
code.textContent = node.attrs.content || "";
|
||||
pre.appendChild(code);
|
||||
|
||||
// Validation status indicator
|
||||
const statusIndicator = document.createElement("div");
|
||||
statusIndicator.className =
|
||||
"absolute top-2 left-2 w-3 h-3 rounded-full opacity-0 group-hover:opacity-100 transition-opacity duration-200";
|
||||
|
||||
// Validate syntax asynchronously
|
||||
const validateSyntax = async () => {
|
||||
const content = node.attrs.content || "";
|
||||
if (!content.trim()) {
|
||||
@@ -250,7 +246,6 @@ export const Mermaid = Node.create({
|
||||
(p: any) => p.spec?.key === "mermaidSelection"
|
||||
);
|
||||
|
||||
// Use intersection observer to trigger update when visible
|
||||
let updateInterval: ReturnType<typeof setInterval> | null = null;
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
const BackArrow = (props: {
|
||||
height: number;
|
||||
width: number;
|
||||
stroke: string;
|
||||
strokeWidth: number;
|
||||
class?: string;
|
||||
}) => {
|
||||
return (
|
||||
<div class={props.class}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={props.strokeWidth}
|
||||
stroke={props.stroke}
|
||||
height={props.height}
|
||||
width={props.width}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BackArrow;
|
||||
@@ -1,39 +0,0 @@
|
||||
function MenuBars() {
|
||||
return (
|
||||
<svg
|
||||
width="36"
|
||||
height="30"
|
||||
viewBox="0 0 120 100"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g id="Mask group">
|
||||
<g id="Frame 1">
|
||||
<rect width="120" height="100" />
|
||||
<line
|
||||
id="LineA"
|
||||
x1="11.5"
|
||||
y1="31.5"
|
||||
x2="108.5"
|
||||
y2="31.5"
|
||||
strokeWidth="6"
|
||||
strokeLinecap="round"
|
||||
class="stroke-black dark:stroke-white"
|
||||
/>
|
||||
<line
|
||||
id="LineB"
|
||||
x1="11.5"
|
||||
y1="64.5"
|
||||
x2="108.5"
|
||||
y2="64.5"
|
||||
strokeWidth="6"
|
||||
strokeLinecap="round"
|
||||
class="stroke-black dark:stroke-white"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default MenuBars;
|
||||
@@ -128,7 +128,6 @@ export default function Button(props: ButtonProps) {
|
||||
height: number;
|
||||
} | null>(null);
|
||||
|
||||
// Measure content dimensions when not loading
|
||||
createEffect(() => {
|
||||
if (!local.loading && contentRef) {
|
||||
const rect = contentRef.getBoundingClientRect();
|
||||
|
||||
Reference in New Issue
Block a user