From da0fce708f76b6d48bc6b8b44432793803f80bb1 Mon Sep 17 00:00:00 2001 From: Richard R Date: Sun, 15 Feb 2026 15:44:20 -0700 Subject: [PATCH] refactor: improve database reliability and clean up code Enable WAL mode and set a busy timeout for SQLite to better handle concurrent access and prevent 500 errors. Add error handling to async calls in UI components and remove unused imports across several files. --- src/app/api/documents/blob/preview/ensure/route.ts | 9 +-------- src/app/api/documents/blob/preview/presign/route.ts | 9 +-------- src/components/PrivacyModal.tsx | 4 +++- src/components/SettingsModal.tsx | 8 ++++++-- src/components/documents/DexieMigrationModal.tsx | 2 +- src/db/index.ts | 6 ++++++ 6 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/app/api/documents/blob/preview/ensure/route.ts b/src/app/api/documents/blob/preview/ensure/route.ts index 0bdae9b..edd06bc 100644 --- a/src/app/api/documents/blob/preview/ensure/route.ts +++ b/src/app/api/documents/blob/preview/ensure/route.ts @@ -1,13 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; -import { and, eq, inArray } from 'drizzle-orm'; -import { db } from '@/db'; -import { documents } from '@/db/schema'; -import { requireAuthContext } from '@/lib/server/auth'; -import { isValidDocumentId } from '@/lib/server/documents-blobstore'; import { presignDocumentPreviewGet } from '@/lib/server/document-previews-blobstore'; -import { ensureDocumentPreview, isPreviewableDocumentType } from '@/lib/server/document-previews'; -import { getOpenReaderTestNamespace, getUnclaimedUserIdForNamespace } from '@/lib/server/test-namespace'; -import { isS3Configured } from '@/lib/server/s3'; +import { ensureDocumentPreview } from '@/lib/server/document-previews'; import { validatePreviewRequest } from '../utils'; export const dynamic = 'force-dynamic'; diff --git a/src/app/api/documents/blob/preview/presign/route.ts b/src/app/api/documents/blob/preview/presign/route.ts index ef280d5..c4f9f84 100644 --- a/src/app/api/documents/blob/preview/presign/route.ts +++ b/src/app/api/documents/blob/preview/presign/route.ts @@ -1,13 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; -import { and, eq, inArray } from 'drizzle-orm'; -import { db } from '@/db'; -import { documents } from '@/db/schema'; -import { requireAuthContext } from '@/lib/server/auth'; -import { isValidDocumentId } from '@/lib/server/documents-blobstore'; import { presignDocumentPreviewGet } from '@/lib/server/document-previews-blobstore'; -import { ensureDocumentPreview, isPreviewableDocumentType } from '@/lib/server/document-previews'; -import { getOpenReaderTestNamespace, getUnclaimedUserIdForNamespace } from '@/lib/server/test-namespace'; -import { isS3Configured } from '@/lib/server/s3'; +import { ensureDocumentPreview } from '@/lib/server/document-previews'; import { validatePreviewRequest } from '../utils'; export const dynamic = 'force-dynamic'; diff --git a/src/components/PrivacyModal.tsx b/src/components/PrivacyModal.tsx index 0f40573..0a0f7d3 100644 --- a/src/components/PrivacyModal.tsx +++ b/src/components/PrivacyModal.tsx @@ -134,7 +134,9 @@ export function PrivacyModal({ onAccept, authEnabled = false }: PrivacyModalProp }, []); useEffect(() => { - checkPrivacyAccepted(); + checkPrivacyAccepted().catch((err) => { + console.error('Privacy acceptance check failed:', err); + }); }, [checkPrivacyAccepted]); useEffect(() => { diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx index e69d499..41867de 100644 --- a/src/components/SettingsModal.tsx +++ b/src/components/SettingsModal.tsx @@ -160,7 +160,9 @@ export function SettingsModal({ className = '' }: { className?: string }) { }, [setIsOpen]); useEffect(() => { - checkFirstVist(); + checkFirstVist().catch((err) => { + console.error('First visit check failed:', err); + }); setLocalApiKey(apiKey); setLocalBaseUrl(baseUrl); setLocalTTSProvider(ttsProvider); @@ -170,7 +172,9 @@ export function SettingsModal({ className = '' }: { className?: string }) { useEffect(() => { const onPrivacyAccepted = () => { - checkFirstVist(); + checkFirstVist().catch((err) => { + console.error('First visit check after privacy acceptance failed:', err); + }); }; window.addEventListener('openreader:privacyAccepted', onPrivacyAccepted); return () => { diff --git a/src/components/documents/DexieMigrationModal.tsx b/src/components/documents/DexieMigrationModal.tsx index 7e3226f..2b57554 100644 --- a/src/components/documents/DexieMigrationModal.tsx +++ b/src/components/documents/DexieMigrationModal.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { Fragment, useCallback, useEffect, useRef, useState } from 'react'; import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild, Button } from '@headlessui/react'; import { getAppConfig, getAllEpubDocuments, getAllHtmlDocuments, getAllPdfDocuments, updateAppConfig } from '@/lib/dexie'; import { listDocuments, mimeTypeForDoc, uploadDocuments } from '@/lib/client-documents'; diff --git a/src/db/index.ts b/src/db/index.ts index 599b9f2..981b0ec 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -74,6 +74,12 @@ function getDrizzleDB() { fs.mkdirSync(dir, { recursive: true }); } const sqlite = new Database(dbPath); + // WAL mode allows concurrent readers + writer without blocking each other. + // busy_timeout retries on SQLITE_BUSY instead of failing immediately, + // which prevents 500 errors under concurrent API requests (e.g. multiple + // Playwright browser projects hitting the server simultaneously). + sqlite.pragma('journal_mode = WAL'); + sqlite.pragma('busy_timeout = 5000'); dbInstance = drizzleSqlite(sqlite, { schema: { ...schema, ...authSchemaSqlite } }); }