remove excess comments

This commit is contained in:
Michael Freno
2025-12-23 10:30:51 -05:00
parent 236555e41e
commit 8ca8e6f712
29 changed files with 1 additions and 242 deletions

View File

@@ -114,7 +114,6 @@ function AppLayout(props: { children: any }) {
if (Math.abs(deltaX) > Math.abs(deltaY)) { if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Mobile: Only left bar // Mobile: Only left bar
if (currentIsMobile) { if (currentIsMobile) {
// Swipe right anywhere - reveal left bar
if (deltaX > SWIPE_THRESHOLD) { if (deltaX > SWIPE_THRESHOLD) {
setLeftBarVisible(true); setLeftBarVisible(true);
} }
@@ -124,7 +123,6 @@ function AppLayout(props: { children: any }) {
} }
} else { } else {
// Desktop: Both bars // Desktop: Both bars
// Swipe right anywhere - reveal left bar
if (deltaX > SWIPE_THRESHOLD) { if (deltaX > SWIPE_THRESHOLD) {
setLeftBarVisible(true); setLeftBarVisible(true);
} }

View File

@@ -4,7 +4,6 @@ import LoadingSpinner from "~/components/LoadingSpinner";
import { getClientCookie } from "~/lib/cookies.client"; import { getClientCookie } from "~/lib/cookies.client";
export default function DeletionForm() { export default function DeletionForm() {
// State management
const [countDown, setCountDown] = createSignal(0); const [countDown, setCountDown] = createSignal(0);
const [emailSent, setEmailSent] = createSignal(false); const [emailSent, setEmailSent] = createSignal(false);
const [error, setError] = createSignal(""); const [error, setError] = createSignal("");
@@ -30,7 +29,6 @@ export default function DeletionForm() {
} }
}; };
// Check for existing timer on mount
createEffect(() => { createEffect(() => {
const timer = getClientCookie("deletionRequestSent"); const timer = getClientCookie("deletionRequestSent");
if (timer) { if (timer) {
@@ -46,7 +44,6 @@ export default function DeletionForm() {
} }
}); });
// Form submission handler
const sendEmailTrigger = async (e: Event) => { const sendEmailTrigger = async (e: Event) => {
e.preventDefault(); e.preventDefault();
setLoading(true); setLoading(true);

View File

@@ -25,7 +25,6 @@ import ReactionBar from "./ReactionBar";
export default function CommentBlock(props: CommentBlockProps) { export default function CommentBlock(props: CommentBlockProps) {
const location = useLocation(); const location = useLocation();
// State signals
const [commentCollapsed, setCommentCollapsed] = createSignal(false); const [commentCollapsed, setCommentCollapsed] = createSignal(false);
const [showingReactionOptions, setShowingReactionOptions] = const [showingReactionOptions, setShowingReactionOptions] =
createSignal(false); createSignal(false);

View File

@@ -22,7 +22,6 @@ const RETRY_INTERVAL = 5000;
export default function CommentSectionWrapper( export default function CommentSectionWrapper(
props: CommentSectionWrapperProps props: CommentSectionWrapperProps
) { ) {
// State signals
const [allComments, setAllComments] = createSignal<Comment[]>( const [allComments, setAllComments] = createSignal<Comment[]>(
props.allComments props.allComments
); );
@@ -57,14 +56,12 @@ export default function CommentSectionWrapper(
const [commentBodyForModification, setCommentBodyForModification] = const [commentBodyForModification, setCommentBodyForModification] =
createSignal<string>(""); createSignal<string>("");
// Non-reactive refs (store without triggering reactivity)
let userCommentMap: Map<UserPublicData, number[]> = props.userCommentMap; let userCommentMap: Map<UserPublicData, number[]> = props.userCommentMap;
let deletePromptRef: HTMLDivElement | undefined; let deletePromptRef: HTMLDivElement | undefined;
let modificationPromptRef: HTMLDivElement | undefined; let modificationPromptRef: HTMLDivElement | undefined;
let retryCount = 0; let retryCount = 0;
let socket: WebSocket | undefined; let socket: WebSocket | undefined;
// WebSocket connection effect
createEffect(() => { createEffect(() => {
const connect = () => { const connect = () => {
if (socket) return; if (socket) return;
@@ -121,7 +118,6 @@ export default function CommentSectionWrapper(
connect(); connect();
// Cleanup on unmount
onCleanup(() => { onCleanup(() => {
if (socket?.readyState === WebSocket.OPEN) { if (socket?.readyState === WebSocket.OPEN) {
socket.close(); socket.close();
@@ -130,7 +126,6 @@ export default function CommentSectionWrapper(
}); });
}); });
// Helper functions
const updateChannel = () => { const updateChannel = () => {
if (!socket || socket.readyState !== WebSocket.OPEN) { if (!socket || socket.readyState !== WebSocket.OPEN) {
return; return;
@@ -155,7 +150,6 @@ export default function CommentSectionWrapper(
} }
}; };
// Comment creation
const newComment = async (commentBody: string, parentCommentID?: number) => { const newComment = async (commentBody: string, parentCommentID?: number) => {
setCommentSubmitLoading(true); setCommentSubmitLoading(true);
@@ -179,11 +173,9 @@ export default function CommentSectionWrapper(
); );
} catch (error) { } catch (error) {
console.error("Error sending comment creation:", error); console.error("Error sending comment creation:", error);
// Fallback to HTTP API on WebSocket error
await fallbackCommentCreation(commentBody, parentCommentID); await fallbackCommentCreation(commentBody, parentCommentID);
} }
} else { } else {
// Fallback to HTTP API if WebSocket unavailable
await fallbackCommentCreation(commentBody, parentCommentID); await fallbackCommentCreation(commentBody, parentCommentID);
} }
}; };
@@ -254,7 +246,6 @@ export default function CommentSectionWrapper(
} }
setAllComments((prevComments) => [...(prevComments || []), newComment]); setAllComments((prevComments) => [...(prevComments || []), newComment]);
// Update user comment map
const existingIDs = Array.from(userCommentMap.entries()).find( const existingIDs = Array.from(userCommentMap.entries()).find(
([key, _]) => ([key, _]) =>
key.email === userData.email && key.email === userData.email &&
@@ -272,7 +263,6 @@ export default function CommentSectionWrapper(
setCommentSubmitLoading(false); setCommentSubmitLoading(false);
}; };
// Comment updating
const editComment = async (body: string, comment_id: number) => { const editComment = async (body: string, comment_id: number) => {
setCommentEditLoading(true); setCommentEditLoading(true);
@@ -375,14 +365,12 @@ export default function CommentSectionWrapper(
"[deleteComment] WebSocket error, falling back to HTTP:", "[deleteComment] WebSocket error, falling back to HTTP:",
error error
); );
// Fallback to HTTP API on WebSocket error
await fallbackCommentDeletion(commentID, commenterID, deletionType); await fallbackCommentDeletion(commentID, commenterID, deletionType);
} }
} else { } else {
console.log( console.log(
"[deleteComment] WebSocket not available, using HTTP fallback" "[deleteComment] WebSocket not available, using HTTP fallback"
); );
// Fallback to HTTP API if WebSocket unavailable
await fallbackCommentDeletion(commentID, commenterID, deletionType); await fallbackCommentDeletion(commentID, commenterID, deletionType);
} }
}; };
@@ -407,7 +395,6 @@ export default function CommentSectionWrapper(
console.log("[fallbackCommentDeletion] Success:", result); console.log("[fallbackCommentDeletion] Success:", result);
// Handle the deletion response
deleteCommentHandler({ deleteCommentHandler({
action: "commentDeletionBroadcast", action: "commentDeletionBroadcast",
commentID: commentID, commentID: commentID,

View File

@@ -84,7 +84,6 @@ async function loadHighlightJS(): Promise<HLJSApi> {
hljs.registerLanguage("diff", diff.default); hljs.registerLanguage("diff", diff.default);
hljs.registerLanguage("toml", toml.default); hljs.registerLanguage("toml", toml.default);
// Also register common aliases
hljs.registerLanguage("js", javascript.default); hljs.registerLanguage("js", javascript.default);
hljs.registerLanguage("ts", typescript.default); hljs.registerLanguage("ts", typescript.default);
hljs.registerLanguage("jsx", javascript.default); hljs.registerLanguage("jsx", javascript.default);
@@ -97,18 +96,15 @@ export default function PostBodyClient(props: PostBodyClientProps) {
let contentRef: HTMLDivElement | undefined; let contentRef: HTMLDivElement | undefined;
const [hljs, setHljs] = createSignal<HLJSApi | null>(null); const [hljs, setHljs] = createSignal<HLJSApi | null>(null);
// Process superscript references and enhance the References section
const processReferences = () => { const processReferences = () => {
if (!contentRef) return; if (!contentRef) return;
const foundRefs = new Map<string, HTMLElement>(); const foundRefs = new Map<string, HTMLElement>();
// Find all <sup> elements with [n] pattern
const supElements = contentRef.querySelectorAll("sup"); const supElements = contentRef.querySelectorAll("sup");
supElements.forEach((sup) => { supElements.forEach((sup) => {
const text = sup.textContent?.trim() || ""; const text = sup.textContent?.trim() || "";
// Match patterns like [1], [2], [a], [*], etc.
const match = text.match(/^\[(.+?)\]$/); const match = text.match(/^\[(.+?)\]$/);
if (match) { if (match) {
@@ -116,10 +112,8 @@ export default function PostBodyClient(props: PostBodyClientProps) {
const refId = `ref-${refNumber}`; const refId = `ref-${refNumber}`;
const refBackId = `ref-${refNumber}-back`; const refBackId = `ref-${refNumber}-back`;
// Add ID to the sup element itself for back navigation
sup.id = refBackId; sup.id = refBackId;
// Replace sup content with a clickable link
sup.innerHTML = ""; sup.innerHTML = "";
const link = document.createElement("a"); const link = document.createElement("a");
link.href = `#${refId}`; link.href = `#${refId}`;
@@ -129,13 +123,11 @@ export default function PostBodyClient(props: PostBodyClientProps) {
link.style.cssText = link.style.cssText =
"text-decoration: none; font-size: 0.75em; vertical-align: super;"; "text-decoration: none; font-size: 0.75em; vertical-align: super;";
// Add smooth scroll behavior
link.onclick = (e) => { link.onclick = (e) => {
e.preventDefault(); e.preventDefault();
const target = document.getElementById(refId); const target = document.getElementById(refId);
if (target) { if (target) {
target.scrollIntoView({ behavior: "smooth", block: "center" }); target.scrollIntoView({ behavior: "smooth", block: "center" });
// Highlight the reference briefly
target.style.backgroundColor = "rgba(137, 180, 250, 0.2)"; target.style.backgroundColor = "rgba(137, 180, 250, 0.2)";
setTimeout(() => { setTimeout(() => {
target.style.backgroundColor = ""; target.style.backgroundColor = "";
@@ -147,7 +139,6 @@ export default function PostBodyClient(props: PostBodyClientProps) {
} }
}); });
// Find and enhance the References section
const headings = contentRef.querySelectorAll("h2"); const headings = contentRef.querySelectorAll("h2");
let referencesSection: HTMLElement | null = null; let referencesSection: HTMLElement | null = null;
@@ -158,7 +149,6 @@ export default function PostBodyClient(props: PostBodyClientProps) {
}); });
if (referencesSection) { if (referencesSection) {
// Style the References heading
referencesSection.className = "text-2xl font-bold mb-4 text-text"; referencesSection.className = "text-2xl font-bold mb-4 text-text";
// Find the parent container and add styling // Find the parent container and add styling

View File

@@ -44,10 +44,8 @@ import swift from "highlight.js/lib/languages/swift";
import kotlin from "highlight.js/lib/languages/kotlin"; import kotlin from "highlight.js/lib/languages/kotlin";
import dockerfile from "highlight.js/lib/languages/dockerfile"; import dockerfile from "highlight.js/lib/languages/dockerfile";
// Create lowlight instance with common languages
const lowlight = createLowlight(common); const lowlight = createLowlight(common);
// Register existing languages
lowlight.register("css", css); lowlight.register("css", css);
lowlight.register("js", js); lowlight.register("js", js);
lowlight.register("javascript", js); lowlight.register("javascript", js);
@@ -56,7 +54,6 @@ lowlight.register("typescript", ts);
lowlight.register("ocaml", ocaml); lowlight.register("ocaml", ocaml);
lowlight.register("rust", rust); lowlight.register("rust", rust);
// Register new languages
lowlight.register("python", python); lowlight.register("python", python);
lowlight.register("py", python); lowlight.register("py", python);
lowlight.register("java", java); lowlight.register("java", java);
@@ -87,7 +84,6 @@ lowlight.register("kt", kotlin);
lowlight.register("dockerfile", dockerfile); lowlight.register("dockerfile", dockerfile);
lowlight.register("docker", dockerfile); lowlight.register("docker", dockerfile);
// Available languages for selector
const AVAILABLE_LANGUAGES = [ const AVAILABLE_LANGUAGES = [
{ value: null, label: "Plain Text" }, { value: null, label: "Plain Text" },
{ value: "bash", label: "Bash/Shell" }, { value: "bash", label: "Bash/Shell" },
@@ -115,7 +111,6 @@ const AVAILABLE_LANGUAGES = [
{ value: "yaml", label: "YAML" } { value: "yaml", label: "YAML" }
] as const; ] as const;
// Mermaid diagram templates
const MERMAID_TEMPLATES = [ const MERMAID_TEMPLATES = [
{ {
name: "Flowchart", name: "Flowchart",
@@ -183,7 +178,6 @@ const MERMAID_TEMPLATES = [
} }
]; ];
// Keyboard shortcuts data
interface ShortcutCategory { interface ShortcutCategory {
name: string; name: string;
shortcuts: Array<{ shortcuts: Array<{
@@ -271,7 +265,6 @@ const isMac = () => {
); );
}; };
// IFrame extension
interface IframeOptions { interface IframeOptions {
allowFullscreen: boolean; allowFullscreen: boolean;
HTMLAttributes: { HTMLAttributes: {
@@ -472,19 +465,16 @@ export default function TextEditor(props: TextEditorProps) {
handleClickOn(view, pos, node, nodePos, event) { handleClickOn(view, pos, node, nodePos, event) {
const target = event.target as HTMLElement; const target = event.target as HTMLElement;
// Check if click is on a summary element inside details
const summary = target.closest("summary"); const summary = target.closest("summary");
if (summary) { if (summary) {
const details = summary.closest('[data-type="details"]'); const details = summary.closest('[data-type="details"]');
if (details) { if (details) {
// Toggle the open attribute
const isOpen = details.hasAttribute("open"); const isOpen = details.hasAttribute("open");
if (isOpen) { if (isOpen) {
details.removeAttribute("open"); details.removeAttribute("open");
} else { } else {
details.setAttribute("open", ""); details.setAttribute("open", "");
} }
// Also toggle hidden attribute on details content
const content = details.querySelector( const content = details.querySelector(
'[data-type="detailsContent"]' '[data-type="detailsContent"]'
); );
@@ -504,7 +494,6 @@ export default function TextEditor(props: TextEditorProps) {
onUpdate: ({ editor }) => { onUpdate: ({ editor }) => {
untrack(() => { untrack(() => {
props.updateContent(editor.getHTML()); props.updateContent(editor.getHTML());
// Auto-manage references section
setTimeout(() => updateReferencesSection(editor), 100); setTimeout(() => updateReferencesSection(editor), 100);
}); });
}, },
@@ -515,7 +504,6 @@ export default function TextEditor(props: TextEditorProps) {
if (hasSelection && !editor.state.selection.empty) { if (hasSelection && !editor.state.selection.empty) {
setShowBubbleMenu(true); setShowBubbleMenu(true);
// Position the bubble menu
const { view } = editor; const { view } = editor;
const start = view.coordsAtPos(from); const start = view.coordsAtPos(from);
const end = view.coordsAtPos(to); const end = view.coordsAtPos(to);
@@ -530,7 +518,6 @@ export default function TextEditor(props: TextEditorProps) {
} }
})); }));
// Update editor content when preSet changes (e.g., when data loads), but only if editor exists and content is different
createEffect( createEffect(
on( on(
() => props.preSet, () => props.preSet,
@@ -544,14 +531,12 @@ export default function TextEditor(props: TextEditorProps) {
) )
); );
// Auto-manage references section
const updateReferencesSection = (editorInstance: any) => { const updateReferencesSection = (editorInstance: any) => {
if (!editorInstance) return; if (!editorInstance) return;
const doc = editorInstance.state.doc; const doc = editorInstance.state.doc;
const foundRefs = new Set<string>(); const foundRefs = new Set<string>();
// Scan document for superscript marks containing [n] patterns
doc.descendants((node: any) => { doc.descendants((node: any) => {
if (node.isText && node.marks) { if (node.isText && node.marks) {
const hasSuperscript = node.marks.some( const hasSuperscript = node.marks.some(
@@ -567,7 +552,6 @@ export default function TextEditor(props: TextEditorProps) {
} }
}); });
// If no references found, remove references section if it exists
if (foundRefs.size === 0) { if (foundRefs.size === 0) {
let hasReferencesSection = false; let hasReferencesSection = false;
let hrPos = -1; let hrPos = -1;
@@ -581,7 +565,6 @@ export default function TextEditor(props: TextEditorProps) {
}); });
if (hasReferencesSection && sectionStartPos > 0) { if (hasReferencesSection && sectionStartPos > 0) {
// Find the HR before References heading
doc.nodesBetween( doc.nodesBetween(
Math.max(0, sectionStartPos - 50), Math.max(0, sectionStartPos - 50),
sectionStartPos, sectionStartPos,
@@ -592,7 +575,6 @@ export default function TextEditor(props: TextEditorProps) {
} }
); );
// Delete from HR to end of document
if (hrPos >= 0) { if (hrPos >= 0) {
const tr = editorInstance.state.tr; const tr = editorInstance.state.tr;
tr.delete(hrPos, doc.content.size); tr.delete(hrPos, doc.content.size);
@@ -602,7 +584,6 @@ export default function TextEditor(props: TextEditorProps) {
return; return;
} }
// Convert Set to sorted array
const refNumbers = Array.from(foundRefs).sort((a, b) => { const refNumbers = Array.from(foundRefs).sort((a, b) => {
const numA = parseInt(a); const numA = parseInt(a);
const numB = parseInt(b); const numB = parseInt(b);
@@ -612,7 +593,6 @@ export default function TextEditor(props: TextEditorProps) {
return a.localeCompare(b); return a.localeCompare(b);
}); });
// Check if References section already exists
let referencesHeadingPos = -1; let referencesHeadingPos = -1;
let existingRefs = new Set<string>(); let existingRefs = new Set<string>();
@@ -620,7 +600,6 @@ export default function TextEditor(props: TextEditorProps) {
if (node.type.name === "heading" && node.textContent === "References") { if (node.type.name === "heading" && node.textContent === "References") {
referencesHeadingPos = pos; referencesHeadingPos = pos;
} }
// Check for existing reference list items
if (referencesHeadingPos >= 0 && node.type.name === "paragraph") { if (referencesHeadingPos >= 0 && node.type.name === "paragraph") {
const match = node.textContent.match(/^\[(.+?)\]/); const match = node.textContent.match(/^\[(.+?)\]/);
if (match) { if (match) {
@@ -629,7 +608,6 @@ export default function TextEditor(props: TextEditorProps) {
} }
}); });
// If references section doesn't exist, create it
if (referencesHeadingPos === -1) { if (referencesHeadingPos === -1) {
const content: any[] = [ const content: any[] = [
{ type: "horizontalRule" }, { type: "horizontalRule" },
@@ -640,7 +618,6 @@ export default function TextEditor(props: TextEditorProps) {
} }
]; ];
// Add each reference as a paragraph
refNumbers.forEach((refNum) => { refNumbers.forEach((refNum) => {
content.push({ content.push({
type: "paragraph", type: "paragraph",
@@ -658,7 +635,6 @@ export default function TextEditor(props: TextEditorProps) {
}); });
}); });
// Insert at the end
const tr = editorInstance.state.tr; const tr = editorInstance.state.tr;
tr.insert( tr.insert(
doc.content.size, doc.content.size,
@@ -666,11 +642,9 @@ export default function TextEditor(props: TextEditorProps) {
); );
editorInstance.view.dispatch(tr); editorInstance.view.dispatch(tr);
} else { } else {
// Update existing references section - add missing refs
const newRefs = refNumbers.filter((ref) => !existingRefs.has(ref)); const newRefs = refNumbers.filter((ref) => !existingRefs.has(ref));
if (newRefs.length > 0) { if (newRefs.length > 0) {
// Find position after References heading to insert new refs
let insertPos = referencesHeadingPos; let insertPos = referencesHeadingPos;
doc.nodesBetween( doc.nodesBetween(
referencesHeadingPos, referencesHeadingPos,
@@ -781,17 +755,12 @@ export default function TextEditor(props: TextEditorProps) {
const { from } = instance.state.selection; const { from } = instance.state.selection;
instance.chain().focus().insertContent(content).run(); instance.chain().focus().insertContent(content).run();
// Move cursor to the paragraph inside detailsContent
// Structure: details (from+1) > detailsSummary > detailsContent > paragraph
// We need to position inside the paragraph which is roughly from + title.length + 3 nodes deep
setTimeout(() => { setTimeout(() => {
const { state } = instance; const { state } = instance;
let targetPos = from; let targetPos = from;
// Navigate through the document to find the paragraph inside detailsContent
state.doc.nodesBetween(from, from + 200, (node, pos) => { state.doc.nodesBetween(from, from + 200, (node, pos) => {
if (node.type.name === "detailsContent") { if (node.type.name === "detailsContent") {
// Position cursor at the start of the first child (paragraph)
targetPos = pos + 1; targetPos = pos + 1;
return false; // Stop iteration return false; // Stop iteration
} }
@@ -811,7 +780,6 @@ export default function TextEditor(props: TextEditorProps) {
instance.chain().focus().toggleCodeBlock().run(); instance.chain().focus().toggleCodeBlock().run();
// If language specified, update the node attributes
if (language) { if (language) {
instance.chain().updateAttributes("codeBlock", { language }).run(); instance.chain().updateAttributes("codeBlock", { language }).run();
} }
@@ -869,7 +837,6 @@ export default function TextEditor(props: TextEditorProps) {
const { state } = instance; const { state } = instance;
const { selection } = state; const { selection } = state;
// Find the row node
let rowNode = null; let rowNode = null;
let depth = 0; let depth = 0;
for (let d = selection.$anchor.depth; d > 0; d--) { for (let d = selection.$anchor.depth; d > 0; d--) {
@@ -908,10 +875,8 @@ export default function TextEditor(props: TextEditorProps) {
const { state } = instance; const { state } = instance;
const { selection } = state; const { selection } = state;
// Get the current cell position
const cellPos = selection.$anchor; const cellPos = selection.$anchor;
// Find table and column index
let tableNode = null; let tableNode = null;
let tableDepth = 0; let tableDepth = 0;
for (let d = cellPos.depth; d > 0; d--) { for (let d = cellPos.depth; d > 0; d--) {
@@ -924,7 +889,6 @@ export default function TextEditor(props: TextEditorProps) {
} }
if (tableNode) { if (tableNode) {
// Find which column we're in
let colIndex = 0; let colIndex = 0;
const cellNode = cellPos.node(cellPos.depth); const cellNode = cellPos.node(cellPos.depth);
const rowNode = cellPos.node(cellPos.depth - 1); const rowNode = cellPos.node(cellPos.depth - 1);
@@ -939,7 +903,6 @@ export default function TextEditor(props: TextEditorProps) {
} }
}); });
// Check if this column has content
let hasContent = false; let hasContent = false;
tableNode.descendants((node, pos, parent) => { tableNode.descendants((node, pos, parent) => {
if (parent && parent.type.name === "tableRow") { if (parent && parent.type.name === "tableRow") {
@@ -964,7 +927,6 @@ export default function TextEditor(props: TextEditorProps) {
instance.chain().focus().deleteColumn().run(); instance.chain().focus().deleteColumn().run();
}; };
// Close language selector on outside click
createEffect(() => { createEffect(() => {
if (showLanguageSelector()) { if (showLanguageSelector()) {
const handleClickOutside = (e: MouseEvent) => { const handleClickOutside = (e: MouseEvent) => {
@@ -985,7 +947,6 @@ export default function TextEditor(props: TextEditorProps) {
} }
}); });
// Close table menu on outside click
createEffect(() => { createEffect(() => {
if (showTableMenu()) { if (showTableMenu()) {
const handleClickOutside = (e: MouseEvent) => { const handleClickOutside = (e: MouseEvent) => {
@@ -1006,7 +967,6 @@ export default function TextEditor(props: TextEditorProps) {
} }
}); });
// Close mermaid menu on outside click
createEffect(() => { createEffect(() => {
if (showMermaidTemplates()) { if (showMermaidTemplates()) {
const handleClickOutside = (e: MouseEvent) => { const handleClickOutside = (e: MouseEvent) => {
@@ -1027,7 +987,6 @@ export default function TextEditor(props: TextEditorProps) {
} }
}); });
// Close conditional config on outside click
createEffect(() => { createEffect(() => {
if (showConditionalConfig()) { if (showConditionalConfig()) {
const handleClickOutside = (e: MouseEvent) => { const handleClickOutside = (e: MouseEvent) => {
@@ -1065,7 +1024,6 @@ export default function TextEditor(props: TextEditorProps) {
setShowMermaidTemplates(false); setShowMermaidTemplates(false);
}; };
// Conditional block functions
const showConditionalConfigurator = (e: MouseEvent) => { const showConditionalConfigurator = (e: MouseEvent) => {
const buttonRect = (e.currentTarget as HTMLElement).getBoundingClientRect(); const buttonRect = (e.currentTarget as HTMLElement).getBoundingClientRect();
setConditionalConfigPosition({ setConditionalConfigPosition({
@@ -1073,7 +1031,6 @@ export default function TextEditor(props: TextEditorProps) {
left: buttonRect.left left: buttonRect.left
}); });
// If cursor is in existing conditional, load its values
const instance = editor(); const instance = editor();
if (instance?.isActive("conditionalBlock")) { if (instance?.isActive("conditionalBlock")) {
const attrs = instance.getAttributes("conditionalBlock"); const attrs = instance.getAttributes("conditionalBlock");
@@ -1092,7 +1049,6 @@ export default function TextEditor(props: TextEditorProps) {
inline: true inline: true
}); });
} else { } else {
// Reset to defaults for new conditional
setConditionalForm({ setConditionalForm({
conditionType: "auth", conditionType: "auth",
conditionValue: "authenticated", conditionValue: "authenticated",
@@ -1112,9 +1068,7 @@ export default function TextEditor(props: TextEditorProps) {
conditionalForm(); conditionalForm();
if (inline) { if (inline) {
// Handle inline conditionals (Mark)
if (instance.isActive("conditionalInline")) { if (instance.isActive("conditionalInline")) {
// Update existing inline conditional
instance instance
.chain() .chain()
.focus() .focus()
@@ -1126,7 +1080,6 @@ export default function TextEditor(props: TextEditorProps) {
}) })
.run(); .run();
} else { } else {
// Apply inline conditional to selection
instance instance
.chain() .chain()
.focus() .focus()
@@ -1138,9 +1091,7 @@ export default function TextEditor(props: TextEditorProps) {
.run(); .run();
} }
} else { } else {
// Handle block conditionals (Node)
if (instance.isActive("conditionalBlock")) { if (instance.isActive("conditionalBlock")) {
// Update existing conditional
instance instance
.chain() .chain()
.focus() .focus()
@@ -1151,7 +1102,6 @@ export default function TextEditor(props: TextEditorProps) {
}) })
.run(); .run();
} else { } else {
// Wrap selection in new conditional
instance instance
.chain() .chain()
.focus() .focus()
@@ -1167,12 +1117,10 @@ export default function TextEditor(props: TextEditorProps) {
setShowConditionalConfig(false); setShowConditionalConfig(false);
}; };
// Toggle fullscreen mode
const toggleFullscreen = () => { const toggleFullscreen = () => {
setIsFullscreen(!isFullscreen()); setIsFullscreen(!isFullscreen());
}; };
// ESC key to exit fullscreen
createEffect(() => { createEffect(() => {
if (isFullscreen()) { if (isFullscreen()) {
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
@@ -1187,7 +1135,6 @@ export default function TextEditor(props: TextEditorProps) {
} }
}); });
// Detect mobile keyboard visibility
createEffect(() => { createEffect(() => {
if (typeof window === "undefined" || !window.visualViewport) return; if (typeof window === "undefined" || !window.visualViewport) return;
@@ -1198,7 +1145,6 @@ export default function TextEditor(props: TextEditorProps) {
const currentHeight = viewport.height; const currentHeight = viewport.height;
const heightDiff = initialHeight - currentHeight; const heightDiff = initialHeight - currentHeight;
// If viewport height decreased by more than 150px, keyboard is likely open
if (heightDiff > 150) { if (heightDiff > 150) {
setKeyboardVisible(true); setKeyboardVisible(true);
setKeyboardHeight(heightDiff); setKeyboardHeight(heightDiff);
@@ -1217,7 +1163,6 @@ export default function TextEditor(props: TextEditorProps) {
}; };
}); });
// Table Grid Selector Component
const TableGridSelector = () => { const TableGridSelector = () => {
const [hoverCell, setHoverCell] = createSignal({ row: 0, col: 0 }); const [hoverCell, setHoverCell] = createSignal({ row: 0, col: 0 });
const maxRows = 10; const maxRows = 10;
@@ -1268,7 +1213,6 @@ export default function TextEditor(props: TextEditorProps) {
); );
}; };
// Conditional Configurator Component
const ConditionalConfigurator = () => { const ConditionalConfigurator = () => {
return ( return (
<div class="bg-mantle border-surface2 w-80 rounded border p-4 shadow-lg"> <div class="bg-mantle border-surface2 w-80 rounded border p-4 shadow-lg">

View File

@@ -30,7 +30,6 @@ export const DarkModeProvider: ParentComponent = (props) => {
const [isDark, setIsDark] = createSignal(false); const [isDark, setIsDark] = createSignal(false);
onMount(() => { onMount(() => {
// Check system preference
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
setIsDark(mediaQuery.matches); setIsDark(mediaQuery.matches);

3
src/env/client.ts vendored
View File

@@ -70,7 +70,6 @@ export const validateClientEnv = (
}; };
// Validate and export environment variables directly // Validate and export environment variables directly
// This happens once at module load time on the client
const validateAndExportEnv = (): ClientEnv => { const validateAndExportEnv = (): ClientEnv => {
try { try {
const validated = validateClientEnv(import.meta.env); const validated = validateClientEnv(import.meta.env);
@@ -84,12 +83,10 @@ const validateAndExportEnv = (): ClientEnv => {
export const env = validateAndExportEnv(); export const env = validateAndExportEnv();
// Helper function to check if a variable is missing
export const isMissingEnvVar = (varName: string): boolean => { export const isMissingEnvVar = (varName: string): boolean => {
return !import.meta.env[varName] || import.meta.env[varName]?.trim() === ""; return !import.meta.env[varName] || import.meta.env[varName]?.trim() === "";
}; };
// Helper function to get all missing client environment variables
export const getMissingEnvVars = (): string[] => { export const getMissingEnvVars = (): string[] => {
const requiredClientVars = [ const requiredClientVars = [
"VITE_DOMAIN", "VITE_DOMAIN",

3
src/env/server.ts vendored
View File

@@ -90,7 +90,6 @@ export const validateServerEnv = (
}; };
// Validate and export environment variables directly // Validate and export environment variables directly
// This happens once at module load time on the server
const validateAndExportEnv = (): ServerEnv => { const validateAndExportEnv = (): ServerEnv => {
try { try {
const validated = validateServerEnv(process.env); const validated = validateServerEnv(process.env);
@@ -104,12 +103,10 @@ const validateAndExportEnv = (): ServerEnv => {
export const env = validateAndExportEnv(); export const env = validateAndExportEnv();
// Helper function to check if a variable is missing
export const isMissingEnvVar = (varName: string): boolean => { export const isMissingEnvVar = (varName: string): boolean => {
return !process.env[varName] || process.env[varName]?.trim() === ""; return !process.env[varName] || process.env[varName]?.trim() === "";
}; };
// Helper function to get all missing server environment variables
export const getMissingEnvVars = (): string[] => { export const getMissingEnvVars = (): string[] => {
const requiredServerVars = [ const requiredServerVars = [
"NODE_ENV", "NODE_ENV",

View File

@@ -38,7 +38,6 @@ const getUserProfile = query(async (): Promise<UserProfile | null> => {
const user = res.rows[0] as any; const user = res.rows[0] as any;
// Transform database User to UserProfile
return { return {
id: user.id, id: user.id,
email: user.email ?? undefined, email: user.email ?? undefined,
@@ -63,10 +62,8 @@ export default function AccountPage() {
const userData = createAsync(() => getUserProfile(), { deferStream: true }); const userData = createAsync(() => getUserProfile(), { deferStream: true });
// Local user state for client-side updates
const [user, setUser] = createSignal<UserProfile | null>(null); const [user, setUser] = createSignal<UserProfile | null>(null);
// Form loading states
const [emailButtonLoading, setEmailButtonLoading] = createSignal(false); const [emailButtonLoading, setEmailButtonLoading] = createSignal(false);
const [displayNameButtonLoading, setDisplayNameButtonLoading] = const [displayNameButtonLoading, setDisplayNameButtonLoading] =
createSignal(false); createSignal(false);
@@ -76,7 +73,6 @@ export default function AccountPage() {
const [profileImageSetLoading, setProfileImageSetLoading] = const [profileImageSetLoading, setProfileImageSetLoading] =
createSignal(false); createSignal(false);
// Password state
const [passwordsMatch, setPasswordsMatch] = createSignal(false); const [passwordsMatch, setPasswordsMatch] = createSignal(false);
const [showPasswordLengthWarning, setShowPasswordLengthWarning] = const [showPasswordLengthWarning, setShowPasswordLengthWarning] =
createSignal(false); createSignal(false);
@@ -86,19 +82,16 @@ export default function AccountPage() {
const [passwordError, setPasswordError] = createSignal(false); const [passwordError, setPasswordError] = createSignal(false);
const [passwordDeletionError, setPasswordDeletionError] = createSignal(false); const [passwordDeletionError, setPasswordDeletionError] = createSignal(false);
// Show/hide password toggles
const [showOldPasswordInput, setShowOldPasswordInput] = createSignal(false); const [showOldPasswordInput, setShowOldPasswordInput] = createSignal(false);
const [showPasswordInput, setShowPasswordInput] = createSignal(false); const [showPasswordInput, setShowPasswordInput] = createSignal(false);
const [showPasswordConfInput, setShowPasswordConfInput] = createSignal(false); const [showPasswordConfInput, setShowPasswordConfInput] = createSignal(false);
// Success messages
const [showImageSuccess, setShowImageSuccess] = createSignal(false); const [showImageSuccess, setShowImageSuccess] = createSignal(false);
const [showEmailSuccess, setShowEmailSuccess] = createSignal(false); const [showEmailSuccess, setShowEmailSuccess] = createSignal(false);
const [showDisplayNameSuccess, setShowDisplayNameSuccess] = const [showDisplayNameSuccess, setShowDisplayNameSuccess] =
createSignal(false); createSignal(false);
const [showPasswordSuccess, setShowPasswordSuccess] = createSignal(false); const [showPasswordSuccess, setShowPasswordSuccess] = createSignal(false);
// Profile image state
const [profileImage, setProfileImage] = createSignal<Blob | undefined>( const [profileImage, setProfileImage] = createSignal<Blob | undefined>(
undefined undefined
); );
@@ -109,7 +102,6 @@ export default function AccountPage() {
createSignal(false); createSignal(false);
const [preSetHolder, setPreSetHolder] = createSignal<string | null>(null); const [preSetHolder, setPreSetHolder] = createSignal<string | null>(null);
// Form refs
let oldPasswordRef: HTMLInputElement | undefined; let oldPasswordRef: HTMLInputElement | undefined;
let newPasswordRef: HTMLInputElement | undefined; let newPasswordRef: HTMLInputElement | undefined;
let newPasswordConfRef: HTMLInputElement | undefined; let newPasswordConfRef: HTMLInputElement | undefined;
@@ -117,10 +109,8 @@ export default function AccountPage() {
let displayNameRef: HTMLInputElement | undefined; let displayNameRef: HTMLInputElement | undefined;
let deleteAccountPasswordRef: HTMLInputElement | undefined; let deleteAccountPasswordRef: HTMLInputElement | undefined;
// Helper to get current user (from SSR data or local state)
const currentUser = () => user() || userData(); const currentUser = () => user() || userData();
// Initialize preSetHolder when userData loads
createEffect(() => { createEffect(() => {
const userProfile = userData(); const userProfile = userData();
if (userProfile?.image && !preSetHolder()) { if (userProfile?.image && !preSetHolder()) {
@@ -308,7 +298,6 @@ export default function AccountPage() {
if (response.ok && result.result?.data?.success) { if (response.ok && result.result?.data?.success) {
setShowPasswordSuccess(true); setShowPasswordSuccess(true);
setTimeout(() => setShowPasswordSuccess(false), 3000); setTimeout(() => setShowPasswordSuccess(false), 3000);
// Clear form
if (oldPasswordRef) oldPasswordRef.value = ""; if (oldPasswordRef) oldPasswordRef.value = "";
if (newPasswordRef) newPasswordRef.value = ""; if (newPasswordRef) newPasswordRef.value = "";
if (newPasswordConfRef) newPasswordConfRef.value = ""; if (newPasswordConfRef) newPasswordConfRef.value = "";
@@ -362,7 +351,6 @@ export default function AccountPage() {
} }
setShowPasswordSuccess(true); setShowPasswordSuccess(true);
setTimeout(() => setShowPasswordSuccess(false), 3000); setTimeout(() => setShowPasswordSuccess(false), 3000);
// Clear form
if (newPasswordRef) newPasswordRef.value = ""; if (newPasswordRef) newPasswordRef.value = "";
if (newPasswordConfRef) newPasswordConfRef.value = ""; if (newPasswordConfRef) newPasswordConfRef.value = "";
} else { } else {
@@ -395,7 +383,6 @@ export default function AccountPage() {
const result = await response.json(); const result = await response.json();
if (response.ok && result.result?.data?.success) { if (response.ok && result.result?.data?.success) {
// Redirect to login
navigate("/login"); navigate("/login");
} else { } else {
setPasswordDeletionError(true); setPasswordDeletionError(true);
@@ -425,7 +412,6 @@ export default function AccountPage() {
} }
}; };
// Password validation helpers
const checkPasswordLength = (password: string) => { const checkPasswordLength = (password: string) => {
if (password.length >= 8) { if (password.length >= 8) {
setPasswordLengthSufficient(true); setPasswordLengthSufficient(true);

View File

@@ -7,7 +7,6 @@ export async function GET(event: APIEvent) {
const code = url.searchParams.get("code"); const code = url.searchParams.get("code");
const error = url.searchParams.get("error"); const error = url.searchParams.get("error");
// Handle OAuth error (user denied access, etc.)
if (error) { if (error) {
return new Response(null, { return new Response(null, {
status: 302, status: 302,
@@ -15,7 +14,6 @@ export async function GET(event: APIEvent) {
}); });
} }
// Missing authorization code
if (!code) { if (!code) {
return new Response(null, { return new Response(null, {
status: 302, status: 302,
@@ -32,13 +30,11 @@ export async function GET(event: APIEvent) {
const result = await caller.auth.githubCallback({ code }); const result = await caller.auth.githubCallback({ code });
if (result.success) { if (result.success) {
// Redirect to account page on success
return new Response(null, { return new Response(null, {
status: 302, status: 302,
headers: { Location: result.redirectTo || "/account" } headers: { Location: result.redirectTo || "/account" }
}); });
} else { } else {
// Redirect to login with error
return new Response(null, { return new Response(null, {
status: 302, status: 302,
headers: { Location: "/login?error=auth_failed" } headers: { Location: "/login?error=auth_failed" }
@@ -47,7 +43,6 @@ export async function GET(event: APIEvent) {
} catch (error) { } catch (error) {
console.error("GitHub OAuth callback error:", error); console.error("GitHub OAuth callback error:", error);
// Handle specific TRPC errors
if (error && typeof error === "object" && "code" in error) { if (error && typeof error === "object" && "code" in error) {
const trpcError = error as { code: string; message?: string }; const trpcError = error as { code: string; message?: string };

View File

@@ -7,7 +7,6 @@ export async function GET(event: APIEvent) {
const code = url.searchParams.get("code"); const code = url.searchParams.get("code");
const error = url.searchParams.get("error"); const error = url.searchParams.get("error");
// Handle OAuth error (user denied access, etc.)
if (error) { if (error) {
return new Response(null, { return new Response(null, {
status: 302, status: 302,
@@ -15,7 +14,6 @@ export async function GET(event: APIEvent) {
}); });
} }
// Missing authorization code
if (!code) { if (!code) {
return new Response(null, { return new Response(null, {
status: 302, status: 302,
@@ -32,13 +30,11 @@ export async function GET(event: APIEvent) {
const result = await caller.auth.googleCallback({ code }); const result = await caller.auth.googleCallback({ code });
if (result.success) { if (result.success) {
// Redirect to account page on success
return new Response(null, { return new Response(null, {
status: 302, status: 302,
headers: { Location: result.redirectTo || "/account" } headers: { Location: result.redirectTo || "/account" }
}); });
} else { } else {
// Redirect to login with error
return new Response(null, { return new Response(null, {
status: 302, status: 302,
headers: { Location: "/login?error=auth_failed" } headers: { Location: "/login?error=auth_failed" }
@@ -47,7 +43,6 @@ export async function GET(event: APIEvent) {
} catch (error) { } catch (error) {
console.error("Google OAuth callback error:", error); console.error("Google OAuth callback error:", error);
// Handle specific TRPC errors
if (error && typeof error === "object" && "code" in error) { if (error && typeof error === "object" && "code" in error) {
const trpcError = error as { code: string; message?: string }; const trpcError = error as { code: string; message?: string };

View File

@@ -11,7 +11,6 @@ export async function GET(event: APIEvent) {
// Parse rememberMe parameter // Parse rememberMe parameter
const rememberMe = rememberMeParam === "true"; const rememberMe = rememberMeParam === "true";
// Missing required parameters
if (!email || !token) { if (!email || !token) {
return new Response(null, { return new Response(null, {
status: 302, status: 302,
@@ -32,13 +31,11 @@ export async function GET(event: APIEvent) {
}); });
if (result.success) { if (result.success) {
// Redirect to account page on success
return new Response(null, { return new Response(null, {
status: 302, status: 302,
headers: { Location: result.redirectTo || "/account" }, headers: { Location: result.redirectTo || "/account" },
}); });
} else { } else {
// Redirect to login with error
return new Response(null, { return new Response(null, {
status: 302, status: 302,
headers: { Location: "/login?error=auth_failed" }, headers: { Location: "/login?error=auth_failed" },

View File

@@ -7,7 +7,6 @@ export async function GET(event: APIEvent) {
const email = url.searchParams.get("email"); const email = url.searchParams.get("email");
const token = url.searchParams.get("token"); const token = url.searchParams.get("token");
// Missing required parameters
if (!email || !token) { if (!email || !token) {
return new Response( return new Response(
` `

View File

@@ -15,7 +15,6 @@ export async function POST() {
expires: new Date(0) // Set expiry to past date expires: new Date(0) // Set expiry to past date
}); });
// Redirect to home page
return new Response(null, { return new Response(null, {
status: 302, status: 302,
headers: { headers: {

View File

@@ -18,7 +18,6 @@ import type { Comment, CommentReaction, UserPublicData } from "~/types/comment";
import { TerminalSplash } from "~/components/TerminalSplash"; import { TerminalSplash } from "~/components/TerminalSplash";
import { api } from "~/lib/api"; import { api } from "~/lib/api";
// Server function to fetch post by title
const getPostByTitle = query( const getPostByTitle = query(
async ( async (
title: string, title: string,
@@ -48,7 +47,6 @@ const getPostByTitle = query(
const post = postResults.rows[0] as any; const post = postResults.rows[0] as any;
if (!post) { if (!post) {
// Check if post exists but is unpublished
const existQuery = "SELECT id FROM Post WHERE title = ?"; const existQuery = "SELECT id FROM Post WHERE title = ?";
const existRes = await conn.execute({ const existRes = await conn.execute({
sql: existQuery, sql: existQuery,

View File

@@ -8,7 +8,6 @@ import TagSelector from "~/components/blog/TagSelector";
import PostSorting from "~/components/blog/PostSorting"; import PostSorting from "~/components/blog/PostSorting";
import { TerminalSplash } from "~/components/TerminalSplash"; import { TerminalSplash } from "~/components/TerminalSplash";
// Server function to fetch all posts
const getPosts = query(async () => { const getPosts = query(async () => {
"use server"; "use server";
const { ConnectionFactory, getPrivilegeLevel } = const { ConnectionFactory, getPrivilegeLevel } =

View File

@@ -40,7 +40,6 @@ const getContactData = query(async () => {
return { remainingTime }; return { remainingTime };
}, "contact-data"); }, "contact-data");
// Server action for form submission
const sendContactEmail = action(async (formData: FormData) => { const sendContactEmail = action(async (formData: FormData) => {
"use server"; "use server";
const name = formData.get("name") as string; const name = formData.get("name") as string;
@@ -65,7 +64,6 @@ const sendContactEmail = action(async (formData: FormData) => {
); );
} }
// Check rate limit
const contactExp = getCookie("contactRequestSent"); const contactExp = getCookie("contactRequestSent");
if (contactExp) { if (contactExp) {
const expires = new Date(contactExp); const expires = new Date(contactExp);
@@ -189,7 +187,6 @@ export default function ContactPage() {
setCountDown(serverData.remainingTime); setCountDown(serverData.remainingTime);
} }
// Check for existing timer
const timer = getClientCookie("contactRequestSent"); const timer = getClientCookie("contactRequestSent");
if (timer) { if (timer) {
timerIdRef = setInterval(() => calcRemainder(timer), 1000); timerIdRef = setInterval(() => calcRemainder(timer), 1000);

View File

@@ -42,7 +42,6 @@ export default function LoginPage() {
const register = () => searchParams.mode === "register"; const register = () => searchParams.mode === "register";
const usePassword = () => searchParams.auth === "password"; const usePassword = () => searchParams.auth === "password";
// State management
const [error, setError] = createSignal(""); const [error, setError] = createSignal("");
const [loading, setLoading] = createSignal(false); const [loading, setLoading] = createSignal(false);
const [countDown, setCountDown] = createSignal(0); const [countDown, setCountDown] = createSignal(0);
@@ -58,7 +57,6 @@ export default function LoginPage() {
createSignal(false); createSignal(false);
const [passwordBlurred, setPasswordBlurred] = createSignal(false); const [passwordBlurred, setPasswordBlurred] = createSignal(false);
// Form refs
let emailRef: HTMLInputElement | undefined; let emailRef: HTMLInputElement | undefined;
let passwordRef: HTMLInputElement | undefined; let passwordRef: HTMLInputElement | undefined;
let passwordConfRef: HTMLInputElement | undefined; let passwordConfRef: HTMLInputElement | undefined;
@@ -70,7 +68,6 @@ export default function LoginPage() {
const githubClientId = env.VITE_GITHUB_CLIENT_ID; const githubClientId = env.VITE_GITHUB_CLIENT_ID;
const domain = env.VITE_DOMAIN || "https://www.freno.me"; const domain = env.VITE_DOMAIN || "https://www.freno.me";
// Calculate remaining time from cookie
const calcRemainder = (timer: string) => { const calcRemainder = (timer: string) => {
const expires = new Date(timer); const expires = new Date(timer);
const remaining = expires.getTime() - Date.now(); const remaining = expires.getTime() - Date.now();
@@ -86,7 +83,6 @@ export default function LoginPage() {
} }
}; };
// Check for existing timer on mount
createEffect(() => { createEffect(() => {
const timer = getClientCookie("emailLoginLinkRequested"); const timer = getClientCookie("emailLoginLinkRequested");
if (timer) { if (timer) {
@@ -103,7 +99,6 @@ export default function LoginPage() {
}); });
}); });
// Check for OAuth/callback errors in URL
createEffect(() => { createEffect(() => {
const errorParam = searchParams.error; const errorParam = searchParams.error;
if (errorParam) { if (errorParam) {
@@ -121,7 +116,6 @@ export default function LoginPage() {
} }
}); });
// Form submission handler
const formHandler = async (e: Event) => { const formHandler = async (e: Event) => {
e.preventDefault(); e.preventDefault();
setLoading(true); setLoading(true);
@@ -273,7 +267,6 @@ export default function LoginPage() {
} }
}; };
// Countdown timer render function
const renderTime = ({ remainingTime }: { remainingTime: number }) => { const renderTime = ({ remainingTime }: { remainingTime: number }) => {
return ( return (
<div class="timer"> <div class="timer">
@@ -282,7 +275,6 @@ export default function LoginPage() {
); );
}; };
// Password validation helpers
const checkForMatch = (newPassword: string, newPasswordConf: string) => { const checkForMatch = (newPassword: string, newPasswordConf: string) => {
setPasswordsMatch(newPassword === newPasswordConf); setPasswordsMatch(newPassword === newPasswordConf);
}; };

View File

@@ -11,7 +11,6 @@ export default function PasswordResetPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
// State management
const [passwordBlurred, setPasswordBlurred] = createSignal(false); const [passwordBlurred, setPasswordBlurred] = createSignal(false);
const [passwordChangeLoading, setPasswordChangeLoading] = createSignal(false); const [passwordChangeLoading, setPasswordChangeLoading] = createSignal(false);
const [passwordsMatch, setPasswordsMatch] = createSignal(false); const [passwordsMatch, setPasswordsMatch] = createSignal(false);
@@ -25,21 +24,18 @@ export default function PasswordResetPage() {
const [showPasswordInput, setShowPasswordInput] = createSignal(false); const [showPasswordInput, setShowPasswordInput] = createSignal(false);
const [showPasswordConfInput, setShowPasswordConfInput] = createSignal(false); const [showPasswordConfInput, setShowPasswordConfInput] = createSignal(false);
// Form refs
let newPasswordRef: HTMLInputElement | undefined; let newPasswordRef: HTMLInputElement | undefined;
let newPasswordConfRef: HTMLInputElement | undefined; let newPasswordConfRef: HTMLInputElement | undefined;
// Get token from URL // Get token from URL
const token = searchParams.token; const token = searchParams.token;
// Redirect to request page if no token
createEffect(() => { createEffect(() => {
if (!token) { if (!token) {
navigate("/login/request-password-reset"); navigate("/login/request-password-reset");
} }
}); });
// Form submission handler
const setNewPasswordTrigger = async (e: Event) => { const setNewPasswordTrigger = async (e: Event) => {
e.preventDefault(); e.preventDefault();
setShowRequestNewEmail(false); setShowRequestNewEmail(false);
@@ -93,7 +89,6 @@ export default function PasswordResetPage() {
} }
}; };
// Check if passwords match
const checkForMatch = (newPassword: string, newPasswordConf: string) => { const checkForMatch = (newPassword: string, newPasswordConf: string) => {
if (newPassword === newPasswordConf) { if (newPassword === newPasswordConf) {
setPasswordsMatch(true); setPasswordsMatch(true);
@@ -102,7 +97,6 @@ export default function PasswordResetPage() {
} }
}; };
// Check password length
const checkPasswordLength = (password: string) => { const checkPasswordLength = (password: string) => {
if (password.length >= 8) { if (password.length >= 8) {
setPasswordLengthSufficient(true); setPasswordLengthSufficient(true);
@@ -115,7 +109,6 @@ export default function PasswordResetPage() {
} }
}; };
// Handle password blur
const passwordLengthBlurCheck = () => { const passwordLengthBlurCheck = () => {
if ( if (
!passwordLengthSufficient() && !passwordLengthSufficient() &&
@@ -127,7 +120,6 @@ export default function PasswordResetPage() {
setPasswordBlurred(true); setPasswordBlurred(true);
}; };
// Handle new password change
const handleNewPasswordChange = (e: Event) => { const handleNewPasswordChange = (e: Event) => {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement;
checkPasswordLength(target.value); checkPasswordLength(target.value);
@@ -136,7 +128,6 @@ export default function PasswordResetPage() {
} }
}; };
// Handle password confirmation change
const handlePasswordConfChange = (e: Event) => { const handlePasswordConfChange = (e: Event) => {
const target = e.target as HTMLInputElement; const target = e.target as HTMLInputElement;
if (newPasswordRef) { if (newPasswordRef) {
@@ -144,7 +135,6 @@ export default function PasswordResetPage() {
} }
}; };
// Handle password blur
const handlePasswordBlur = () => { const handlePasswordBlur = () => {
passwordLengthBlurCheck(); passwordLengthBlurCheck();
}; };

View File

@@ -8,17 +8,14 @@ import { getClientCookie } from "~/lib/cookies.client";
export default function RequestPasswordResetPage() { export default function RequestPasswordResetPage() {
const navigate = useNavigate(); const navigate = useNavigate();
// State management
const [loading, setLoading] = createSignal(false); const [loading, setLoading] = createSignal(false);
const [countDown, setCountDown] = createSignal(0); const [countDown, setCountDown] = createSignal(0);
const [showSuccessMessage, setShowSuccessMessage] = createSignal(false); const [showSuccessMessage, setShowSuccessMessage] = createSignal(false);
const [error, setError] = createSignal(""); const [error, setError] = createSignal("");
// Form refs
let emailRef: HTMLInputElement | undefined; let emailRef: HTMLInputElement | undefined;
let timerInterval: number | undefined; let timerInterval: number | undefined;
// Calculate remaining time from cookie
const calcRemainder = (timer: string) => { const calcRemainder = (timer: string) => {
const expires = new Date(timer); const expires = new Date(timer);
const remaining = expires.getTime() - Date.now(); const remaining = expires.getTime() - Date.now();
@@ -34,7 +31,6 @@ export default function RequestPasswordResetPage() {
} }
}; };
// Check for existing timer on mount
createEffect(() => { createEffect(() => {
const timer = getClientCookie("passwordResetRequested"); const timer = getClientCookie("passwordResetRequested");
if (timer) { if (timer) {
@@ -51,7 +47,6 @@ export default function RequestPasswordResetPage() {
}); });
}); });
// Form submission handler
const requestPasswordResetTrigger = async (e: Event) => { const requestPasswordResetTrigger = async (e: Event) => {
e.preventDefault(); e.preventDefault();
setError(""); setError("");

View File

@@ -432,9 +432,8 @@ export const authRouter = createTRPCRouter({
}; };
if (rememberMe) { if (rememberMe) {
cookieOptions.maxAge = 60 * 60 * 24 * 14; // 14 days cookieOptions.maxAge = 60 * 60 * 24 * 14;
} }
// If rememberMe is false, cookie will be session-only (no maxAge)
setCookie( setCookie(
ctx.event.nativeEvent, ctx.event.nativeEvent,
@@ -591,7 +590,6 @@ export const authRouter = createTRPCRouter({
}); });
} }
// If provider is unknown/null, update it to "email" since they're logging in with password
if ( if (
!user.provider || !user.provider ||
!["email", "google", "github", "apple"].includes(user.provider) !["email", "google", "github", "apple"].includes(user.provider)
@@ -669,7 +667,6 @@ export const authRouter = createTRPCRouter({
.setExpirationTime("15m") .setExpirationTime("15m")
.sign(secret); .sign(secret);
// Send email
const domain = env.VITE_DOMAIN || "https://freno.me"; const domain = env.VITE_DOMAIN || "https://freno.me";
const htmlContent = `<html> const htmlContent = `<html>
<head> <head>
@@ -754,7 +751,6 @@ export const authRouter = createTRPCRouter({
const { email } = input; const { email } = input;
try { try {
// Check rate limiting
const requested = getCookie( const requested = getCookie(
ctx.event.nativeEvent, ctx.event.nativeEvent,
"passwordResetRequested" "passwordResetRequested"
@@ -777,20 +773,16 @@ export const authRouter = createTRPCRouter({
}); });
if (res.rows.length === 0) { if (res.rows.length === 0) {
// Don't reveal if user exists
return { success: true, message: "email sent" }; return { success: true, message: "email sent" };
} }
const user = res.rows[0] as unknown as User; const user = res.rows[0] as unknown as User;
// Create JWT token with user ID (15min expiry)
const secret = new TextEncoder().encode(env.JWT_SECRET_KEY); const secret = new TextEncoder().encode(env.JWT_SECRET_KEY);
const token = await new SignJWT({ id: user.id }) const token = await new SignJWT({ id: user.id })
.setProtectedHeader({ alg: "HS256" }) .setProtectedHeader({ alg: "HS256" })
.setExpirationTime("15m") .setExpirationTime("15m")
.sign(secret); .sign(secret);
// Send email
const domain = env.VITE_DOMAIN || "https://freno.me"; const domain = env.VITE_DOMAIN || "https://freno.me";
const htmlContent = `<html> const htmlContent = `<html>
<head> <head>
@@ -832,7 +824,6 @@ export const authRouter = createTRPCRouter({
await sendEmail(email, "password reset", htmlContent); await sendEmail(email, "password reset", htmlContent);
// Set rate limit cookie (5 minutes)
const exp = new Date(Date.now() + 5 * 60 * 1000); const exp = new Date(Date.now() + 5 * 60 * 1000);
setCookie( setCookie(
ctx.event.nativeEvent, ctx.event.nativeEvent,
@@ -870,7 +861,6 @@ export const authRouter = createTRPCRouter({
} }
}), }),
// Reset password with token
resetPassword: publicProcedure resetPassword: publicProcedure
.input( .input(
z.object({ z.object({
@@ -890,7 +880,6 @@ export const authRouter = createTRPCRouter({
} }
try { try {
// Verify JWT token
const secret = new TextEncoder().encode(env.JWT_SECRET_KEY); const secret = new TextEncoder().encode(env.JWT_SECRET_KEY);
const { payload } = await jwtVerify(token, secret); const { payload } = await jwtVerify(token, secret);
@@ -904,7 +893,6 @@ export const authRouter = createTRPCRouter({
const conn = ConnectionFactory(); const conn = ConnectionFactory();
const passwordHash = await hashPassword(newPassword); const passwordHash = await hashPassword(newPassword);
// Get user to check current provider
const userRes = await conn.execute({ const userRes = await conn.execute({
sql: "SELECT provider FROM User WHERE id = ?", sql: "SELECT provider FROM User WHERE id = ?",
args: [payload.id] args: [payload.id]
@@ -919,7 +907,6 @@ export const authRouter = createTRPCRouter({
const currentProvider = (userRes.rows[0] as any).provider; const currentProvider = (userRes.rows[0] as any).provider;
// Only update provider to "email" if it's null, undefined, or not a known OAuth provider
if ( if (
!currentProvider || !currentProvider ||
!["google", "github", "apple"].includes(currentProvider) !["google", "github", "apple"].includes(currentProvider)
@@ -929,14 +916,12 @@ export const authRouter = createTRPCRouter({
args: [passwordHash, "email", payload.id] args: [passwordHash, "email", payload.id]
}); });
} else { } else {
// Keep existing OAuth provider, just update password
await conn.execute({ await conn.execute({
sql: "UPDATE User SET password_hash = ? WHERE id = ?", sql: "UPDATE User SET password_hash = ? WHERE id = ?",
args: [passwordHash, payload.id] args: [passwordHash, payload.id]
}); });
} }
// Clear any session cookies
setCookie(ctx.event.nativeEvent, "emailToken", "", { setCookie(ctx.event.nativeEvent, "emailToken", "", {
maxAge: 0, maxAge: 0,
path: "/" path: "/"
@@ -959,14 +944,12 @@ export const authRouter = createTRPCRouter({
} }
}), }),
// Resend email verification
resendEmailVerification: publicProcedure resendEmailVerification: publicProcedure
.input(z.object({ email: z.string().email() })) .input(z.object({ email: z.string().email() }))
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { email } = input; const { email } = input;
try { try {
// Check rate limiting
const requested = getCookie( const requested = getCookie(
ctx.event.nativeEvent, ctx.event.nativeEvent,
"emailVerificationRequested" "emailVerificationRequested"
@@ -998,14 +981,12 @@ export const authRouter = createTRPCRouter({
}); });
} }
// Create JWT token (15min expiry)
const secret = new TextEncoder().encode(env.JWT_SECRET_KEY); const secret = new TextEncoder().encode(env.JWT_SECRET_KEY);
const token = await new SignJWT({ email }) const token = await new SignJWT({ email })
.setProtectedHeader({ alg: "HS256" }) .setProtectedHeader({ alg: "HS256" })
.setExpirationTime("15m") .setExpirationTime("15m")
.sign(secret); .sign(secret);
// Send email
const domain = env.VITE_DOMAIN || "https://freno.me"; const domain = env.VITE_DOMAIN || "https://freno.me";
const htmlContent = `<html> const htmlContent = `<html>
<head> <head>
@@ -1044,7 +1025,6 @@ export const authRouter = createTRPCRouter({
await sendEmail(email, "freno.me email verification", htmlContent); await sendEmail(email, "freno.me email verification", htmlContent);
// Set rate limit cookie
setCookie( setCookie(
ctx.event.nativeEvent, ctx.event.nativeEvent,
"emailVerificationRequested", "emailVerificationRequested",
@@ -1081,7 +1061,6 @@ export const authRouter = createTRPCRouter({
} }
}), }),
// Sign out
signOut: publicProcedure.mutation(async ({ ctx }) => { signOut: publicProcedure.mutation(async ({ ctx }) => {
setCookie(ctx.event.nativeEvent, "userIDToken", "", { setCookie(ctx.event.nativeEvent, "userIDToken", "", {
maxAge: 0, maxAge: 0,

View File

@@ -162,7 +162,6 @@ export const databaseRouter = createTRPCRouter({
requestingUser: ctx.userId requestingUser: ctx.userId
}); });
// User can only delete their own comments with "user" type
if (input.deletionType === "user" && !isOwner && !isAdmin) { if (input.deletionType === "user" && !isOwner && !isAdmin) {
throw new TRPCError({ throw new TRPCError({
code: "FORBIDDEN", code: "FORBIDDEN",
@@ -170,7 +169,6 @@ export const databaseRouter = createTRPCRouter({
}); });
} }
// Only admins can do admin or database deletion
if ( if (
(input.deletionType === "admin" || (input.deletionType === "admin" ||
input.deletionType === "database") && input.deletionType === "database") &&
@@ -184,14 +182,11 @@ export const databaseRouter = createTRPCRouter({
if (input.deletionType === "database") { if (input.deletionType === "database") {
console.log("[deleteComment] Performing database deletion"); console.log("[deleteComment] Performing database deletion");
// Full deletion - remove from database
// First delete reactions
await conn.execute({ await conn.execute({
sql: "DELETE FROM CommentReaction WHERE comment_id = ?", sql: "DELETE FROM CommentReaction WHERE comment_id = ?",
args: [input.commentID] args: [input.commentID]
}); });
// Then delete the comment
await conn.execute({ await conn.execute({
sql: "DELETE FROM Comment WHERE id = ?", sql: "DELETE FROM Comment WHERE id = ?",
args: [input.commentID] args: [input.commentID]
@@ -205,7 +200,6 @@ export const databaseRouter = createTRPCRouter({
}; };
} else if (input.deletionType === "admin") { } else if (input.deletionType === "admin") {
console.log("[deleteComment] Performing admin deletion"); console.log("[deleteComment] Performing admin deletion");
// Admin delete - replace body with admin message
await conn.execute({ await conn.execute({
sql: "UPDATE Comment SET body = ?, commenter_id = ? WHERE id = ?", sql: "UPDATE Comment SET body = ?, commenter_id = ? WHERE id = ?",
args: ["[deleted by admin]", "", input.commentID] args: ["[deleted by admin]", "", input.commentID]
@@ -219,7 +213,6 @@ export const databaseRouter = createTRPCRouter({
}; };
} else { } else {
console.log("[deleteComment] Performing user deletion"); console.log("[deleteComment] Performing user deletion");
// User delete - replace body with user message
await conn.execute({ await conn.execute({
sql: "UPDATE Comment SET body = ?, commenter_id = ? WHERE id = ?", sql: "UPDATE Comment SET body = ?, commenter_id = ? WHERE id = ?",
args: ["[deleted]", "", input.commentID] args: ["[deleted]", "", input.commentID]
@@ -249,7 +242,6 @@ export const databaseRouter = createTRPCRouter({
.query(async ({ input }) => { .query(async ({ input }) => {
try { try {
const conn = ConnectionFactory(); const conn = ConnectionFactory();
// Join with Post table to get post titles along with comments
const query = ` const query = `
SELECT c.*, p.title as post_title SELECT c.*, p.title as post_title
FROM Comment c FROM Comment c
@@ -270,10 +262,6 @@ export const databaseRouter = createTRPCRouter({
} }
}), }),
// ============================================================
// Post Routes
// ============================================================
getPostById: publicProcedure getPostById: publicProcedure
.input( .input(
z.object({ z.object({
@@ -288,7 +276,6 @@ export const databaseRouter = createTRPCRouter({
async () => { async () => {
try { try {
const conn = ConnectionFactory(); const conn = ConnectionFactory();
// Single query with JOIN to get post and tags in one go
const query = ` const query = `
SELECT p.*, t.value as tag_value SELECT p.*, t.value as tag_value
FROM Post p FROM Post p
@@ -301,7 +288,6 @@ export const databaseRouter = createTRPCRouter({
}); });
if (results.rows[0]) { if (results.rows[0]) {
// Group tags by post ID
const post = results.rows[0]; const post = results.rows[0];
const tags = results.rows const tags = results.rows
.filter((row) => row.tag_value) .filter((row) => row.tag_value)
@@ -339,7 +325,6 @@ export const databaseRouter = createTRPCRouter({
try { try {
const conn = ConnectionFactory(); const conn = ConnectionFactory();
// Get post by title with JOINs to get all related data in one query
const postQuery = ` const postQuery = `
SELECT SELECT
p.*, p.*,
@@ -364,7 +349,6 @@ export const databaseRouter = createTRPCRouter({
const postRow = postResults.rows[0]; const postRow = postResults.rows[0];
// Return structured data with proper formatting
return { return {
post: postRow, post: postRow,
comments: [], // Comments are not included in this optimized query - would need separate call if needed comments: [], // Comments are not included in this optimized query - would need separate call if needed
@@ -426,7 +410,6 @@ export const databaseRouter = createTRPCRouter({
await conn.execute(tagQuery); await conn.execute(tagQuery);
} }
// Invalidate blog cache
cache.deleteByPrefix("blog-"); cache.deleteByPrefix("blog-");
return { data: results.lastInsertRowid }; return { data: results.lastInsertRowid };
@@ -502,7 +485,6 @@ export const databaseRouter = createTRPCRouter({
const results = await conn.execute({ sql: query, args: params }); const results = await conn.execute({ sql: query, args: params });
// Handle tags
const deleteTagsQuery = `DELETE FROM Tag WHERE post_id = ?`; const deleteTagsQuery = `DELETE FROM Tag WHERE post_id = ?`;
await conn.execute({ await conn.execute({
sql: deleteTagsQuery, sql: deleteTagsQuery,
@@ -516,7 +498,6 @@ export const databaseRouter = createTRPCRouter({
await conn.execute(tagQuery); await conn.execute(tagQuery);
} }
// Invalidate blog cache
cache.deleteByPrefix("blog-"); cache.deleteByPrefix("blog-");
return { data: results.lastInsertRowid }; return { data: results.lastInsertRowid };
@@ -535,31 +516,26 @@ export const databaseRouter = createTRPCRouter({
try { try {
const conn = ConnectionFactory(); const conn = ConnectionFactory();
// Delete associated tags first
await conn.execute({ await conn.execute({
sql: "DELETE FROM Tag WHERE post_id = ?", sql: "DELETE FROM Tag WHERE post_id = ?",
args: [input.id.toString()] args: [input.id.toString()]
}); });
// Delete associated likes
await conn.execute({ await conn.execute({
sql: "DELETE FROM PostLike WHERE post_id = ?", sql: "DELETE FROM PostLike WHERE post_id = ?",
args: [input.id.toString()] args: [input.id.toString()]
}); });
// Delete associated comments
await conn.execute({ await conn.execute({
sql: "DELETE FROM Comment WHERE post_id = ?", sql: "DELETE FROM Comment WHERE post_id = ?",
args: [input.id] args: [input.id]
}); });
// Finally delete the post
await conn.execute({ await conn.execute({
sql: "DELETE FROM Post WHERE id = ?", sql: "DELETE FROM Post WHERE id = ?",
args: [input.id] args: [input.id]
}); });
// Invalidate blog cache
cache.deleteByPrefix("blog-"); cache.deleteByPrefix("blog-");
return { success: true }; return { success: true };

View File

@@ -10,7 +10,6 @@ import {
APIError APIError
} from "~/server/fetch-utils"; } from "~/server/fetch-utils";
// Types for commits
interface GitCommit { interface GitCommit {
sha: string; sha: string;
message: string; message: string;
@@ -26,7 +25,6 @@ interface ContributionDay {
} }
export const gitActivityRouter = createTRPCRouter({ export const gitActivityRouter = createTRPCRouter({
// Get recent commits from GitHub
getGitHubCommits: publicProcedure getGitHubCommits: publicProcedure
.input(z.object({ limit: z.number().default(3) })) .input(z.object({ limit: z.number().default(3) }))
.query(async ({ input }) => { .query(async ({ input }) => {
@@ -34,7 +32,6 @@ export const gitActivityRouter = createTRPCRouter({
`github-commits-${input.limit}`, `github-commits-${input.limit}`,
10 * 60 * 1000, // 10 minutes 10 * 60 * 1000, // 10 minutes
async () => { async () => {
// Get user's repositories sorted by most recently pushed
const reposResponse = await fetchWithTimeout( const reposResponse = await fetchWithTimeout(
`https://api.github.com/users/MikeFreno/repos?sort=pushed&per_page=10`, `https://api.github.com/users/MikeFreno/repos?sort=pushed&per_page=10`,
{ {
@@ -50,7 +47,6 @@ export const gitActivityRouter = createTRPCRouter({
const repos = await reposResponse.json(); const repos = await reposResponse.json();
const allCommits: GitCommit[] = []; const allCommits: GitCommit[] = [];
// Fetch recent commits from each repo
for (const repo of repos) { for (const repo of repos) {
if (allCommits.length >= input.limit * 3) break; // Get extra to sort later if (allCommits.length >= input.limit * 3) break; // Get extra to sort later
@@ -69,7 +65,6 @@ export const gitActivityRouter = createTRPCRouter({
if (commitsResponse.ok) { if (commitsResponse.ok) {
const commits = await commitsResponse.json(); const commits = await commitsResponse.json();
for (const commit of commits) { for (const commit of commits) {
// Filter for commits by the authenticated user
if ( if (
commit.author?.login === "MikeFreno" || commit.author?.login === "MikeFreno" ||
commit.commit?.author?.email?.includes("mike") commit.commit?.author?.email?.includes("mike")
@@ -91,7 +86,6 @@ export const gitActivityRouter = createTRPCRouter({
} }
} }
} catch (error) { } catch (error) {
// Log individual repo failures but continue with others
if ( if (
error instanceof NetworkError || error instanceof NetworkError ||
error instanceof TimeoutError error instanceof TimeoutError
@@ -108,7 +102,6 @@ export const gitActivityRouter = createTRPCRouter({
} }
} }
// Sort by date and return the most recent
allCommits.sort( allCommits.sort(
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime() (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
); );
@@ -117,7 +110,6 @@ export const gitActivityRouter = createTRPCRouter({
}, },
{ maxStaleMs: 24 * 60 * 60 * 1000 } // Accept stale data up to 24 hours old { maxStaleMs: 24 * 60 * 60 * 1000 } // Accept stale data up to 24 hours old
).catch((error) => { ).catch((error) => {
// Final fallback - return empty array if everything fails
if (error instanceof NetworkError) { if (error instanceof NetworkError) {
console.error("GitHub API unavailable (network error)"); console.error("GitHub API unavailable (network error)");
} else if (error instanceof TimeoutError) { } else if (error instanceof TimeoutError) {
@@ -133,7 +125,6 @@ export const gitActivityRouter = createTRPCRouter({
}); });
}), }),
// Get recent commits from Gitea
getGiteaCommits: publicProcedure getGiteaCommits: publicProcedure
.input(z.object({ limit: z.number().default(3) })) .input(z.object({ limit: z.number().default(3) }))
.query(async ({ input }) => { .query(async ({ input }) => {
@@ -141,7 +132,6 @@ export const gitActivityRouter = createTRPCRouter({
`gitea-commits-${input.limit}`, `gitea-commits-${input.limit}`,
10 * 60 * 1000, // 10 minutes 10 * 60 * 1000, // 10 minutes
async () => { async () => {
// First, get user's repositories
const reposResponse = await fetchWithTimeout( const reposResponse = await fetchWithTimeout(
`${env.GITEA_URL}/api/v1/users/Mike/repos?limit=100`, `${env.GITEA_URL}/api/v1/users/Mike/repos?limit=100`,
{ {
@@ -157,7 +147,6 @@ export const gitActivityRouter = createTRPCRouter({
const repos = await reposResponse.json(); const repos = await reposResponse.json();
const allCommits: GitCommit[] = []; const allCommits: GitCommit[] = [];
// Fetch recent commits from each repo
for (const repo of repos) { for (const repo of repos) {
if (allCommits.length >= input.limit * 3) break; // Get extra to sort later if (allCommits.length >= input.limit * 3) break; // Get extra to sort later
@@ -199,7 +188,6 @@ export const gitActivityRouter = createTRPCRouter({
} }
} }
} catch (error) { } catch (error) {
// Log individual repo failures but continue with others
if ( if (
error instanceof NetworkError || error instanceof NetworkError ||
error instanceof TimeoutError error instanceof TimeoutError
@@ -216,7 +204,6 @@ export const gitActivityRouter = createTRPCRouter({
} }
} }
// Sort by date and return the most recent
allCommits.sort( allCommits.sort(
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime() (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
); );
@@ -225,7 +212,6 @@ export const gitActivityRouter = createTRPCRouter({
}, },
{ maxStaleMs: 24 * 60 * 60 * 1000 } { maxStaleMs: 24 * 60 * 60 * 1000 }
).catch((error) => { ).catch((error) => {
// Final fallback - return empty array if everything fails
if (error instanceof NetworkError) { if (error instanceof NetworkError) {
console.error("Gitea API unavailable (network error)"); console.error("Gitea API unavailable (network error)");
} else if (error instanceof TimeoutError) { } else if (error instanceof TimeoutError) {
@@ -245,7 +231,6 @@ export const gitActivityRouter = createTRPCRouter({
"github-activity", "github-activity",
10 * 60 * 1000, 10 * 60 * 1000,
async () => { async () => {
// Use GitHub GraphQL API for contribution data
const query = ` const query = `
query($userName: String!) { query($userName: String!) {
user(login: $userName) { user(login: $userName) {
@@ -287,7 +272,6 @@ export const gitActivityRouter = createTRPCRouter({
throw new APIError("GraphQL query failed", 500, "GraphQL Error"); throw new APIError("GraphQL query failed", 500, "GraphQL Error");
} }
// Extract contribution days from the response
const contributions: ContributionDay[] = []; const contributions: ContributionDay[] = [];
const weeks = const weeks =
data.data?.user?.contributionsCollection?.contributionCalendar data.data?.user?.contributionsCollection?.contributionCalendar
@@ -327,7 +311,6 @@ export const gitActivityRouter = createTRPCRouter({
"gitea-activity", "gitea-activity",
10 * 60 * 1000, 10 * 60 * 1000,
async () => { async () => {
// Get user's repositories
const reposResponse = await fetchWithTimeout( const reposResponse = await fetchWithTimeout(
`${env.GITEA_URL}/api/v1/user/repos?limit=100`, `${env.GITEA_URL}/api/v1/user/repos?limit=100`,
{ {
@@ -343,7 +326,6 @@ export const gitActivityRouter = createTRPCRouter({
const repos = await reposResponse.json(); const repos = await reposResponse.json();
const contributionsByDay = new Map<string, number>(); const contributionsByDay = new Map<string, number>();
// Get commits from each repo (last 3 months to avoid too many API calls)
const threeMonthsAgo = new Date(); const threeMonthsAgo = new Date();
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3); threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3);
@@ -373,7 +355,6 @@ export const gitActivityRouter = createTRPCRouter({
} }
} }
} catch (error) { } catch (error) {
// Log individual repo failures but continue with others
if ( if (
error instanceof NetworkError || error instanceof NetworkError ||
error instanceof TimeoutError error instanceof TimeoutError
@@ -387,7 +368,6 @@ export const gitActivityRouter = createTRPCRouter({
} }
} }
// Convert to array format
const contributions: ContributionDay[] = Array.from( const contributions: ContributionDay[] = Array.from(
contributionsByDay.entries() contributionsByDay.entries()
).map(([date, count]) => ({ date, count })); ).map(([date, count]) => ({ date, count }));

View File

@@ -47,7 +47,6 @@ import potions from "~/lineage-json/item-route/potions.json";
import poison from "~/lineage-json/item-route/poison.json"; import poison from "~/lineage-json/item-route/poison.json";
import staves from "~/lineage-json/item-route/staves.json"; import staves from "~/lineage-json/item-route/staves.json";
// Misc data imports
import activities from "~/lineage-json/misc-route/activities.json"; import activities from "~/lineage-json/misc-route/activities.json";
import investments from "~/lineage-json/misc-route/investments.json"; import investments from "~/lineage-json/misc-route/investments.json";
import jobs from "~/lineage-json/misc-route/jobs.json"; import jobs from "~/lineage-json/misc-route/jobs.json";

View File

@@ -96,7 +96,6 @@ export const miscRouter = createTRPCRouter({
credentials: credentials credentials: credentials
}); });
// Sanitize the title and filename for S3 key (replace spaces with hyphens, remove special chars)
const sanitizeForS3 = (str: string) => { const sanitizeForS3 = (str: string) => {
return str return str
.replace(/\s+/g, "-") // Replace spaces with hyphens .replace(/\s+/g, "-") // Replace spaces with hyphens
@@ -262,7 +261,6 @@ export const miscRouter = createTRPCRouter({
}) })
) )
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
// Check if contact request was recently sent
const contactExp = getCookie("contactRequestSent"); const contactExp = getCookie("contactRequestSent");
let remaining = 0; let remaining = 0;
@@ -314,7 +312,6 @@ export const miscRouter = createTRPCRouter({
} }
); );
// Set cookie to prevent spam (60 second cooldown)
const exp = new Date(Date.now() + 1 * 60 * 1000); const exp = new Date(Date.now() + 1 * 60 * 1000);
setCookie("contactRequestSent", exp.toUTCString(), { setCookie("contactRequestSent", exp.toUTCString(), {
expires: exp, expires: exp,
@@ -365,7 +362,6 @@ export const miscRouter = createTRPCRouter({
sendDeletionRequestEmail: publicProcedure sendDeletionRequestEmail: publicProcedure
.input(z.object({ email: z.string().email() })) .input(z.object({ email: z.string().email() }))
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
// Check if deletion request was recently sent
const deletionExp = getCookie("deletionRequestSent"); const deletionExp = getCookie("deletionRequestSent");
let remaining = 0; let remaining = 0;
@@ -445,7 +441,6 @@ export const miscRouter = createTRPCRouter({
) )
]); ]);
// Set cookie to prevent spam (60 second cooldown)
const exp = new Date(Date.now() + 1 * 60 * 1000); const exp = new Date(Date.now() + 1 * 60 * 1000);
setCookie("deletionRequestSent", exp.toUTCString(), { setCookie("deletionRequestSent", exp.toUTCString(), {
expires: exp, expires: exp,

View File

@@ -13,7 +13,6 @@ import type { User } from "~/types/user";
import { toUserProfile } from "~/types/user"; import { toUserProfile } from "~/types/user";
export const userRouter = createTRPCRouter({ export const userRouter = createTRPCRouter({
// Get current user profile
getProfile: publicProcedure.query(async ({ ctx }) => { getProfile: publicProcedure.query(async ({ ctx }) => {
const userId = await getUserID(ctx.event.nativeEvent); const userId = await getUserID(ctx.event.nativeEvent);
@@ -41,7 +40,6 @@ export const userRouter = createTRPCRouter({
return toUserProfile(user); return toUserProfile(user);
}), }),
// Update email
updateEmail: publicProcedure updateEmail: publicProcedure
.input(z.object({ email: z.string().email() })) .input(z.object({ email: z.string().email() }))
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@@ -62,7 +60,6 @@ export const userRouter = createTRPCRouter({
args: [email, 0, userId] args: [email, 0, userId]
}); });
// Fetch updated user
const res = await conn.execute({ const res = await conn.execute({
sql: "SELECT * FROM User WHERE id = ?", sql: "SELECT * FROM User WHERE id = ?",
args: [userId] args: [userId]
@@ -70,7 +67,6 @@ export const userRouter = createTRPCRouter({
const user = res.rows[0] as unknown as User; const user = res.rows[0] as unknown as User;
// Set email cookie for verification flow
setCookie(ctx.event.nativeEvent, "emailToken", email, { setCookie(ctx.event.nativeEvent, "emailToken", email, {
path: "/" path: "/"
}); });
@@ -78,7 +74,6 @@ export const userRouter = createTRPCRouter({
return toUserProfile(user); return toUserProfile(user);
}), }),
// Update display name
updateDisplayName: publicProcedure updateDisplayName: publicProcedure
.input(z.object({ displayName: z.string().min(1).max(50) })) .input(z.object({ displayName: z.string().min(1).max(50) }))
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@@ -99,7 +94,6 @@ export const userRouter = createTRPCRouter({
args: [displayName, userId] args: [displayName, userId]
}); });
// Fetch updated user
const res = await conn.execute({ const res = await conn.execute({
sql: "SELECT * FROM User WHERE id = ?", sql: "SELECT * FROM User WHERE id = ?",
args: [userId] args: [userId]
@@ -109,7 +103,6 @@ export const userRouter = createTRPCRouter({
return toUserProfile(user); return toUserProfile(user);
}), }),
// Update profile image
updateProfileImage: publicProcedure updateProfileImage: publicProcedure
.input(z.object({ imageUrl: z.string() })) .input(z.object({ imageUrl: z.string() }))
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@@ -130,7 +123,6 @@ export const userRouter = createTRPCRouter({
args: [imageUrl, userId] args: [imageUrl, userId]
}); });
// Fetch updated user
const res = await conn.execute({ const res = await conn.execute({
sql: "SELECT * FROM User WHERE id = ?", sql: "SELECT * FROM User WHERE id = ?",
args: [userId] args: [userId]
@@ -140,7 +132,6 @@ export const userRouter = createTRPCRouter({
return toUserProfile(user); return toUserProfile(user);
}), }),
// Change password (requires old password)
changePassword: publicProcedure changePassword: publicProcedure
.input( .input(
z.object({ z.object({
@@ -202,14 +193,12 @@ export const userRouter = createTRPCRouter({
}); });
} }
// Update password
const newPasswordHash = await hashPassword(newPassword); const newPasswordHash = await hashPassword(newPassword);
await conn.execute({ await conn.execute({
sql: "UPDATE User SET password_hash = ? WHERE id = ?", sql: "UPDATE User SET password_hash = ? WHERE id = ?",
args: [newPasswordHash, userId] args: [newPasswordHash, userId]
}); });
// Clear session cookies (force re-login)
setCookie(ctx.event.nativeEvent, "emailToken", "", { setCookie(ctx.event.nativeEvent, "emailToken", "", {
maxAge: 0, maxAge: 0,
path: "/" path: "/"
@@ -222,7 +211,6 @@ export const userRouter = createTRPCRouter({
return { success: true, message: "success" }; return { success: true, message: "success" };
}), }),
// Set password (for OAuth users who don't have password)
setPassword: publicProcedure setPassword: publicProcedure
.input( .input(
z.object({ z.object({
@@ -271,14 +259,12 @@ export const userRouter = createTRPCRouter({
}); });
} }
// Set password
const passwordHash = await hashPassword(newPassword); const passwordHash = await hashPassword(newPassword);
await conn.execute({ await conn.execute({
sql: "UPDATE User SET password_hash = ? WHERE id = ?", sql: "UPDATE User SET password_hash = ? WHERE id = ?",
args: [passwordHash, userId] args: [passwordHash, userId]
}); });
// Clear session cookies (force re-login)
setCookie(ctx.event.nativeEvent, "emailToken", "", { setCookie(ctx.event.nativeEvent, "emailToken", "", {
maxAge: 0, maxAge: 0,
path: "/" path: "/"
@@ -291,7 +277,6 @@ export const userRouter = createTRPCRouter({
return { success: true, message: "success" }; return { success: true, message: "success" };
}), }),
// Delete account (anonymize data)
deleteAccount: publicProcedure deleteAccount: publicProcedure
.input(z.object({ password: z.string() })) .input(z.object({ password: z.string() }))
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@@ -337,7 +322,6 @@ export const userRouter = createTRPCRouter({
}); });
} }
// Anonymize user data (don't hard delete)
await conn.execute({ await conn.execute({
sql: `UPDATE User SET sql: `UPDATE User SET
email = ?, email = ?,
@@ -350,7 +334,6 @@ export const userRouter = createTRPCRouter({
args: [null, 0, null, "user deleted", null, null, userId] args: [null, 0, null, "user deleted", null, null, userId]
}); });
// Clear session cookies
setCookie(ctx.event.nativeEvent, "emailToken", "", { setCookie(ctx.event.nativeEvent, "emailToken", "", {
maxAge: 0, maxAge: 0,
path: "/" path: "/"

View File

@@ -50,7 +50,6 @@ export const t = initTRPC.context<Context>().create();
export const createTRPCRouter = t.router; export const createTRPCRouter = t.router;
export const publicProcedure = t.procedure; export const publicProcedure = t.procedure;
// Middleware to enforce authentication
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => { const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.userId || ctx.privilegeLevel === "anonymous") { if (!ctx.userId || ctx.privilegeLevel === "anonymous") {
throw new TRPCError({ code: "UNAUTHORIZED", message: "Not authenticated" }); throw new TRPCError({ code: "UNAUTHORIZED", message: "Not authenticated" });
@@ -63,7 +62,6 @@ const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
}); });
}); });
// Middleware to enforce admin access
const enforceUserIsAdmin = t.middleware(({ ctx, next }) => { const enforceUserIsAdmin = t.middleware(({ ctx, next }) => {
if (ctx.privilegeLevel !== "admin") { if (ctx.privilegeLevel !== "admin") {
throw new TRPCError({ throw new TRPCError({

View File

@@ -1,5 +1,4 @@
// Manual test file for fetch-utils error handling // Manual test file for fetch-utils error handling
// Run with: bun run src/server/fetch-utils.test.ts
import { import {
fetchWithTimeout, fetchWithTimeout,