diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx index 27e7deb..33e629b 100644 --- a/src/components/ui/Button.tsx +++ b/src/components/ui/Button.tsx @@ -7,9 +7,6 @@ export interface ButtonProps extends JSX.ButtonHTMLAttributes fullWidth?: boolean; } -/** - * Reusable button component with variants and loading state - */ export default function Button(props: ButtonProps) { const [local, others] = splitProps(props, [ "variant", @@ -18,13 +15,14 @@ export default function Button(props: ButtonProps) { "fullWidth", "class", "children", - "disabled", + "disabled" ]); const variant = () => local.variant || "primary"; const size = () => local.size || "md"; - const baseClasses = "flex justify-center items-center rounded font-semibold transition-all duration-300 ease-out disabled:opacity-50 disabled:cursor-not-allowed"; + const baseClasses = + "flex justify-center items-center rounded font-semibold transition-all duration-300 ease-out disabled:opacity-50 disabled:cursor-not-allowed"; const variantClasses = () => { switch (variant()) { @@ -64,7 +62,7 @@ export default function Button(props: ButtonProps) { > { helperText?: string; } -/** - * Reusable input component with label and error handling - * Styled to match Next.js migration source (underlined input style) - */ export default function Input(props: InputProps) { - const [local, others] = splitProps(props, ["label", "error", "helperText", "class"]); + const [local, others] = splitProps(props, [ + "label", + "error", + "helperText", + "class" + ]); return (
@@ -23,22 +24,18 @@ export default function Input(props: InputProps) { aria-describedby={local.error ? `${others.id}-error` : undefined} /> - {local.label && ( - - )} + {local.label && } {local.error && ( {local.error} )} {local.helperText && !local.error && ( - - {local.helperText} - + {local.helperText} )}
); diff --git a/src/routes/blog/edit/[id].tsx b/src/routes/blog/edit/[id].tsx index 1b03874..995c8fb 100644 --- a/src/routes/blog/edit/[id].tsx +++ b/src/routes/blog/edit/[id].tsx @@ -48,7 +48,7 @@ export default function EditPost() { subtitle: p.subtitle || "", body: p.body || "", banner_photo: p.banner_photo || "", - published: p.published || false, + published: Boolean(p.published), tags: tagValues, attachments: p.attachments }; diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index 843870e..634be22 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -16,7 +16,6 @@ import { APIError } from "~/server/fetch-utils"; -// Helper to create JWT token async function createJWT( userId: string, expiresIn: string = "14d" @@ -29,7 +28,6 @@ async function createJWT( return token; } -// Helper to send email via Brevo/SendInBlue with retry logic async function sendEmail(to: string, subject: string, htmlContent: string) { const apiKey = env.SENDINBLUE_KEY; const apiUrl = "https://api.sendinblue.com/v3/smtp/email"; @@ -68,14 +66,12 @@ async function sendEmail(to: string, subject: string, htmlContent: string) { } export const authRouter = createTRPCRouter({ - // GitHub callback route githubCallback: publicProcedure .input(z.object({ code: z.string() })) .mutation(async ({ input, ctx }) => { const { code } = input; try { - // Exchange code for access token with timeout const tokenResponse = await fetchWithTimeout( "https://github.com/login/oauth/access_token", { @@ -103,7 +99,6 @@ export const authRouter = createTRPCRouter({ }); } - // Fetch user info from GitHub with timeout const userResponse = await fetchWithTimeout( "https://api.github.com/user", { @@ -119,7 +114,6 @@ export const authRouter = createTRPCRouter({ const login = user.login; const icon = user.avatar_url; - // Fetch primary email from GitHub emails endpoint const emailsResponse = await fetchWithTimeout( "https://api.github.com/user/emails", { @@ -133,7 +127,6 @@ export const authRouter = createTRPCRouter({ await checkResponse(emailsResponse); const emails = await emailsResponse.json(); - // Find primary verified email const primaryEmail = emails.find( (e: { primary: boolean; verified: boolean; email: string }) => e.primary && e.verified @@ -143,7 +136,6 @@ export const authRouter = createTRPCRouter({ const conn = ConnectionFactory(); - // Check if user exists const query = `SELECT * FROM User WHERE provider = ? AND display_name = ?`; const params = ["github", login]; const res = await conn.execute({ sql: query, args: params }); @@ -151,7 +143,6 @@ export const authRouter = createTRPCRouter({ let userId: string; if (res.rows[0]) { - // User exists - update email and image if changed userId = (res.rows[0] as unknown as User).id; try { @@ -160,7 +151,6 @@ export const authRouter = createTRPCRouter({ args: [email, emailVerified ? 1 : 0, icon, userId] }); } catch (updateError: any) { - // Handle unique constraint error on email if ( updateError.code === "SQLITE_CONSTRAINT" && updateError.message?.includes("User.email") @@ -174,7 +164,6 @@ export const authRouter = createTRPCRouter({ throw updateError; } } else { - // Create new user userId = uuidV4(); const insertQuery = `INSERT INTO User (id, email, email_verified, display_name, provider, image) VALUES (?, ?, ?, ?, ?, ?)`; @@ -190,7 +179,6 @@ export const authRouter = createTRPCRouter({ try { await conn.execute({ sql: insertQuery, args: insertParams }); } catch (insertError: any) { - // Handle unique constraint error on email if ( insertError.code === "SQLITE_CONSTRAINT" && insertError.message?.includes("User.email") @@ -205,10 +193,8 @@ export const authRouter = createTRPCRouter({ } } - // Create JWT token const token = await createJWT(userId); - // Set cookie setCookie(ctx.event.nativeEvent, "userIDToken", token, { maxAge: 60 * 60 * 24 * 14, // 14 days path: "/", @@ -226,7 +212,6 @@ export const authRouter = createTRPCRouter({ throw error; } - // Provide specific error messages for different failure types if (error instanceof TimeoutError) { console.error("GitHub API timeout:", error.message); throw new TRPCError({ @@ -255,14 +240,12 @@ export const authRouter = createTRPCRouter({ } }), - // Google callback route googleCallback: publicProcedure .input(z.object({ code: z.string() })) .mutation(async ({ input, ctx }) => { const { code } = input; try { - // Exchange code for access token with timeout const tokenResponse = await fetchWithTimeout( "https://oauth2.googleapis.com/token", { @@ -291,7 +274,6 @@ export const authRouter = createTRPCRouter({ }); } - // Fetch user info from Google with timeout const userResponse = await fetchWithTimeout( "https://www.googleapis.com/oauth2/v3/userinfo", { @@ -311,7 +293,6 @@ export const authRouter = createTRPCRouter({ const conn = ConnectionFactory(); - // Check if user exists const query = `SELECT * FROM User WHERE provider = ? AND email = ?`; const params = ["google", email]; const res = await conn.execute({ sql: query, args: params }); @@ -319,16 +300,13 @@ export const authRouter = createTRPCRouter({ let userId: string; if (res.rows[0]) { - // User exists - update email, email_verified, display_name, and image if changed userId = (res.rows[0] as unknown as User).id; - // No need to catch constraint error here since we're updating the same user's record await conn.execute({ sql: `UPDATE User SET email = ?, email_verified = ?, display_name = ?, image = ? WHERE id = ?`, args: [email, email_verified ? 1 : 0, name, image, userId] }); } else { - // Create new user userId = uuidV4(); const insertQuery = `INSERT INTO User (id, email, email_verified, display_name, provider, image) VALUES (?, ?, ?, ?, ?, ?)`; @@ -347,7 +325,6 @@ export const authRouter = createTRPCRouter({ args: insertParams }); } catch (insertError: any) { - // Handle unique constraint error on email if ( insertError.code === "SQLITE_CONSTRAINT" && insertError.message?.includes("User.email") @@ -362,10 +339,8 @@ export const authRouter = createTRPCRouter({ } } - // Create JWT token const token = await createJWT(userId); - // Set cookie setCookie(ctx.event.nativeEvent, "userIDToken", token, { maxAge: 60 * 60 * 24 * 14, // 14 days path: "/", @@ -383,7 +358,6 @@ export const authRouter = createTRPCRouter({ throw error; } - // Provide specific error messages for different failure types if (error instanceof TimeoutError) { console.error("Google API timeout:", error.message); throw new TRPCError({ @@ -412,7 +386,6 @@ export const authRouter = createTRPCRouter({ } }), - // Email login route emailLogin: publicProcedure .input( z.object({ @@ -425,11 +398,9 @@ export const authRouter = createTRPCRouter({ const { email, token, rememberMe } = input; try { - // Verify JWT token const secret = new TextEncoder().encode(env.JWT_SECRET_KEY); const { payload } = await jwtVerify(token, secret); - // Check if email matches if (payload.email !== email) { throw new TRPCError({ code: "UNAUTHORIZED", @@ -451,10 +422,8 @@ export const authRouter = createTRPCRouter({ const userId = (res.rows[0] as unknown as User).id; - // Create JWT token const userToken = await createJWT(userId); - // Set cookie based on rememberMe flag const cookieOptions: any = { path: "/", httpOnly: true, @@ -490,7 +459,6 @@ export const authRouter = createTRPCRouter({ } }), - // Email verification route emailVerification: publicProcedure .input( z.object({ @@ -502,11 +470,9 @@ export const authRouter = createTRPCRouter({ const { email, token } = input; try { - // Verify JWT token const secret = new TextEncoder().encode(env.JWT_SECRET_KEY); const { payload } = await jwtVerify(token, secret); - // Check if email matches if (payload.email !== email) { throw new TRPCError({ code: "UNAUTHORIZED", @@ -535,7 +501,6 @@ export const authRouter = createTRPCRouter({ } }), - // Email/password registration emailRegistration: publicProcedure .input( z.object({ @@ -564,10 +529,8 @@ export const authRouter = createTRPCRouter({ args: [userId, email, passwordHash, "email"] }); - // Create JWT token const token = await createJWT(userId); - // Set cookie setCookie(ctx.event.nativeEvent, "userIDToken", token, { maxAge: 60 * 60 * 24 * 14, // 14 days path: "/", @@ -586,7 +549,6 @@ export const authRouter = createTRPCRouter({ } }), - // Email/password login emailPasswordLogin: publicProcedure .input( z.object({ @@ -640,11 +602,9 @@ export const authRouter = createTRPCRouter({ }); } - // Create JWT token with appropriate expiry const expiresIn = rememberMe ? "14d" : "12h"; const token = await createJWT(user.id, expiresIn); - // Set cookie const cookieOptions: any = { path: "/", httpOnly: true, @@ -661,7 +621,6 @@ export const authRouter = createTRPCRouter({ return { success: true, message: "success" }; }), - // Request email login link requestEmailLinkLogin: publicProcedure .input( z.object({ @@ -673,7 +632,6 @@ export const authRouter = createTRPCRouter({ const { email, rememberMe } = input; try { - // Check rate limiting const requested = getCookie( ctx.event.nativeEvent, "emailLoginLinkRequested" @@ -702,7 +660,6 @@ export const authRouter = createTRPCRouter({ }); } - // Create JWT token for email link (15min expiry) const secret = new TextEncoder().encode(env.JWT_SECRET_KEY); const token = await new SignJWT({ email, @@ -754,7 +711,6 @@ export const authRouter = createTRPCRouter({ await sendEmail(email, "freno.me login link", htmlContent); - // Set rate limit cookie (2 minutes) const exp = new Date(Date.now() + 2 * 60 * 1000); setCookie( ctx.event.nativeEvent, @@ -772,7 +728,6 @@ export const authRouter = createTRPCRouter({ throw error; } - // Handle email sending failures gracefully if ( error instanceof TimeoutError || error instanceof NetworkError || @@ -793,7 +748,6 @@ export const authRouter = createTRPCRouter({ } }), - // Request password reset requestPasswordReset: publicProcedure .input(z.object({ email: z.string().email() })) .mutation(async ({ input, ctx }) => { @@ -896,7 +850,6 @@ export const authRouter = createTRPCRouter({ throw error; } - // Handle email sending failures gracefully if ( error instanceof TimeoutError || error instanceof NetworkError || @@ -1108,7 +1061,6 @@ export const authRouter = createTRPCRouter({ throw error; } - // Handle email sending failures gracefully if ( error instanceof TimeoutError || error instanceof NetworkError || diff --git a/src/server/api/routers/database.ts b/src/server/api/routers/database.ts index b245439..5fca1a5 100644 --- a/src/server/api/routers/database.ts +++ b/src/server/api/routers/database.ts @@ -12,10 +12,6 @@ import { cache, withCacheAndStale } from "~/server/cache"; const BLOG_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours export const databaseRouter = createTRPCRouter({ - // ============================================================ - // Comment Reactions Routes - // ============================================================ - getCommentReactions: publicProcedure .input(z.object({ commentID: z.string() })) .query(async ({ input }) => { @@ -105,14 +101,9 @@ export const databaseRouter = createTRPCRouter({ } }), - // ============================================================ - // Comments Routes - // ============================================================ - getAllComments: publicProcedure.query(async () => { try { const conn = ConnectionFactory(); - // Join with Post table to get post titles along with comments const query = ` SELECT c.*, p.title as post_title FROM Comment c @@ -148,7 +139,6 @@ export const databaseRouter = createTRPCRouter({ privilegeLevel: ctx.privilegeLevel }); - // Get the comment to check ownership const commentQuery = await conn.execute({ sql: "SELECT * FROM Comment WHERE id = ?", args: [input.commentID] @@ -162,7 +152,6 @@ export const databaseRouter = createTRPCRouter({ }); } - // Authorization checks const isOwner = comment.commenter_id === ctx.userId; const isAdmin = ctx.privilegeLevel === "admin"; diff --git a/src/server/cache.ts b/src/server/cache.ts index be80ba8..5c228a0 100644 --- a/src/server/cache.ts +++ b/src/server/cache.ts @@ -19,17 +19,11 @@ class SimpleCache { return entry.data as T; } - /** - * Get cached data even if expired (for stale-while-revalidate) - */ getStale(key: string): T | null { const entry = this.cache.get(key); return entry ? (entry.data as T) : null; } - /** - * Check if cache entry exists (regardless of expiration) - */ has(key: string): boolean { return this.cache.has(key); } @@ -49,9 +43,6 @@ class SimpleCache { this.cache.delete(key); } - /** - * Delete all keys starting with a prefix - */ deleteByPrefix(prefix: string): void { for (const key of this.cache.keys()) { if (key.startsWith(prefix)) { @@ -62,8 +53,6 @@ class SimpleCache { } export const cache = new SimpleCache(); - -// Helper function to wrap async operations with caching export async function withCache( key: string, ttlMs: number, @@ -80,7 +69,6 @@ export async function withCache( } /** - * Cache wrapper with stale-while-revalidate support * Returns stale data if fetch fails, with optional stale time limit */ export async function withCacheAndStale( @@ -88,19 +76,17 @@ export async function withCacheAndStale( ttlMs: number, fn: () => Promise, options: { - maxStaleMs?: number; // Maximum age of stale data to return (default: 7 days) - logErrors?: boolean; // Whether to log errors (default: true) + maxStaleMs?: number; + logErrors?: boolean; } = {} ): Promise { const { maxStaleMs = 7 * 24 * 60 * 60 * 1000, logErrors = true } = options; - // Try to get fresh cached data const cached = cache.get(key, ttlMs); if (cached !== null) { return cached; } - // Try to fetch new data try { const result = await fn(); cache.set(key, result); @@ -110,10 +96,8 @@ export async function withCacheAndStale( console.error(`Error fetching data for cache key "${key}":`, error); } - // If fetch fails, try to serve stale data const stale = cache.getStale(key); if (stale !== null) { - // Check if stale data is within acceptable age const entry = (cache as any).cache.get(key); const age = Date.now() - entry.timestamp; @@ -127,7 +111,6 @@ export async function withCacheAndStale( } } - // No stale data available or too old, re-throw the error throw error; } } diff --git a/src/server/conditional-parser.ts b/src/server/conditional-parser.ts index 3a4d29a..332a758 100644 --- a/src/server/conditional-parser.ts +++ b/src/server/conditional-parser.ts @@ -1,17 +1,7 @@ -/** - * Server-side conditional parser for blog content - * Evaluates conditional blocks and returns processed HTML - */ - -/** - * Get safe environment variables for conditional evaluation - * Only exposes non-sensitive variables that are safe to use in content conditionals - */ export function getSafeEnvVariables(): Record { return { NODE_ENV: process.env.NODE_ENV, VERCEL_ENV: process.env.VERCEL_ENV - // Add other safe, non-sensitive env vars here as needed // DO NOT expose API keys, secrets, database URLs, etc. }; } @@ -33,12 +23,6 @@ interface ConditionalBlock { content: string; } -/** - * Parse HTML and evaluate conditional blocks (both block and inline) - * @param html - Raw HTML from database - * @param context - Evaluation context (user, date, features) - * @returns Processed HTML with conditionals evaluated - */ export function parseConditionals( html: string, context: ConditionalContext @@ -47,40 +31,29 @@ export function parseConditionals( let processedHtml = html; - // First, process block-level conditionals (div elements) processedHtml = processBlockConditionals(processedHtml, context); - - // Then, process inline conditionals (span elements) processedHtml = processInlineConditionals(processedHtml, context); return processedHtml; } -/** - * Process block-level conditional divs - */ function processBlockConditionals( html: string, context: ConditionalContext ): string { - // More flexible regex that handles attributes in any order - // Match div with class="conditional-block" and capture the full tag const divRegex = /]*class="[^"]*conditional-block[^"]*"[^>]*)>([\s\S]*?)<\/div>/gi; let processedHtml = html; let match: RegExpExecArray | null; - // Reset regex lastIndex divRegex.lastIndex = 0; - // Collect all matches first to avoid regex state issues const matches: ConditionalBlock[] = []; while ((match = divRegex.exec(html)) !== null) { const attributes = match[1]; const content = match[2]; - // Extract individual attributes const typeMatch = /data-condition-type="([^"]+)"/.exec(attributes); const valueMatch = /data-condition-value="([^"]+)"/.exec(attributes); const showWhenMatch = /data-show-when="(true|false)"/.exec(attributes); @@ -96,7 +69,6 @@ function processBlockConditionals( } } - // Process each conditional block for (const block of matches) { const shouldShow = evaluateCondition( block.conditionType, @@ -106,8 +78,6 @@ function processBlockConditionals( ); if (shouldShow) { - // Keep content, but remove conditional wrapper - // Extract content from inner
const innerContentRegex = /([\s\S]*?)<\/div>/i; const innerMatch = block.fullMatch.match(innerContentRegex); @@ -115,7 +85,6 @@ function processBlockConditionals( processedHtml = processedHtml.replace(block.fullMatch, innerContent); } else { - // Remove entire block processedHtml = processedHtml.replace(block.fullMatch, ""); } } @@ -123,31 +92,23 @@ function processBlockConditionals( return processedHtml; } -/** - * Process inline conditional spans - */ function processInlineConditionals( html: string, context: ConditionalContext ): string { - // More flexible regex that handles attributes in any order - // Match span with class="conditional-inline" and capture the full tag const spanRegex = /]*class="[^"]*conditional-inline[^"]*"[^>]*)>([\s\S]*?)<\/span>/gi; let processedHtml = html; let match: RegExpExecArray | null; - // Reset regex lastIndex spanRegex.lastIndex = 0; - // Collect all matches first const matches: ConditionalBlock[] = []; while ((match = spanRegex.exec(html)) !== null) { const attributes = match[1]; const content = match[2]; - // Extract individual attributes const typeMatch = /data-condition-type="([^"]+)"/.exec(attributes); const valueMatch = /data-condition-value="([^"]+)"/.exec(attributes); const showWhenMatch = /data-show-when="(true|false)"/.exec(attributes); @@ -163,7 +124,6 @@ function processInlineConditionals( } } - // Process each inline conditional for (const inline of matches) { const shouldShow = evaluateCondition( inline.conditionType, @@ -173,10 +133,8 @@ function processInlineConditionals( ); if (shouldShow) { - // Keep content, remove span wrapper processedHtml = processedHtml.replace(inline.fullMatch, inline.content); } else { - // Remove entire inline span processedHtml = processedHtml.replace(inline.fullMatch, ""); } } @@ -184,9 +142,6 @@ function processInlineConditionals( return processedHtml; } -/** - * Evaluate a single condition - */ function evaluateCondition( conditionType: string, conditionValue: string, @@ -212,18 +167,12 @@ function evaluateCondition( conditionMet = evaluateEnvCondition(conditionValue, context); break; default: - // Unknown condition type - default to hiding content for safety conditionMet = false; } - // Apply showWhen logic: if showWhen is true, show when condition is met - // If showWhen is false, show when condition is NOT met return showWhen ? conditionMet : !conditionMet; } -/** - * Evaluate authentication condition - */ function evaluateAuthCondition( value: string, context: ConditionalContext @@ -238,9 +187,6 @@ function evaluateAuthCondition( } } -/** - * Evaluate privilege level condition - */ function evaluatePrivilegeCondition( value: string, context: ConditionalContext @@ -249,7 +195,6 @@ function evaluatePrivilegeCondition( } /** - * Evaluate date-based condition * Supports: "before:YYYY-MM-DD", "after:YYYY-MM-DD", "between:YYYY-MM-DD,YYYY-MM-DD" */ function evaluateDateCondition( @@ -287,9 +232,6 @@ function evaluateDateCondition( } } -/** - * Evaluate feature flag condition - */ function evaluateFeatureCondition( value: string, context: ConditionalContext @@ -298,7 +240,6 @@ function evaluateFeatureCondition( } /** - * Evaluate environment variable condition * Format: "ENV_VAR_NAME:expected_value" or "ENV_VAR_NAME:*" for any truthy value */ function evaluateEnvCondition( @@ -306,7 +247,6 @@ function evaluateEnvCondition( context: ConditionalContext ): boolean { try { - // Parse format: "VAR_NAME:expected_value" const colonIndex = value.indexOf(":"); if (colonIndex === -1) return false; @@ -315,12 +255,10 @@ function evaluateEnvCondition( const actualValue = context.env[varName]; - // If expected value is "*", check if variable exists and is truthy if (expectedValue === "*") { return !!actualValue; } - // Otherwise, check for exact match return actualValue === expectedValue; } catch (error) { console.error("Error parsing env condition:", error); diff --git a/src/server/database.ts b/src/server/database.ts index 7ff0ee9..06658c3 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -92,7 +92,6 @@ export async function dumpAndSendDB({ reason?: string; }> { try { - // Fetch database dump with timeout const res = await fetchWithTimeout( `https://${dbName}-mikefreno.turso.io/dump`, { @@ -100,7 +99,7 @@ export async function dumpAndSendDB({ headers: { Authorization: `Bearer ${dbToken}` }, - timeout: 30000 // 30s for database dump + timeout: 30000 } ); @@ -132,7 +131,6 @@ export async function dumpAndSendDB({ ] }; - // Send email with retry logic await fetchWithRetry( async () => { const sendRes = await fetchWithTimeout(apiUrl, { @@ -143,7 +141,7 @@ export async function dumpAndSendDB({ "content-type": "application/json" }, body: JSON.stringify(emailPayload), - timeout: 20000 // 20s for email with attachment + timeout: 20000 }); await checkResponse(sendRes); @@ -157,7 +155,6 @@ export async function dumpAndSendDB({ return { success: true }; } catch (error) { - // Log specific error types for debugging if (error instanceof TimeoutError) { console.error("Database dump timeout:", error.message); return { success: false, reason: "Database dump timed out" }; diff --git a/src/server/feature-flags.ts b/src/server/feature-flags.ts index 81941c0..ec552e8 100644 --- a/src/server/feature-flags.ts +++ b/src/server/feature-flags.ts @@ -1,8 +1,3 @@ -/** - * Feature flag system for conditional content - * Centralized configuration for feature toggles - */ - export interface FeatureFlags { [key: string]: boolean; } diff --git a/src/server/fetch-utils.ts b/src/server/fetch-utils.ts index b34d849..855e231 100644 --- a/src/server/fetch-utils.ts +++ b/src/server/fetch-utils.ts @@ -1,4 +1,3 @@ -// Error types for better error classification export class NetworkError extends Error { constructor( message: string, @@ -34,9 +33,6 @@ interface FetchWithTimeoutOptions extends RequestInit { timeout?: number; } -/** - * Fetch wrapper with timeout support and proper error classification - */ export async function fetchWithTimeout( url: string, options: FetchWithTimeoutOptions = {} @@ -57,9 +53,7 @@ export async function fetchWithTimeout( } catch (error: unknown) { clearTimeout(timeoutId); - // Classify the error for better handling if (error instanceof Error) { - // Check for abort/timeout if (error.name === "AbortError") { throw new TimeoutError( `Request to ${url} timed out after ${timeout}ms`, @@ -67,7 +61,6 @@ export async function fetchWithTimeout( ); } - // Check for connection errors (various runtime-specific errors) if ( error.message.includes("fetch failed") || error.message.includes("ECONNREFUSED") || @@ -84,14 +77,10 @@ export async function fetchWithTimeout( } } - // Re-throw unknown errors throw error; } } -/** - * Helper to check response status and throw APIError if not ok - */ export async function checkResponse(response: Response): Promise { if (!response.ok) { throw new APIError( @@ -103,9 +92,6 @@ export async function checkResponse(response: Response): Promise { return response; } -/** - * Safe JSON parse that handles errors gracefully - */ export async function safeJsonParse(response: Response): Promise { try { return await response.json(); @@ -115,9 +101,6 @@ export async function safeJsonParse(response: Response): Promise { } } -/** - * Retry logic with exponential backoff - */ export async function fetchWithRetry( fn: () => Promise, options: { @@ -141,12 +124,10 @@ export async function fetchWithRetry( } catch (error) { lastError = error; - // Don't retry if it's the last attempt or error is not retryable if (attempt === maxRetries || !retryableErrors(error)) { throw error; } - // Exponential backoff const delay = retryDelay * Math.pow(2, attempt); await new Promise((resolve) => setTimeout(resolve, delay)); } diff --git a/src/types/comment.ts b/src/types/comment.ts index f06bef8..c4bc966 100644 --- a/src/types/comment.ts +++ b/src/types/comment.ts @@ -1,7 +1,6 @@ /** * Comment System Type Definitions * - * Types for the blog comment system including: * - Comment and CommentReaction models * - WebSocket message types * - User data structures diff --git a/src/types/user.ts b/src/types/user.ts index bce9ab4..f42c96e 100644 --- a/src/types/user.ts +++ b/src/types/user.ts @@ -1,11 +1,7 @@ -/** - * User type definitions matching database schema - */ - export interface User { id: string; email: string | null; - email_verified: number; // SQLite boolean (0 or 1) + email_verified: number; password_hash: string | null; display_name: string | null; provider: "email" | "google" | "github" | null; @@ -19,9 +15,6 @@ export interface User { updated_at: string; } -/** - * Client-safe user data (excludes sensitive fields) - */ export interface UserProfile { id: string; email?: string; @@ -32,9 +25,6 @@ export interface UserProfile { hasPassword: boolean; } -/** - * Convert database User to client-safe UserProfile - */ export function toUserProfile(user: User): UserProfile { return { id: user.id, @@ -47,24 +37,15 @@ export function toUserProfile(user: User): UserProfile { }; } -/** - * JWT payload for session tokens - */ export interface SessionPayload { - id: string; // user ID + id: string; email?: string; } -/** - * JWT payload for email verification - */ export interface EmailVerificationPayload { email: string; } -/** - * JWT payload for password reset - */ export interface PasswordResetPayload { email: string; }