mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-15 18:25:18 +02:00
fix: critical timestamp parsing and audit fixes
- Fix timestamp conversion: String(epochMs) → new Date(epochMs).toISOString() in use-messages-sync, use-comments-sync, use-documents, use-inbox. Without this, date comparisons (isEdited, cutoff filters) would fail. - Fix updated_at: undefined → null in use-inbox to match InboxItem type - Fix ZeroProvider: skip Zero connection for unauthenticated users - Clean 30+ stale "Electric SQL" comments in backend Python code
This commit is contained in:
parent
f04ab89418
commit
cf21eaacfc
33 changed files with 62 additions and 57 deletions
|
|
@ -664,7 +664,7 @@ async def index_composio_gmail(
|
||||||
on_heartbeat_callback=on_heartbeat_callback,
|
on_heartbeat_callback=on_heartbeat_callback,
|
||||||
)
|
)
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp so Electric SQL syncs
|
# CRITICAL: Always update timestamp so Zero syncs
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
# Final commit to ensure all documents are persisted
|
# Final commit to ensure all documents are persisted
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,7 @@ async def index_composio_google_calendar(
|
||||||
await task_logger.log_task_success(
|
await task_logger.log_task_success(
|
||||||
log_entry, success_msg, {"events_count": 0}
|
log_entry, success_msg, {"events_count": 0}
|
||||||
)
|
)
|
||||||
# CRITICAL: Update timestamp even when no events found so Electric SQL syncs and UI shows indexed status
|
# CRITICAL: Update timestamp even when no events found so Zero syncs and UI shows indexed status
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
return (
|
return (
|
||||||
|
|
@ -503,7 +503,7 @@ async def index_composio_google_calendar(
|
||||||
documents_failed += 1
|
documents_failed += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
# This ensures the UI shows "Last indexed" instead of "Never indexed"
|
# This ensures the UI shows "Last indexed" instead of "Never indexed"
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -775,7 +775,7 @@ async def index_composio_google_drive(
|
||||||
flag_modified(connector, "config")
|
flag_modified(connector, "config")
|
||||||
logger.info(f"Saved indexing settings hash for connector {connector_id}")
|
logger.info(f"Saved indexing settings hash for connector {connector_id}")
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp so Electric SQL syncs and UI shows indexed status
|
# CRITICAL: Always update timestamp so Zero syncs and UI shows indexed status
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
# Final commit
|
# Final commit
|
||||||
|
|
|
||||||
|
|
@ -712,7 +712,7 @@ class ChatComment(BaseModel, TimestampMixin):
|
||||||
nullable=False,
|
nullable=False,
|
||||||
index=True,
|
index=True,
|
||||||
)
|
)
|
||||||
# Denormalized thread_id for efficient Electric SQL subscriptions (one per thread)
|
# Denormalized thread_id for efficient Zero subscriptions (one per thread)
|
||||||
thread_id = Column(
|
thread_id = Column(
|
||||||
Integer,
|
Integer,
|
||||||
ForeignKey("new_chat_threads.id", ondelete="CASCADE"),
|
ForeignKey("new_chat_threads.id", ondelete="CASCADE"),
|
||||||
|
|
@ -782,7 +782,7 @@ class ChatCommentMention(BaseModel, TimestampMixin):
|
||||||
class ChatSessionState(BaseModel):
|
class ChatSessionState(BaseModel):
|
||||||
"""
|
"""
|
||||||
Tracks real-time session state for shared chat collaboration.
|
Tracks real-time session state for shared chat collaboration.
|
||||||
One record per thread, synced via Electric SQL.
|
One record per thread, synced via Zero.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "chat_session_state"
|
__tablename__ = "chat_session_state"
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ router.include_router(model_list_router) # Dynamic LLM model catalogue from Ope
|
||||||
router.include_router(logs_router)
|
router.include_router(logs_router)
|
||||||
router.include_router(circleback_webhook_router) # Circleback meeting webhooks
|
router.include_router(circleback_webhook_router) # Circleback meeting webhooks
|
||||||
router.include_router(surfsense_docs_router) # Surfsense documentation for citations
|
router.include_router(surfsense_docs_router) # Surfsense documentation for citations
|
||||||
router.include_router(notifications_router) # Notifications with Electric SQL sync
|
router.include_router(notifications_router) # Notifications with Zero sync
|
||||||
router.include_router(composio_router) # Composio OAuth and toolkit management
|
router.include_router(composio_router) # Composio OAuth and toolkit management
|
||||||
router.include_router(public_chat_router) # Public chat sharing and cloning
|
router.include_router(public_chat_router) # Public chat sharing and cloning
|
||||||
router.include_router(incentive_tasks_router) # Incentive tasks for earning free pages
|
router.include_router(incentive_tasks_router) # Incentive tasks for earning free pages
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ async def create_documents_file_upload(
|
||||||
Upload files as documents with real-time status tracking.
|
Upload files as documents with real-time status tracking.
|
||||||
|
|
||||||
Implements 2-phase document status updates for real-time UI feedback:
|
Implements 2-phase document status updates for real-time UI feedback:
|
||||||
- Phase 1: Create all documents with 'pending' status (visible in UI immediately via ElectricSQL)
|
- Phase 1: Create all documents with 'pending' status (visible in UI immediately via Zero)
|
||||||
- Phase 2: Celery processes each file: pending → processing → ready/failed
|
- Phase 2: Celery processes each file: pending → processing → ready/failed
|
||||||
|
|
||||||
Requires DOCUMENTS_CREATE permission.
|
Requires DOCUMENTS_CREATE permission.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
Notifications API routes.
|
Notifications API routes.
|
||||||
These endpoints allow marking notifications as read and fetching older notifications.
|
These endpoints allow marking notifications as read and fetching older notifications.
|
||||||
Electric SQL automatically syncs the changes to all connected clients for recent items.
|
Zero automatically syncs the changes to all connected clients for recent items.
|
||||||
For older items (beyond the sync window), use the list endpoint.
|
For older items (beyond the sync window), use the list endpoint.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -267,7 +267,7 @@ async def get_unread_count(
|
||||||
|
|
||||||
This allows the frontend to calculate:
|
This allows the frontend to calculate:
|
||||||
- older_unread = total_unread - recent_unread (static until reconciliation)
|
- older_unread = total_unread - recent_unread (static until reconciliation)
|
||||||
- Display count = older_unread + live_recent_count (from Electric SQL)
|
- Display count = older_unread + live_recent_count (from Zero)
|
||||||
"""
|
"""
|
||||||
# Calculate cutoff date for sync window
|
# Calculate cutoff date for sync window
|
||||||
cutoff_date = datetime.now(UTC) - timedelta(days=SYNC_WINDOW_DAYS)
|
cutoff_date = datetime.now(UTC) - timedelta(days=SYNC_WINDOW_DAYS)
|
||||||
|
|
@ -344,7 +344,7 @@ async def list_notifications(
|
||||||
List notifications for the current user with pagination.
|
List notifications for the current user with pagination.
|
||||||
|
|
||||||
This endpoint is used as a fallback for older notifications that are
|
This endpoint is used as a fallback for older notifications that are
|
||||||
outside the Electric SQL sync window (2 weeks).
|
outside the Zero sync window (2 weeks).
|
||||||
|
|
||||||
Use `before_date` to paginate through older notifications efficiently.
|
Use `before_date` to paginate through older notifications efficiently.
|
||||||
"""
|
"""
|
||||||
|
|
@ -487,7 +487,7 @@ async def mark_notification_as_read(
|
||||||
"""
|
"""
|
||||||
Mark a single notification as read.
|
Mark a single notification as read.
|
||||||
|
|
||||||
Electric SQL will automatically sync this change to all connected clients.
|
Zero will automatically sync this change to all connected clients.
|
||||||
"""
|
"""
|
||||||
# Verify the notification belongs to the user
|
# Verify the notification belongs to the user
|
||||||
result = await session.execute(
|
result = await session.execute(
|
||||||
|
|
@ -528,7 +528,7 @@ async def mark_all_notifications_as_read(
|
||||||
"""
|
"""
|
||||||
Mark all notifications as read for the current user.
|
Mark all notifications as read for the current user.
|
||||||
|
|
||||||
Electric SQL will automatically sync these changes to all connected clients.
|
Zero will automatically sync these changes to all connected clients.
|
||||||
"""
|
"""
|
||||||
# Update all unread notifications for the user
|
# Update all unread notifications for the user
|
||||||
result = await session.execute(
|
result = await session.execute(
|
||||||
|
|
|
||||||
|
|
@ -1433,7 +1433,7 @@ async def _run_indexing_with_notifications(
|
||||||
)
|
)
|
||||||
await (
|
await (
|
||||||
session.commit()
|
session.commit()
|
||||||
) # Commit to ensure Electric SQL syncs the notification update
|
) # Commit to ensure Zero syncs the notification update
|
||||||
elif documents_processed > 0:
|
elif documents_processed > 0:
|
||||||
# Update notification to storing stage
|
# Update notification to storing stage
|
||||||
if notification:
|
if notification:
|
||||||
|
|
@ -1460,7 +1460,7 @@ async def _run_indexing_with_notifications(
|
||||||
)
|
)
|
||||||
await (
|
await (
|
||||||
session.commit()
|
session.commit()
|
||||||
) # Commit to ensure Electric SQL syncs the notification update
|
) # Commit to ensure Zero syncs the notification update
|
||||||
else:
|
else:
|
||||||
# No new documents processed - check if this is an error or just no changes
|
# No new documents processed - check if this is an error or just no changes
|
||||||
if error_or_warning:
|
if error_or_warning:
|
||||||
|
|
@ -1486,7 +1486,7 @@ async def _run_indexing_with_notifications(
|
||||||
if is_duplicate_warning or is_empty_result or is_info_warning:
|
if is_duplicate_warning or is_empty_result or is_info_warning:
|
||||||
# These are success cases - sync worked, just found nothing new
|
# These are success cases - sync worked, just found nothing new
|
||||||
logger.info(f"Indexing completed successfully: {error_or_warning}")
|
logger.info(f"Indexing completed successfully: {error_or_warning}")
|
||||||
# Still update timestamp so ElectricSQL syncs and clears "Syncing" UI
|
# Still update timestamp so Zero syncs and clears "Syncing" UI
|
||||||
if update_timestamp_func:
|
if update_timestamp_func:
|
||||||
await update_timestamp_func(session, connector_id)
|
await update_timestamp_func(session, connector_id)
|
||||||
await session.commit() # Commit timestamp update
|
await session.commit() # Commit timestamp update
|
||||||
|
|
@ -1509,7 +1509,7 @@ async def _run_indexing_with_notifications(
|
||||||
)
|
)
|
||||||
await (
|
await (
|
||||||
session.commit()
|
session.commit()
|
||||||
) # Commit to ensure Electric SQL syncs the notification update
|
) # Commit to ensure Zero syncs the notification update
|
||||||
else:
|
else:
|
||||||
# Actual failure
|
# Actual failure
|
||||||
logger.error(f"Indexing failed: {error_or_warning}")
|
logger.error(f"Indexing failed: {error_or_warning}")
|
||||||
|
|
@ -1525,13 +1525,13 @@ async def _run_indexing_with_notifications(
|
||||||
)
|
)
|
||||||
await (
|
await (
|
||||||
session.commit()
|
session.commit()
|
||||||
) # Commit to ensure Electric SQL syncs the notification update
|
) # Commit to ensure Zero syncs the notification update
|
||||||
else:
|
else:
|
||||||
# Success - just no new documents to index (all skipped/unchanged)
|
# Success - just no new documents to index (all skipped/unchanged)
|
||||||
logger.info(
|
logger.info(
|
||||||
"Indexing completed: No new documents to process (all up to date)"
|
"Indexing completed: No new documents to process (all up to date)"
|
||||||
)
|
)
|
||||||
# Still update timestamp so ElectricSQL syncs and clears "Syncing" UI
|
# Still update timestamp so Zero syncs and clears "Syncing" UI
|
||||||
if update_timestamp_func:
|
if update_timestamp_func:
|
||||||
await update_timestamp_func(session, connector_id)
|
await update_timestamp_func(session, connector_id)
|
||||||
await session.commit() # Commit timestamp update
|
await session.commit() # Commit timestamp update
|
||||||
|
|
@ -1547,7 +1547,7 @@ async def _run_indexing_with_notifications(
|
||||||
)
|
)
|
||||||
await (
|
await (
|
||||||
session.commit()
|
session.commit()
|
||||||
) # Commit to ensure Electric SQL syncs the notification update
|
) # Commit to ensure Zero syncs the notification update
|
||||||
except SoftTimeLimitExceeded:
|
except SoftTimeLimitExceeded:
|
||||||
# Celery soft time limit was reached - task is about to be killed
|
# Celery soft time limit was reached - task is about to be killed
|
||||||
# Gracefully save progress and mark as interrupted
|
# Gracefully save progress and mark as interrupted
|
||||||
|
|
@ -2650,7 +2650,7 @@ async def run_composio_indexing(
|
||||||
Run Composio connector indexing with real-time notifications.
|
Run Composio connector indexing with real-time notifications.
|
||||||
|
|
||||||
This wraps the Composio indexer with the notification system so that
|
This wraps the Composio indexer with the notification system so that
|
||||||
Electric SQL can sync indexing progress to the frontend in real-time.
|
Zero can sync indexing progress to the frontend in real-time.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
session: Database session
|
session: Database session
|
||||||
|
|
|
||||||
|
|
@ -456,7 +456,7 @@ async def create_comment(
|
||||||
thread = message.thread
|
thread = message.thread
|
||||||
comment = ChatComment(
|
comment = ChatComment(
|
||||||
message_id=message_id,
|
message_id=message_id,
|
||||||
thread_id=thread.id, # Denormalized for efficient Electric subscriptions
|
thread_id=thread.id, # Denormalized for efficient per-thread sync
|
||||||
author_id=user.id,
|
author_id=user.id,
|
||||||
content=content,
|
content=content,
|
||||||
)
|
)
|
||||||
|
|
@ -569,7 +569,7 @@ async def create_reply(
|
||||||
thread = parent_comment.message.thread
|
thread = parent_comment.message.thread
|
||||||
reply = ChatComment(
|
reply = ChatComment(
|
||||||
message_id=parent_comment.message_id,
|
message_id=parent_comment.message_id,
|
||||||
thread_id=thread.id, # Denormalized for efficient Electric subscriptions
|
thread_id=thread.id, # Denormalized for efficient per-thread sync
|
||||||
parent_id=comment_id,
|
parent_id=comment_id,
|
||||||
author_id=user.id,
|
author_id=user.id,
|
||||||
content=content,
|
content=content,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
"""Service for creating and managing notifications with Electric SQL sync."""
|
"""Service for creating and managing notifications with Zero sync."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
|
|
@ -1045,7 +1045,7 @@ class PageLimitNotificationHandler(BaseNotificationHandler):
|
||||||
|
|
||||||
|
|
||||||
class NotificationService:
|
class NotificationService:
|
||||||
"""Service for creating and managing notifications that sync via Electric SQL."""
|
"""Service for creating and managing notifications that sync via Zero."""
|
||||||
|
|
||||||
# Handler instances
|
# Handler instances
|
||||||
connector_indexing = ConnectorIndexingNotificationHandler()
|
connector_indexing = ConnectorIndexingNotificationHandler()
|
||||||
|
|
@ -1065,7 +1065,7 @@ class NotificationService:
|
||||||
notification_metadata: dict[str, Any] | None = None,
|
notification_metadata: dict[str, Any] | None = None,
|
||||||
) -> Notification:
|
) -> Notification:
|
||||||
"""
|
"""
|
||||||
Create a notification - Electric SQL will automatically sync it to frontend.
|
Create a notification - Zero will automatically sync it to frontend.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
session: Database session
|
session: Database session
|
||||||
|
|
|
||||||
|
|
@ -887,7 +887,7 @@ async def _process_file_with_document(
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Set status to PROCESSING (shows spinner in UI via ElectricSQL)
|
# Set status to PROCESSING (shows spinner in UI via Zero)
|
||||||
document.status = DocumentStatus.processing()
|
document.status = DocumentStatus.processing()
|
||||||
await session.commit()
|
await session.commit()
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|
@ -951,7 +951,7 @@ async def _process_file_with_document(
|
||||||
):
|
):
|
||||||
page_limit_error = e.__cause__
|
page_limit_error = e.__cause__
|
||||||
|
|
||||||
# Mark document as failed (shows error in UI via ElectricSQL)
|
# Mark document as failed (shows error in UI via Zero)
|
||||||
error_message = str(e)[:500]
|
error_message = str(e)[:500]
|
||||||
document.status = DocumentStatus.failed(error_message)
|
document.status = DocumentStatus.failed(error_message)
|
||||||
document.updated_at = get_current_timestamp()
|
document.updated_at = get_current_timestamp()
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ async def index_airtable_records(
|
||||||
await task_logger.log_task_success(
|
await task_logger.log_task_success(
|
||||||
log_entry, success_msg, {"bases_count": 0}
|
log_entry, success_msg, {"bases_count": 0}
|
||||||
)
|
)
|
||||||
# CRITICAL: Update timestamp even when no bases found so Electric SQL syncs
|
# CRITICAL: Update timestamp even when no bases found so Zero syncs
|
||||||
await update_connector_last_indexed(
|
await update_connector_last_indexed(
|
||||||
session, connector, update_last_indexed
|
session, connector, update_last_indexed
|
||||||
)
|
)
|
||||||
|
|
@ -460,7 +460,7 @@ async def index_airtable_records(
|
||||||
documents_failed += 1
|
documents_failed += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
total_processed = documents_indexed
|
total_processed = documents_indexed
|
||||||
|
|
|
||||||
|
|
@ -462,7 +462,7 @@ async def index_bookstack_pages(
|
||||||
documents_failed += 1
|
documents_failed += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
# This ensures the UI shows "Last indexed" instead of "Never indexed"
|
# This ensures the UI shows "Last indexed" instead of "Never indexed"
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -470,7 +470,7 @@ async def index_clickup_tasks(
|
||||||
|
|
||||||
total_processed = documents_indexed
|
total_processed = documents_indexed
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
# This ensures the UI shows "Last indexed" instead of "Never indexed"
|
# This ensures the UI shows "Last indexed" instead of "Never indexed"
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -442,7 +442,7 @@ async def index_confluence_pages(
|
||||||
documents_failed += 1
|
documents_failed += 1
|
||||||
continue # Skip this page and continue with others
|
continue # Skip this page and continue with others
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
# This ensures the UI shows "Last indexed" instead of "Never indexed"
|
# This ensures the UI shows "Last indexed" instead of "Never indexed"
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -718,7 +718,7 @@ async def index_discord_messages(
|
||||||
documents_failed += 1
|
documents_failed += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
# Final commit for any remaining documents not yet committed in batches
|
# Final commit for any remaining documents not yet committed in batches
|
||||||
|
|
|
||||||
|
|
@ -413,7 +413,7 @@ async def index_elasticsearch_documents(
|
||||||
documents_failed += 1
|
documents_failed += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
# This ensures the UI shows "Last indexed" instead of "Never indexed"
|
# This ensures the UI shows "Last indexed" instead of "Never indexed"
|
||||||
if update_last_indexed:
|
if update_last_indexed:
|
||||||
connector.last_indexed_at = (
|
connector.last_indexed_at = (
|
||||||
|
|
|
||||||
|
|
@ -451,7 +451,7 @@ async def index_github_repos(
|
||||||
documents_failed += 1
|
documents_failed += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
# Final commit
|
# Final commit
|
||||||
|
|
|
||||||
|
|
@ -554,7 +554,7 @@ async def index_google_calendar_events(
|
||||||
documents_failed += 1
|
documents_failed += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
# Final commit for any remaining documents not yet committed in batches
|
# Final commit for any remaining documents not yet committed in batches
|
||||||
|
|
|
||||||
|
|
@ -477,7 +477,7 @@ async def index_google_gmail_messages(
|
||||||
documents_failed += 1
|
documents_failed += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
# Final commit for any remaining documents not yet committed in batches
|
# Final commit for any remaining documents not yet committed in batches
|
||||||
|
|
|
||||||
|
|
@ -422,7 +422,7 @@ async def index_jira_issues(
|
||||||
documents_failed += 1
|
documents_failed += 1
|
||||||
continue # Skip this issue and continue with others
|
continue # Skip this issue and continue with others
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
# This ensures the UI shows "Last indexed" instead of "Never indexed"
|
# This ensures the UI shows "Last indexed" instead of "Never indexed"
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -463,7 +463,7 @@ async def index_linear_issues(
|
||||||
documents_failed += 1
|
documents_failed += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
# Final commit for any remaining documents not yet committed in batches
|
# Final commit for any remaining documents not yet committed in batches
|
||||||
|
|
|
||||||
|
|
@ -520,7 +520,7 @@ async def index_luma_events(
|
||||||
documents_failed += 1
|
documents_failed += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
# This ensures the UI shows "Last indexed" instead of "Never indexed"
|
# This ensures the UI shows "Last indexed" instead of "Never indexed"
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@ async def index_notion_pages(
|
||||||
{"pages_found": 0},
|
{"pages_found": 0},
|
||||||
)
|
)
|
||||||
logger.info("No Notion pages found to index")
|
logger.info("No Notion pages found to index")
|
||||||
# CRITICAL: Update timestamp even when no pages found so Electric SQL syncs
|
# CRITICAL: Update timestamp even when no pages found so Zero syncs
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
await notion_client.close()
|
await notion_client.close()
|
||||||
|
|
@ -506,7 +506,7 @@ async def index_notion_pages(
|
||||||
documents_failed += 1
|
documents_failed += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
total_processed = documents_indexed
|
total_processed = documents_indexed
|
||||||
|
|
|
||||||
|
|
@ -599,7 +599,7 @@ async def index_obsidian_vault(
|
||||||
failed_count += 1
|
failed_count += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
# Final commit for any remaining documents not yet committed in batches
|
# Final commit for any remaining documents not yet committed in batches
|
||||||
|
|
|
||||||
|
|
@ -256,7 +256,7 @@ async def index_slack_messages(
|
||||||
f"No Slack channels found for connector {connector_id}",
|
f"No Slack channels found for connector {connector_id}",
|
||||||
{"channels_found": 0},
|
{"channels_found": 0},
|
||||||
)
|
)
|
||||||
# CRITICAL: Update timestamp even when no channels found so Electric SQL syncs
|
# CRITICAL: Update timestamp even when no channels found so Zero syncs
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
return 0, None # Return None (not error) when no channels found
|
return 0, None # Return None (not error) when no channels found
|
||||||
|
|
@ -593,7 +593,7 @@ async def index_slack_messages(
|
||||||
documents_failed += 1
|
documents_failed += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
# Final commit for any remaining documents not yet committed in batches
|
# Final commit for any remaining documents not yet committed in batches
|
||||||
|
|
|
||||||
|
|
@ -249,7 +249,7 @@ async def index_teams_messages(
|
||||||
f"No Teams found for connector {connector_id}",
|
f"No Teams found for connector {connector_id}",
|
||||||
{"teams_found": 0},
|
{"teams_found": 0},
|
||||||
)
|
)
|
||||||
# CRITICAL: Update timestamp even when no teams found so Electric SQL syncs
|
# CRITICAL: Update timestamp even when no teams found so Zero syncs
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
return 0, None # Return None (not error) when no items found
|
return 0, None # Return None (not error) when no items found
|
||||||
|
|
@ -635,7 +635,7 @@ async def index_teams_messages(
|
||||||
documents_failed += 1
|
documents_failed += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
# Final commit for any remaining documents not yet committed in batches
|
# Final commit for any remaining documents not yet committed in batches
|
||||||
|
|
|
||||||
|
|
@ -444,7 +444,7 @@ async def index_crawled_urls(
|
||||||
|
|
||||||
total_processed = documents_indexed + documents_updated
|
total_processed = documents_indexed + documents_updated
|
||||||
|
|
||||||
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Electric SQL syncs
|
# CRITICAL: Always update timestamp (even if 0 documents indexed) so Zero syncs
|
||||||
await update_connector_last_indexed(session, connector, update_last_indexed)
|
await update_connector_last_indexed(session, connector, update_last_indexed)
|
||||||
|
|
||||||
# Final commit for any remaining documents not yet committed in batches
|
# Final commit for any remaining documents not yet committed in batches
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,13 @@ const cacheURL = process.env.NEXT_PUBLIC_ZERO_CACHE_URL || "http://localhost:484
|
||||||
|
|
||||||
export function ZeroProvider({ children }: { children: React.ReactNode }) {
|
export function ZeroProvider({ children }: { children: React.ReactNode }) {
|
||||||
const { data: user } = useAtomValue(currentUserAtom);
|
const { data: user } = useAtomValue(currentUserAtom);
|
||||||
const userID = user?.id ? String(user.id) : "";
|
|
||||||
const context = user?.id ? { userId: String(user.id) } : undefined;
|
if (!user?.id) {
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userID = String(user.id);
|
||||||
|
const context = { userId: userID };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ZeroReactProvider {...{ userID, context, cacheURL, schema, queries }}>
|
<ZeroReactProvider {...{ userID, context, cacheURL, schema, queries }}>
|
||||||
|
|
|
||||||
|
|
@ -203,8 +203,8 @@ export function useCommentsSync(threadId: number | null) {
|
||||||
parent_id: c.parentId ?? null,
|
parent_id: c.parentId ?? null,
|
||||||
author_id: c.authorId ?? null,
|
author_id: c.authorId ?? null,
|
||||||
content: c.content,
|
content: c.content,
|
||||||
created_at: String(c.createdAt),
|
created_at: new Date(c.createdAt).toISOString(),
|
||||||
updated_at: String(c.updatedAt),
|
updated_at: new Date(c.updatedAt).toISOString(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
updateReactQueryCache(rows);
|
updateReactQueryCache(rows);
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,7 @@ export function useDocuments(
|
||||||
created_by_email: doc.createdById
|
created_by_email: doc.createdById
|
||||||
? (emailCacheRef.current.get(doc.createdById) ?? null)
|
? (emailCacheRef.current.get(doc.createdById) ?? null)
|
||||||
: null,
|
: null,
|
||||||
created_at: String(doc.createdAt),
|
created_at: new Date(doc.createdAt).toISOString(),
|
||||||
status: (doc.status as unknown as DocumentStatusType) ?? { state: "ready" },
|
status: (doc.status as unknown as DocumentStatusType) ?? { state: "ready" },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -154,8 +154,8 @@ export function useInbox(
|
||||||
message: item.message,
|
message: item.message,
|
||||||
read: item.read,
|
read: item.read,
|
||||||
metadata: item.metadata as unknown as Record<string, unknown>,
|
metadata: item.metadata as unknown as Record<string, unknown>,
|
||||||
created_at: String(item.createdAt),
|
created_at: new Date(item.createdAt).toISOString(),
|
||||||
updated_at: item.updatedAt ? String(item.updatedAt) : undefined,
|
updated_at: item.updatedAt ? new Date(item.updatedAt).toISOString() : null,
|
||||||
} as InboxItem));
|
} as InboxItem));
|
||||||
|
|
||||||
let updated = prev.map((existing) => {
|
let updated = prev.map((existing) => {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export function useMessagesSync(
|
||||||
role: msg.role,
|
role: msg.role,
|
||||||
content: msg.content,
|
content: msg.content,
|
||||||
author_id: msg.authorId ?? null,
|
author_id: msg.authorId ?? null,
|
||||||
created_at: String(msg.createdAt),
|
created_at: new Date(msg.createdAt).toISOString(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
onMessagesUpdateRef.current(mapped);
|
onMessagesUpdateRef.current(mapped);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue