code quality issues fixed

This commit is contained in:
Anish Sarkar 2025-11-23 16:39:23 +05:30
parent abbaa848f3
commit 3fac196c35
17 changed files with 495 additions and 493 deletions

View file

@ -4,6 +4,7 @@ Revision ID: 38
Revises: 37 Revises: 37
""" """
from collections.abc import Sequence from collections.abc import Sequence
import sqlalchemy as sa import sqlalchemy as sa
@ -12,8 +13,8 @@ from sqlalchemy.dialects import postgresql
from alembic import op from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = '38' revision: str = "38"
down_revision: str | None = '37' down_revision: str | None = "37"
branch_labels: str | Sequence[str] | None = None branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None
@ -22,20 +23,28 @@ def upgrade() -> None:
"""Upgrade schema - Add BlockNote fields only.""" """Upgrade schema - Add BlockNote fields only."""
op.add_column( op.add_column(
'documents', "documents",
sa.Column('blocknote_document', postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column(
"blocknote_document", postgresql.JSONB(astext_type=sa.Text()), nullable=True
),
) )
op.add_column( op.add_column(
'documents', "documents",
sa.Column('content_needs_reindexing', sa.Boolean(), nullable=False, server_default=sa.false()), sa.Column(
"content_needs_reindexing",
sa.Boolean(),
nullable=False,
server_default=sa.false(),
),
) )
op.add_column( op.add_column(
'documents', "documents",
sa.Column('last_edited_at', sa.TIMESTAMP(timezone=True), nullable=True) sa.Column("last_edited_at", sa.TIMESTAMP(timezone=True), nullable=True),
) )
def downgrade() -> None: def downgrade() -> None:
"""Downgrade schema - Remove BlockNote fields.""" """Downgrade schema - Remove BlockNote fields."""
op.drop_column('documents', 'last_edited_at') op.drop_column("documents", "last_edited_at")
op.drop_column('documents', 'content_needs_reindexing') op.drop_column("documents", "content_needs_reindexing")
op.drop_column('documents', 'blocknote_document') op.drop_column("documents", "blocknote_document")

View file

@ -1,6 +1,7 @@
""" """
Editor routes for BlockNote document editing. Editor routes for BlockNote document editing.
""" """
from datetime import UTC, datetime from datetime import UTC, datetime
from typing import Any from typing import Any
@ -47,14 +48,16 @@ async def get_editor_content(
"document_id": document.id, "document_id": document.id,
"title": document.title, "title": document.title,
"blocknote_document": document.blocknote_document, "blocknote_document": document.blocknote_document,
"last_edited_at": document.last_edited_at.isoformat() if document.last_edited_at else None, "last_edited_at": document.last_edited_at.isoformat()
if document.last_edited_at
else None,
} }
# For old documents without blocknote_document, return error # For old documents without blocknote_document, return error
# (Can't convert summary back to full document) # (Can't convert summary back to full document)
raise HTTPException( raise HTTPException(
status_code=400, status_code=400,
detail="This document was uploaded before editing was enabled. Please re-upload to enable editing." detail="This document was uploaded before editing was enabled. Please re-upload to enable editing.",
) )
@ -158,4 +161,3 @@ async def update_blocknote_content(
# "message": "Document saved. Summary and chunks will be regenerated in the background.", # "message": "Document saved. Summary and chunks will be regenerated in the background.",
# "content_needs_reindexing": True, # "content_needs_reindexing": True,
# } # }

View file

@ -105,8 +105,9 @@ async def add_received_file_document_using_unstructured(
# Convert markdown to BlockNote JSON # Convert markdown to BlockNote JSON
blocknote_json = await convert_markdown_to_blocknote(file_in_markdown) blocknote_json = await convert_markdown_to_blocknote(file_in_markdown)
if not blocknote_json: if not blocknote_json:
logging.warning(f"Failed to convert {file_name} to BlockNote JSON, document will not be editable") logging.warning(
f"Failed to convert {file_name} to BlockNote JSON, document will not be editable"
)
# Update or create document # Update or create document
if existing_document: if existing_document:
@ -229,8 +230,9 @@ async def add_received_file_document_using_llamacloud(
# Convert markdown to BlockNote JSON # Convert markdown to BlockNote JSON
blocknote_json = await convert_markdown_to_blocknote(file_in_markdown) blocknote_json = await convert_markdown_to_blocknote(file_in_markdown)
if not blocknote_json: if not blocknote_json:
logging.warning(f"Failed to convert {file_name} to BlockNote JSON, document will not be editable") logging.warning(
f"Failed to convert {file_name} to BlockNote JSON, document will not be editable"
)
# Update or create document # Update or create document
if existing_document: if existing_document:
@ -378,8 +380,9 @@ async def add_received_file_document_using_docling(
# Convert markdown to BlockNote JSON # Convert markdown to BlockNote JSON
blocknote_json = await convert_markdown_to_blocknote(file_in_markdown) blocknote_json = await convert_markdown_to_blocknote(file_in_markdown)
if not blocknote_json: if not blocknote_json:
logging.warning(f"Failed to convert {file_name} to BlockNote JSON, document will not be editable") logging.warning(
f"Failed to convert {file_name} to BlockNote JSON, document will not be editable"
)
# Update or create document # Update or create document
if existing_document: if existing_document:

View file

@ -115,8 +115,9 @@ async def add_received_markdown_file_document(
# Convert to BlockNote JSON # Convert to BlockNote JSON
blocknote_json = await convert_markdown_to_blocknote(file_in_markdown) blocknote_json = await convert_markdown_to_blocknote(file_in_markdown)
if not blocknote_json: if not blocknote_json:
logging.warning(f"Failed to convert {file_name} to BlockNote JSON, document will not be editable") logging.warning(
f"Failed to convert {file_name} to BlockNote JSON, document will not be editable"
)
# Update or create document # Update or create document
if existing_document: if existing_document:

View file

@ -32,10 +32,10 @@ async def convert_markdown_to_blocknote(markdown: str) -> dict[str, Any] | None:
{ {
"type": "text", "type": "text",
"text": "Document content could not be converted for editing.", "text": "Document content could not be converted for editing.",
"styles": {} "styles": {},
} }
], ],
"children": [] "children": [],
} }
] ]
@ -51,7 +51,9 @@ async def convert_markdown_to_blocknote(markdown: str) -> dict[str, Any] | None:
blocknote_document = data.get("blocknote_document") blocknote_document = data.get("blocknote_document")
if blocknote_document: if blocknote_document:
logger.info(f"Successfully converted markdown to BlockNote (original: {len(markdown)} chars, sanitized: {len(markdown)} chars)") logger.info(
f"Successfully converted markdown to BlockNote (original: {len(markdown)} chars, sanitized: {len(markdown)} chars)"
)
return blocknote_document return blocknote_document
else: else:
logger.warning("Next.js API returned empty blocknote_document") logger.warning("Next.js API returned empty blocknote_document")
@ -61,7 +63,9 @@ async def convert_markdown_to_blocknote(markdown: str) -> dict[str, Any] | None:
logger.error("Timeout converting markdown to BlockNote after 30s") logger.error("Timeout converting markdown to BlockNote after 30s")
return None return None
except httpx.HTTPStatusError as e: except httpx.HTTPStatusError as e:
logger.error(f"HTTP error converting markdown to BlockNote: {e.response.status_code} - {e.response.text}") logger.error(
f"HTTP error converting markdown to BlockNote: {e.response.status_code} - {e.response.text}"
)
# Log first 1000 chars of problematic markdown for debugging # Log first 1000 chars of problematic markdown for debugging
logger.debug(f"Problematic markdown sample: {markdown[:1000]}") logger.debug(f"Problematic markdown sample: {markdown[:1000]}")
return None return None
@ -69,7 +73,10 @@ async def convert_markdown_to_blocknote(markdown: str) -> dict[str, Any] | None:
logger.error(f"Failed to convert markdown to BlockNote: {e}", exc_info=True) logger.error(f"Failed to convert markdown to BlockNote: {e}", exc_info=True)
return None return None
async def convert_blocknote_to_markdown(blocknote_document: dict[str, Any] | list[dict[str, Any]]) -> str | None:
async def convert_blocknote_to_markdown(
blocknote_document: dict[str, Any] | list[dict[str, Any]],
) -> str | None:
""" """
Convert BlockNote JSON to markdown via Next.js API. Convert BlockNote JSON to markdown via Next.js API.
@ -95,7 +102,9 @@ async def convert_blocknote_to_markdown(blocknote_document: dict[str, Any] | lis
markdown = data.get("markdown") markdown = data.get("markdown")
if markdown: if markdown:
logger.info(f"Successfully converted BlockNote to markdown ({len(markdown)} chars)") logger.info(
f"Successfully converted BlockNote to markdown ({len(markdown)} chars)"
)
return markdown return markdown
else: else:
logger.warning("Next.js API returned empty markdown") logger.warning("Next.js API returned empty markdown")
@ -105,9 +114,10 @@ async def convert_blocknote_to_markdown(blocknote_document: dict[str, Any] | lis
logger.error("Timeout converting BlockNote to markdown after 30s") logger.error("Timeout converting BlockNote to markdown after 30s")
return None return None
except httpx.HTTPStatusError as e: except httpx.HTTPStatusError as e:
logger.error(f"HTTP error converting BlockNote to markdown: {e.response.status_code} - {e.response.text}") logger.error(
f"HTTP error converting BlockNote to markdown: {e.response.status_code} - {e.response.text}"
)
return None return None
except Exception as e: except Exception as e:
logger.error(f"Failed to convert BlockNote to markdown: {e}", exc_info=True) logger.error(f"Failed to convert BlockNote to markdown: {e}", exc_info=True)
return None return None

View file

@ -6,10 +6,7 @@ export async function POST(request: NextRequest) {
const { markdown } = await request.json(); const { markdown } = await request.json();
if (!markdown || typeof markdown !== "string") { if (!markdown || typeof markdown !== "string") {
return NextResponse.json( return NextResponse.json({ error: "Markdown string is required" }, { status: 400 });
{ error: "Markdown string is required" },
{ status: 400 }
);
} }
// Log raw markdown input before conversion // Log raw markdown input before conversion
@ -35,7 +32,7 @@ export async function POST(request: NextRequest) {
return NextResponse.json( return NextResponse.json(
{ {
error: "Failed to convert markdown to BlockNote blocks", error: "Failed to convert markdown to BlockNote blocks",
details: error.message details: error.message,
}, },
{ status: 500 } { status: 500 }
); );

View file

@ -6,10 +6,7 @@ export async function POST(request: NextRequest) {
const { blocknote_document } = await request.json(); const { blocknote_document } = await request.json();
if (!blocknote_document || !Array.isArray(blocknote_document)) { if (!blocknote_document || !Array.isArray(blocknote_document)) {
return NextResponse.json( return NextResponse.json({ error: "BlockNote document array is required" }, { status: 400 });
{ error: "BlockNote document array is required" },
{ status: 400 }
);
} }
// Create server-side editor instance // Create server-side editor instance
@ -19,7 +16,7 @@ export async function POST(request: NextRequest) {
const markdown = await editor.blocksToMarkdownLossy(blocknote_document); const markdown = await editor.blocksToMarkdownLossy(blocknote_document);
return NextResponse.json({ return NextResponse.json({
markdown markdown,
}); });
} catch (error) { } catch (error) {
console.error("Failed to convert BlockNote to markdown:", error); console.error("Failed to convert BlockNote to markdown:", error);

View file

@ -1,6 +1,6 @@
"use client"; "use client";
import { MoreHorizontal, Pencil, FileText, Trash2 } from "lucide-react"; import { FileText, MoreHorizontal, Pencil, Trash2 } from "lucide-react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";

View file

@ -1,9 +1,9 @@
"use client"; "use client";
import { AlertCircle, FileText, Loader2, Save, X } from "lucide-react";
import { motion } from "motion/react";
import { useParams, useRouter } from "next/navigation"; import { useParams, useRouter } from "next/navigation";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { motion } from "motion/react";
import { Loader2, Save, X, FileText, AlertCircle } from "lucide-react";
import { toast } from "sonner"; import { toast } from "sonner";
import { BlockNoteEditor } from "@/components/DynamicBlockNoteEditor"; import { BlockNoteEditor } from "@/components/DynamicBlockNoteEditor";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -31,9 +31,8 @@ export default function EditorPage() {
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
// Get auth token // Get auth token
const token = typeof window !== "undefined" const token =
? localStorage.getItem("surfsense_bearer_token") typeof window !== "undefined" ? localStorage.getItem("surfsense_bearer_token") : null;
: null;
// Fetch document content - DIRECT CALL TO FASTAPI // Fetch document content - DIRECT CALL TO FASTAPI
useEffect(() => { useEffect(() => {
@ -56,7 +55,9 @@ export default function EditorPage() {
); );
if (!response.ok) { if (!response.ok) {
const errorData = await response.json().catch(() => ({ detail: "Failed to fetch document" })); const errorData = await response
.json()
.catch(() => ({ detail: "Failed to fetch document" }));
throw new Error(errorData.detail || "Failed to fetch document"); throw new Error(errorData.detail || "Failed to fetch document");
} }
@ -64,7 +65,9 @@ export default function EditorPage() {
// Check if blocknote_document exists // Check if blocknote_document exists
if (!data.blocknote_document) { if (!data.blocknote_document) {
setError("This document does not have BlockNote content. Please re-upload the document to enable editing."); setError(
"This document does not have BlockNote content. Please re-upload the document to enable editing."
);
setLoading(false); setLoading(false);
return; return;
} }
@ -74,7 +77,9 @@ export default function EditorPage() {
setError(null); setError(null);
} catch (error) { } catch (error) {
console.error("Error fetching document:", error); console.error("Error fetching document:", error);
setError(error instanceof Error ? error.message : "Failed to fetch document. Please try again."); setError(
error instanceof Error ? error.message : "Failed to fetch document. Please try again."
);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@ -150,7 +155,9 @@ export default function EditorPage() {
); );
if (!response.ok) { if (!response.ok) {
const errorData = await response.json().catch(() => ({ detail: "Failed to save document" })); const errorData = await response
.json()
.catch(() => ({ detail: "Failed to save document" }));
throw new Error(errorData.detail || "Failed to save document"); throw new Error(errorData.detail || "Failed to save document");
} }
@ -163,7 +170,9 @@ export default function EditorPage() {
}, 500); }, 500);
} catch (error) { } catch (error) {
console.error("Error saving document:", error); console.error("Error saving document:", error);
toast.error(error instanceof Error ? error.message : "Failed to save document. Please try again."); toast.error(
error instanceof Error ? error.message : "Failed to save document. Please try again."
);
} finally { } finally {
setSaving(false); setSaving(false);
} }
@ -245,27 +254,16 @@ export default function EditorPage() {
<FileText className="h-5 w-5 text-muted-foreground shrink-0" /> <FileText className="h-5 w-5 text-muted-foreground shrink-0" />
<div className="flex flex-col min-w-0"> <div className="flex flex-col min-w-0">
<h1 className="text-lg font-semibold truncate">{document.title}</h1> <h1 className="text-lg font-semibold truncate">{document.title}</h1>
{hasUnsavedChanges && ( {hasUnsavedChanges && <p className="text-xs text-muted-foreground">Unsaved changes</p>}
<p className="text-xs text-muted-foreground">Unsaved changes</p>
)}
</div> </div>
</div> </div>
<Separator orientation="vertical" className="h-6" /> <Separator orientation="vertical" className="h-6" />
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button <Button variant="outline" onClick={handleCancel} disabled={saving} className="gap-2">
variant="outline"
onClick={handleCancel}
disabled={saving}
className="gap-2"
>
<X className="h-4 w-4" /> <X className="h-4 w-4" />
Cancel Cancel
</Button> </Button>
<Button <Button onClick={handleSave} disabled={saving} className="gap-2">
onClick={handleSave}
disabled={saving}
className="gap-2"
>
{saving ? ( {saving ? (
<> <>
<Loader2 className="h-4 w-4 animate-spin" /> <Loader2 className="h-4 w-4 animate-spin" />
@ -285,10 +283,7 @@ export default function EditorPage() {
<div className="flex-1 overflow-hidden relative"> <div className="flex-1 overflow-hidden relative">
<div className="h-full w-full overflow-auto p-6"> <div className="h-full w-full overflow-auto p-6">
<div className="max-w-4xl mx-auto"> <div className="max-w-4xl mx-auto">
<BlockNoteEditor <BlockNoteEditor initialContent={editorContent} onChange={setEditorContent} />
initialContent={editorContent}
onChange={setEditorContent}
/>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,21 +1,18 @@
"use client"; "use client";
import { useEffect, useRef, useMemo } from "react";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { useEffect, useMemo, useRef } from "react";
import "@blocknote/core/fonts/inter.css"; import "@blocknote/core/fonts/inter.css";
import "@blocknote/mantine/style.css"; import "@blocknote/mantine/style.css";
import { useCreateBlockNote } from "@blocknote/react";
import { BlockNoteView } from "@blocknote/mantine"; import { BlockNoteView } from "@blocknote/mantine";
import { useCreateBlockNote } from "@blocknote/react";
interface BlockNoteEditorProps { interface BlockNoteEditorProps {
initialContent?: any; initialContent?: any;
onChange?: (content: any) => void; onChange?: (content: any) => void;
} }
export default function BlockNoteEditor({ export default function BlockNoteEditor({ initialContent, onChange }: BlockNoteEditorProps) {
initialContent,
onChange,
}: BlockNoteEditorProps) {
const { resolvedTheme } = useTheme(); const { resolvedTheme } = useTheme();
// Track the initial content to prevent re-initialization // Track the initial content to prevent re-initialization
@ -24,7 +21,7 @@ export default function BlockNoteEditor({
// Creates a new editor instance - only use initialContent on first render // Creates a new editor instance - only use initialContent on first render
const editor = useCreateBlockNote({ const editor = useCreateBlockNote({
initialContent: initialContentRef.current === null ? (initialContent || undefined) : undefined, initialContent: initialContentRef.current === null ? initialContent || undefined : undefined,
}); });
// Store initial content on first render only // Store initial content on first render only

View file

@ -3,7 +3,4 @@
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
// Dynamically import BlockNote editor with SSR disabled // Dynamically import BlockNote editor with SSR disabled
export const BlockNoteEditor = dynamic( export const BlockNoteEditor = dynamic(() => import("./BlockNoteEditor"), { ssr: false });
() => import("./BlockNoteEditor"),
{ ssr: false }
);

View file

@ -41,9 +41,8 @@ export function DashboardBreadcrumb() {
useEffect(() => { useEffect(() => {
if (segments[2] === "editor" && segments[3] && searchSpaceId) { if (segments[2] === "editor" && segments[3] && searchSpaceId) {
const documentId = segments[3]; const documentId = segments[3];
const token = typeof window !== "undefined" const token =
? localStorage.getItem("surfsense_bearer_token") typeof window !== "undefined" ? localStorage.getItem("surfsense_bearer_token") : null;
: null;
if (token) { if (token) {
fetch( fetch(

View file

@ -24,18 +24,13 @@ const nextConfig: NextConfig = {
], ],
}, },
// Mark BlockNote server packages as external // Mark BlockNote server packages as external
serverExternalPackages: [ serverExternalPackages: ["@blocknote/server-util"],
'@blocknote/server-util',
],
// Configure webpack to handle blocknote packages // Configure webpack to handle blocknote packages
webpack: (config, { isServer }) => { webpack: (config, { isServer }) => {
if (isServer) { if (isServer) {
// Don't bundle these packages on the server // Don't bundle these packages on the server
config.externals = [ config.externals = [...(config.externals || []), "@blocknote/server-util"];
...(config.externals || []),
'@blocknote/server-util',
];
} }
return config; return config;
}, },