diff --git a/src/main/database/connection.ts b/src/main/database/connection.ts index b05dec9..a16e6ae 100644 --- a/src/main/database/connection.ts +++ b/src/main/database/connection.ts @@ -64,7 +64,21 @@ export class DatabaseConnection { return this.localDb; } - async initializeRemote(): Promise { + async initializeRemote(remoteConfig?: { tursoUrl: string; tursoAuthToken: string }): Promise { + // Update config if new credentials are provided + if (remoteConfig) { + // Close existing remote connection if credentials changed + if (this.remoteClient && + (this.config.tursoUrl !== remoteConfig.tursoUrl || + this.config.tursoAuthToken !== remoteConfig.tursoAuthToken)) { + this.remoteClient.close(); + this.remoteClient = null; + this.remoteDb = null; + } + this.config.tursoUrl = remoteConfig.tursoUrl; + this.config.tursoAuthToken = remoteConfig.tursoAuthToken; + } + if (!this.config.tursoUrl || !this.config.tursoAuthToken) { return null; } diff --git a/src/main/engine/SyncEngine.ts b/src/main/engine/SyncEngine.ts index 5f723c8..c224d6a 100644 --- a/src/main/engine/SyncEngine.ts +++ b/src/main/engine/SyncEngine.ts @@ -64,14 +64,17 @@ export class SyncEngine extends EventEmitter { // Start auto-sync if enabled if (config.autoSync && config.syncInterval > 0) { this.syncIntervalId = setInterval( - () => this.sync('bidirectional'), + () => this.fullSync('bidirectional'), config.syncInterval * 60 * 1000 ); } - // Initialize remote database connection + // Initialize remote database connection with the provided credentials const db = getDatabase(); - await db.initializeRemote(); + await db.initializeRemote({ + tursoUrl: config.tursoUrl, + tursoAuthToken: config.tursoAuthToken, + }); this.emit('configured', config); } diff --git a/src/main/ipc/handlers.ts b/src/main/ipc/handlers.ts index b87b075..3e93f09 100644 --- a/src/main/ipc/handlers.ts +++ b/src/main/ipc/handlers.ts @@ -307,7 +307,7 @@ export function registerIpcHandlers(): void { ipcMain.handle('sync:start', async (_, direction: SyncDirection = 'bidirectional') => { const engine = getSyncEngine(); - return engine.sync(direction); + return engine.fullSync(direction); }); ipcMain.handle('sync:getStatus', async () => { @@ -337,9 +337,28 @@ export function registerIpcHandlers(): void { // ============ Dropbox Sync Handlers ============ - ipcMain.handle('dropbox:configure', async (_, config: DropboxSyncConfig) => { + ipcMain.handle('dropbox:configure', async (_, config: Partial) => { const engine = getDropboxSyncEngine(); - return engine.configure(config); + + // Inject local project paths so the engine knows where files live + const projectEngine = getProjectEngine(); + const activeProject = await projectEngine.getActiveProject(); + const projectId = activeProject?.id || 'default'; + const paths = projectEngine.getProjectPaths(projectId); + + const fullConfig: DropboxSyncConfig = { + accessToken: config.accessToken, + appKey: config.appKey || '', + appSecret: config.appSecret, + refreshToken: config.refreshToken, + syncEnabled: config.syncEnabled ?? true, + syncInterval: config.syncInterval ?? 60, + localPostsDir: paths.posts, + localMediaDir: paths.media, + remoteBasePath: config.remoteBasePath ?? (config as any).remotePath ?? '', + }; + + return engine.configure(fullConfig); }); ipcMain.handle('dropbox:isConfigured', async () => { diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 60d8553..74f20b9 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -46,6 +46,31 @@ const App: React.FC = () => { setMedia(media as MediaData[]); } + // Re-configure sync backends from saved credentials + const savedCreds = localStorage.getItem('bds-credentials'); + if (savedCreds) { + try { + const creds = JSON.parse(savedCreds); + if (creds.tursoUrl && creds.tursoToken) { + await window.electronAPI?.sync.configure({ + tursoUrl: creds.tursoUrl, + tursoAuthToken: creds.tursoToken, + autoSync: true, + syncInterval: 5, + }); + } + if (creds.dropboxAccessToken && creds.dropboxAppKey) { + await window.electronAPI?.dropbox?.configure({ + accessToken: creds.dropboxAccessToken, + appKey: creds.dropboxAppKey, + remotePath: creds.dropboxRemotePath || '/blog', + }); + } + } catch (e) { + console.error('Failed to restore sync configuration:', e); + } + } + // Check sync status const syncConfigured = await window.electronAPI?.sync.isConfigured(); setSyncConfigured(syncConfigured || false); @@ -136,10 +161,12 @@ const App: React.FC = () => { ); unsubscribers.push( - window.electronAPI?.on('sync:failed', () => { + window.electronAPI?.on('sync:failed', (errorMsg: unknown) => { setSyncStatus('error'); showToast.dismiss(); - showToast.error('Sync failed'); + const message = typeof errorMsg === 'string' && errorMsg ? errorMsg : 'Unknown error'; + showToast.error(`Sync failed: ${message}`); + console.error('Sync failed:', message); }) || (() => {}) ); diff --git a/src/renderer/components/SettingsView/SettingsView.tsx b/src/renderer/components/SettingsView/SettingsView.tsx index 342f85a..117d860 100644 --- a/src/renderer/components/SettingsView/SettingsView.tsx +++ b/src/renderer/components/SettingsView/SettingsView.tsx @@ -185,6 +185,7 @@ export const SettingsView: React.FC = () => { autoSync: true, syncInterval: 5, }); + useAppStore.getState().setSyncConfigured(true); showToast.success('Cloud sync configured'); } else { showToast.success('Credentials saved'); @@ -232,6 +233,7 @@ export const SettingsView: React.FC = () => { case 'turso': newCreds.tursoUrl = ''; newCreds.tursoToken = ''; + useAppStore.getState().setSyncConfigured(false); break; case 'dropbox': newCreds.dropboxAccessToken = '';