feat: Integrate Electric SQL for real-time notifications and enhance PostgreSQL configuration

- Added Electric SQL service to docker-compose for real-time data synchronization.
- Introduced PostgreSQL configuration for logical replication and performance tuning.
- Created scripts for initializing Electric SQL user and electrifying tables.
- Implemented notification model and service in the backend.
- Developed ElectricProvider and useNotifications hook in the frontend for managing notifications.
- Updated environment variables and package dependencies for Electric SQL integration.
This commit is contained in:
Anish Sarkar 2026-01-12 12:47:00 +05:30
parent 383592ce63
commit 82c6dd0221
18 changed files with 1844 additions and 6 deletions

View file

@ -0,0 +1,21 @@
/**
* Get auth token for Electric SQL
* In production, this should get the token from your auth system
*/
export async function getElectricAuthToken(): Promise<string> {
// For insecure mode (development), return empty string
if (process.env.NEXT_PUBLIC_ELECTRIC_AUTH_MODE === 'insecure') {
return ''
}
// In production, get token from your auth system
// This should match your backend auth token
if (typeof window !== 'undefined') {
const token = localStorage.getItem('surfsense_bearer_token')
return token || ''
}
return ''
}

View file

@ -0,0 +1,86 @@
/**
* Electric SQL client setup
* This initializes the Electric SQL client with local PGlite database (PostgreSQL in browser)
*/
import { PGlite } from '@electric-sql/pglite'
import { electrify } from 'electric-sql/pglite'
import { getElectricAuthToken } from './auth'
// We'll generate the schema after running electric:generate
// For now, we'll use a placeholder type
type Electric = any
type Schema = any
let electric: Electric | null = null
let isInitializing = false
let initPromise: Promise<Electric> | null = null
export async function initElectric(): Promise<Electric> {
if (electric) {
return electric
}
if (isInitializing && initPromise) {
return initPromise
}
isInitializing = true
initPromise = (async () => {
try {
const config = {
auth: {
token: await getElectricAuthToken(),
},
url: process.env.NEXT_PUBLIC_ELECTRIC_URL || 'http://localhost:5133',
}
// Initialize PGlite database (PostgreSQL in browser)
// Use idb:// prefix for IndexedDB storage in browser
// relaxedDurability improves responsiveness by scheduling flush after query returns
const conn = new PGlite('idb://surfsense.db', {
relaxedDurability: true,
})
// Import schema (will be generated by electric:generate)
// For now, we'll use a dynamic import that will work after schema generation
let schema: Schema
try {
const schemaModule = await import('./generated/schema')
schema = schemaModule.schema
} catch (error) {
console.warn(
'Electric SQL schema not found. Run "pnpm electric:generate" to generate it.',
error
)
// Return a mock electric client for now
return null as any
}
// Electrify the PGlite database connection
electric = await electrify(conn, schema, config)
console.log('Electric SQL initialized successfully with PGlite')
return electric
} catch (error) {
console.error('Failed to initialize Electric SQL:', error)
throw error
} finally {
isInitializing = false
}
})()
return initPromise
}
export function getElectric(): Electric {
if (!electric) {
throw new Error('Electric not initialized. Call initElectric() first.')
}
return electric
}
export function isElectricInitialized(): boolean {
return electric !== null
}

View file

@ -0,0 +1,19 @@
/**
* Electric SQL configuration
* This file will be used by @electric-sql/cli to generate the schema
*/
export const electricConfig = {
connection: {
host: process.env.ELECTRIC_HOST || 'localhost',
port: parseInt(process.env.ELECTRIC_PORT || '5133', 10),
database: process.env.POSTGRES_DB || 'surfsense',
user: process.env.ELECTRIC_USER || 'electric',
password: process.env.ELECTRIC_PASSWORD || 'electric_password',
},
service: {
host: process.env.ELECTRIC_HOST || 'localhost',
port: parseInt(process.env.ELECTRIC_PORT || '5133', 10),
},
}