mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-26 01:06:23 +02:00
fix: fixed notifications table and Electric SQL replication setup. Removed redirects for upload button
This commit is contained in:
parent
5bd6bd3d67
commit
fede7413fb
6 changed files with 54 additions and 67 deletions
|
|
@ -1,10 +1,11 @@
|
||||||
"""Add notifications table
|
"""Add notifications table and Electric SQL replication
|
||||||
|
|
||||||
Revision ID: 62
|
Revision ID: 62
|
||||||
Revises: 61
|
Revises: 61
|
||||||
|
|
||||||
Note: Electric SQL replication setup (REPLICA IDENTITY FULL and publication)
|
Creates notifications table and sets up Electric SQL replication
|
||||||
is handled in app/db.py setup_electric_replication() which runs on app startup.
|
(REPLICA IDENTITY FULL and publication) for notifications,
|
||||||
|
search_source_connectors, and documents tables.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
|
|
@ -19,7 +20,7 @@ depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
"""Upgrade schema - add notifications table."""
|
"""Upgrade schema - add notifications table and Electric SQL replication."""
|
||||||
# Create notifications table
|
# Create notifications table
|
||||||
op.execute(
|
op.execute(
|
||||||
"""
|
"""
|
||||||
|
|
@ -44,6 +45,51 @@ def upgrade() -> None:
|
||||||
op.create_index("ix_notifications_created_at", "notifications", ["created_at"])
|
op.create_index("ix_notifications_created_at", "notifications", ["created_at"])
|
||||||
op.create_index("ix_notifications_user_read", "notifications", ["user_id", "read"])
|
op.create_index("ix_notifications_user_read", "notifications", ["user_id", "read"])
|
||||||
|
|
||||||
|
# Set up Electric SQL replication for real-time sync tables
|
||||||
|
# Set REPLICA IDENTITY FULL (required by Electric SQL for replication)
|
||||||
|
# This logs full row data for UPDATE/DELETE operations in the WAL
|
||||||
|
op.execute("ALTER TABLE notifications REPLICA IDENTITY FULL;")
|
||||||
|
op.execute("ALTER TABLE search_source_connectors REPLICA IDENTITY FULL;")
|
||||||
|
op.execute("ALTER TABLE documents REPLICA IDENTITY FULL;")
|
||||||
|
|
||||||
|
# Add tables to Electric SQL publication for replication if publication exists
|
||||||
|
op.execute(
|
||||||
|
"""
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF EXISTS (SELECT 1 FROM pg_publication WHERE pubname = 'electric_publication_default') THEN
|
||||||
|
-- Add notifications if not already added
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_publication_tables
|
||||||
|
WHERE pubname = 'electric_publication_default'
|
||||||
|
AND tablename = 'notifications'
|
||||||
|
) THEN
|
||||||
|
ALTER PUBLICATION electric_publication_default ADD TABLE notifications;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Add search_source_connectors if not already added
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_publication_tables
|
||||||
|
WHERE pubname = 'electric_publication_default'
|
||||||
|
AND tablename = 'search_source_connectors'
|
||||||
|
) THEN
|
||||||
|
ALTER PUBLICATION electric_publication_default ADD TABLE search_source_connectors;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Add documents if not already added
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_publication_tables
|
||||||
|
WHERE pubname = 'electric_publication_default'
|
||||||
|
AND tablename = 'documents'
|
||||||
|
) THEN
|
||||||
|
ALTER PUBLICATION electric_publication_default ADD TABLE documents;
|
||||||
|
END IF;
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
"""Downgrade schema - remove notifications table."""
|
"""Downgrade schema - remove notifications table."""
|
||||||
|
|
|
||||||
|
|
@ -988,60 +988,6 @@ async def create_db_and_tables():
|
||||||
await conn.execute(text("CREATE EXTENSION IF NOT EXISTS vector"))
|
await conn.execute(text("CREATE EXTENSION IF NOT EXISTS vector"))
|
||||||
await conn.run_sync(Base.metadata.create_all)
|
await conn.run_sync(Base.metadata.create_all)
|
||||||
await setup_indexes()
|
await setup_indexes()
|
||||||
await setup_electric_replication()
|
|
||||||
|
|
||||||
|
|
||||||
async def setup_electric_replication():
|
|
||||||
"""Set up Electric SQL replication for real-time sync tables."""
|
|
||||||
async with engine.begin() as conn:
|
|
||||||
# Set REPLICA IDENTITY FULL (required by Electric SQL for replication)
|
|
||||||
# This logs full row data for UPDATE/DELETE operations in the WAL
|
|
||||||
await conn.execute(text("ALTER TABLE notifications REPLICA IDENTITY FULL;"))
|
|
||||||
await conn.execute(
|
|
||||||
text("ALTER TABLE search_source_connectors REPLICA IDENTITY FULL;")
|
|
||||||
)
|
|
||||||
await conn.execute(text("ALTER TABLE documents REPLICA IDENTITY FULL;"))
|
|
||||||
|
|
||||||
# Add tables to Electric SQL publication for replication
|
|
||||||
# Only add if publication exists and table not already in it
|
|
||||||
await conn.execute(
|
|
||||||
text(
|
|
||||||
"""
|
|
||||||
DO $$
|
|
||||||
BEGIN
|
|
||||||
IF EXISTS (SELECT 1 FROM pg_publication WHERE pubname = 'electric_publication_default') THEN
|
|
||||||
-- Add notifications if not already added
|
|
||||||
IF NOT EXISTS (
|
|
||||||
SELECT 1 FROM pg_publication_tables
|
|
||||||
WHERE pubname = 'electric_publication_default'
|
|
||||||
AND tablename = 'notifications'
|
|
||||||
) THEN
|
|
||||||
ALTER PUBLICATION electric_publication_default ADD TABLE notifications;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- Add search_source_connectors if not already added
|
|
||||||
IF NOT EXISTS (
|
|
||||||
SELECT 1 FROM pg_publication_tables
|
|
||||||
WHERE pubname = 'electric_publication_default'
|
|
||||||
AND tablename = 'search_source_connectors'
|
|
||||||
) THEN
|
|
||||||
ALTER PUBLICATION electric_publication_default ADD TABLE search_source_connectors;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- Add documents if not already added
|
|
||||||
IF NOT EXISTS (
|
|
||||||
SELECT 1 FROM pg_publication_tables
|
|
||||||
WHERE pubname = 'electric_publication_default'
|
|
||||||
AND tablename = 'documents'
|
|
||||||
) THEN
|
|
||||||
ALTER PUBLICATION electric_publication_default ADD TABLE documents;
|
|
||||||
END IF;
|
|
||||||
END IF;
|
|
||||||
END
|
|
||||||
$$;
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
|
async def get_async_session() -> AsyncGenerator[AsyncSession, None]:
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ export default function DocumentsTable() {
|
||||||
document_type: "SURFSENSE_DOCS",
|
document_type: "SURFSENSE_DOCS",
|
||||||
document_metadata: { source: doc.source },
|
document_metadata: { source: doc.source },
|
||||||
content: doc.content,
|
content: doc.content,
|
||||||
created_at: doc.created_at || doc.updated_at || new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
search_space_id: -1, // Special value for global docs
|
search_space_id: -1, // Special value for global docs
|
||||||
}));
|
}));
|
||||||
}, [surfsenseDocsResponse]);
|
}, [surfsenseDocsResponse]);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { Upload } from "lucide-react";
|
import { Upload } from "lucide-react";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import {
|
import {
|
||||||
createContext,
|
createContext,
|
||||||
type FC,
|
type FC,
|
||||||
|
|
@ -85,13 +84,11 @@ const DocumentUploadPopupContent: FC<{
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
}> = ({ isOpen, onOpenChange }) => {
|
}> = ({ isOpen, onOpenChange }) => {
|
||||||
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
|
const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom);
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
if (!searchSpaceId) return null;
|
if (!searchSpaceId) return null;
|
||||||
|
|
||||||
const handleSuccess = () => {
|
const handleSuccess = () => {
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
router.push(`/dashboard/${searchSpaceId}/documents`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Bell, Check, CheckCheck, Loader2, AlertCircle, CheckCircle2 } from "lucide-react";
|
import { Bell, CheckCheck, Loader2, AlertCircle, CheckCircle2 } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
|
@ -44,7 +44,7 @@ export function NotificationPopup({
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "in_progress":
|
case "in_progress":
|
||||||
return <Loader2 className="h-4 w-4 text-blue-500 animate-spin" />;
|
return <Loader2 className="h-4 w-4 text-white animate-spin" />;
|
||||||
case "completed":
|
case "completed":
|
||||||
return <CheckCircle2 className="h-4 w-4 text-green-500" />;
|
return <CheckCircle2 className="h-4 w-4 text-green-500" />;
|
||||||
case "failed":
|
case "failed":
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { CheckCircle2, FileType, Info, Loader2, Tag, Upload, X } from "lucide-react";
|
import { CheckCircle2, FileType, Info, Loader2, Tag, Upload, X } from "lucide-react";
|
||||||
import { AnimatePresence, motion } from "motion/react";
|
import { AnimatePresence, motion } from "motion/react";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useCallback, useMemo, useRef, useState } from "react";
|
import { useCallback, useMemo, useRef, useState } from "react";
|
||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
|
|
@ -116,7 +115,6 @@ export function DocumentUploadTab({
|
||||||
onAccordionStateChange,
|
onAccordionStateChange,
|
||||||
}: DocumentUploadTabProps) {
|
}: DocumentUploadTabProps) {
|
||||||
const t = useTranslations("upload_documents");
|
const t = useTranslations("upload_documents");
|
||||||
const router = useRouter();
|
|
||||||
const [files, setFiles] = useState<File[]>([]);
|
const [files, setFiles] = useState<File[]>([]);
|
||||||
const [uploadProgress, setUploadProgress] = useState(0);
|
const [uploadProgress, setUploadProgress] = useState(0);
|
||||||
const [accordionValue, setAccordionValue] = useState<string>("");
|
const [accordionValue, setAccordionValue] = useState<string>("");
|
||||||
|
|
@ -185,7 +183,7 @@ export function DocumentUploadTab({
|
||||||
setUploadProgress(100);
|
setUploadProgress(100);
|
||||||
trackDocumentUploadSuccess(Number(searchSpaceId), files.length);
|
trackDocumentUploadSuccess(Number(searchSpaceId), files.length);
|
||||||
toast(t("upload_initiated"), { description: t("upload_initiated_desc") });
|
toast(t("upload_initiated"), { description: t("upload_initiated_desc") });
|
||||||
onSuccess?.() || router.push(`/dashboard/${searchSpaceId}/documents`);
|
onSuccess?.();
|
||||||
},
|
},
|
||||||
onError: (error: unknown) => {
|
onError: (error: unknown) => {
|
||||||
clearInterval(progressInterval);
|
clearInterval(progressInterval);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue