diff --git a/surfsense_backend/app/services/notification_service.py b/surfsense_backend/app/services/notification_service.py index e5387b98b..fd53c7e63 100644 --- a/surfsense_backend/app/services/notification_service.py +++ b/surfsense_backend/app/services/notification_service.py @@ -505,49 +505,3 @@ class NotificationService: }, ) - @staticmethod - async def create_connector_indexed_notification( - session: AsyncSession, - user_id: UUID, - connector_name: str, - connector_type: str, - status: str, - search_space_id: int, - indexed_count: int | None = None, - ) -> Notification: - """ - Create notification when connector indexing completes. - DEPRECATED: Use NotificationService.connector_indexing methods instead. - - Args: - session: Database session - user_id: User to notify - connector_name: Name of the connector - connector_type: Type of connector - status: Indexing status ('SUCCESS', 'FAILED') - search_space_id: Search space ID - indexed_count: Number of items indexed (optional) - - Returns: - Notification: The created notification - """ - status_lower = status.lower() - title = f"Connector indexed: {connector_name}" - message = f'Your connector "{connector_name}" has finished indexing ({status_lower}).' - if indexed_count is not None: - message += f" {indexed_count} items indexed." - - return await NotificationService.create_notification( - session=session, - user_id=user_id, - notification_type="connector_indexed", - title=title, - message=message, - search_space_id=search_space_id, - notification_metadata={ - "connector_name": connector_name, - "connector_type": connector_type, - "status": status, - "indexed_count": indexed_count, - }, - ) diff --git a/surfsense_web/contracts/enums/connector.ts b/surfsense_web/contracts/enums/connector.ts index fc65585e2..aae6e12fd 100644 --- a/surfsense_web/contracts/enums/connector.ts +++ b/surfsense_web/contracts/enums/connector.ts @@ -1,4 +1,5 @@ export enum EnumConnectorName { + SERPER_API = "SERPER_API", TAVILY_API = "TAVILY_API", SEARXNG_API = "SEARXNG_API", LINKUP_API = "LINKUP_API", diff --git a/surfsense_web/contracts/types/document.types.ts b/surfsense_web/contracts/types/document.types.ts index 757c6aeb4..4b2664e58 100644 --- a/surfsense_web/contracts/types/document.types.ts +++ b/surfsense_web/contracts/types/document.types.ts @@ -6,9 +6,11 @@ export const documentTypeEnum = z.enum([ "CRAWLED_URL", "FILE", "SLACK_CONNECTOR", + "TEAMS_CONNECTOR", "NOTION_CONNECTOR", "YOUTUBE_VIDEO", "GITHUB_CONNECTOR", + "LINEAR_CONNECTOR", "DISCORD_CONNECTOR", "JIRA_CONNECTOR", "CONFLUENCE_CONNECTOR", @@ -19,9 +21,9 @@ export const documentTypeEnum = z.enum([ "AIRTABLE_CONNECTOR", "LUMA_CONNECTOR", "ELASTICSEARCH_CONNECTOR", - "LINEAR_CONNECTOR", - "NOTE", + "BOOKSTACK_CONNECTOR", "CIRCLEBACK", + "NOTE", ]); export const document = z.object({ diff --git a/surfsense_web/contracts/types/notification.types.ts b/surfsense_web/contracts/types/notification.types.ts new file mode 100644 index 000000000..bd832c2c9 --- /dev/null +++ b/surfsense_web/contracts/types/notification.types.ts @@ -0,0 +1,108 @@ +import { z } from "zod"; +import { searchSourceConnectorTypeEnum } from "./connector.types"; + +/** + * Notification type enum - matches backend notification types + */ +export const notificationTypeEnum = z.enum([ + "connector_indexing", + "document_processed", +]); + +/** + * Notification status enum - used in metadata + */ +export const notificationStatusEnum = z.enum([ + "in_progress", + "completed", + "failed", +]); + +/** + * Base metadata schema shared across notification types + */ +export const baseNotificationMetadata = z.object({ + operation_id: z.string().optional(), + status: notificationStatusEnum.optional(), + started_at: z.string().optional(), + completed_at: z.string().optional(), +}); + +/** + * Connector indexing metadata schema + */ +export const connectorIndexingMetadata = baseNotificationMetadata.extend({ + connector_id: z.number(), + connector_name: z.string(), + connector_type: searchSourceConnectorTypeEnum, + start_date: z.string().nullable().optional(), + end_date: z.string().nullable().optional(), + indexed_count: z.number(), + total_count: z.number().optional(), + progress_percent: z.number().optional(), + error_message: z.string().nullable().optional(), + // Google Drive specific fields + folder_count: z.number().optional(), + file_count: z.number().optional(), + folder_names: z.array(z.string()).optional(), + file_names: z.array(z.string()).optional(), +}); + +/** + * Document processed metadata schema + */ +export const documentProcessedMetadata = baseNotificationMetadata.extend({ + document_id: z.number(), + status: z.string(), +}); + +/** + * Union of all notification metadata types + * Use this when the notification type is unknown + */ +export const notificationMetadata = z.union([ + connectorIndexingMetadata, + documentProcessedMetadata, + baseNotificationMetadata, +]); + +/** + * Main notification schema + */ +export const notification = z.object({ + id: z.number(), + user_id: z.string(), + search_space_id: z.number().nullable(), + type: notificationTypeEnum, + title: z.string(), + message: z.string(), + read: z.boolean(), + metadata: z.record(z.string(), z.unknown()), + created_at: z.string(), + updated_at: z.string().nullable(), +}); + +/** + * Typed notification schemas for specific notification types + */ +export const connectorIndexingNotification = notification.extend({ + type: z.literal("connector_indexing"), + metadata: connectorIndexingMetadata, +}); + +export const documentProcessedNotification = notification.extend({ + type: z.literal("document_processed"), + metadata: documentProcessedMetadata, +}); + +// Inferred types +export type NotificationTypeEnum = z.infer; +export type NotificationStatusEnum = z.infer; +export type BaseNotificationMetadata = z.infer; +export type ConnectorIndexingMetadata = z.infer; +export type DocumentProcessedMetadata = z.infer; +export type NotificationMetadata = z.infer; +export type Notification = z.infer; +export type ConnectorIndexingNotification = z.infer; +export type DocumentProcessedNotification = z.infer; + diff --git a/surfsense_web/hooks/use-notifications.ts b/surfsense_web/hooks/use-notifications.ts index de19a82fe..d077bd1d8 100644 --- a/surfsense_web/hooks/use-notifications.ts +++ b/surfsense_web/hooks/use-notifications.ts @@ -2,19 +2,9 @@ import { useEffect, useState, useCallback, useRef } from 'react' import { initElectric, isElectricInitialized, type ElectricClient, type SyncHandle } from '@/lib/electric/client' +import type { Notification } from '@/contracts/types/notification.types' -export interface Notification { - id: number - user_id: string - search_space_id: number | null - type: string - title: string - message: string - read: boolean - metadata: Record - created_at: string - updated_at: string | null -} +export type { Notification } from '@/contracts/types/notification.types' export function useNotifications(userId: string | null) { const [electric, setElectric] = useState(null)