feat: sync wired up

This commit is contained in:
2026-02-11 06:54:10 +01:00
parent 503d895588
commit 3126be4e90
5 changed files with 74 additions and 9 deletions

View File

@@ -64,7 +64,21 @@ export class DatabaseConnection {
return this.localDb; return this.localDb;
} }
async initializeRemote(): Promise<DrizzleDB | null> { async initializeRemote(remoteConfig?: { tursoUrl: string; tursoAuthToken: string }): Promise<DrizzleDB | null> {
// 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) { if (!this.config.tursoUrl || !this.config.tursoAuthToken) {
return null; return null;
} }

View File

@@ -64,14 +64,17 @@ export class SyncEngine extends EventEmitter {
// Start auto-sync if enabled // Start auto-sync if enabled
if (config.autoSync && config.syncInterval > 0) { if (config.autoSync && config.syncInterval > 0) {
this.syncIntervalId = setInterval( this.syncIntervalId = setInterval(
() => this.sync('bidirectional'), () => this.fullSync('bidirectional'),
config.syncInterval * 60 * 1000 config.syncInterval * 60 * 1000
); );
} }
// Initialize remote database connection // Initialize remote database connection with the provided credentials
const db = getDatabase(); const db = getDatabase();
await db.initializeRemote(); await db.initializeRemote({
tursoUrl: config.tursoUrl,
tursoAuthToken: config.tursoAuthToken,
});
this.emit('configured', config); this.emit('configured', config);
} }

View File

@@ -307,7 +307,7 @@ export function registerIpcHandlers(): void {
ipcMain.handle('sync:start', async (_, direction: SyncDirection = 'bidirectional') => { ipcMain.handle('sync:start', async (_, direction: SyncDirection = 'bidirectional') => {
const engine = getSyncEngine(); const engine = getSyncEngine();
return engine.sync(direction); return engine.fullSync(direction);
}); });
ipcMain.handle('sync:getStatus', async () => { ipcMain.handle('sync:getStatus', async () => {
@@ -337,9 +337,28 @@ export function registerIpcHandlers(): void {
// ============ Dropbox Sync Handlers ============ // ============ Dropbox Sync Handlers ============
ipcMain.handle('dropbox:configure', async (_, config: DropboxSyncConfig) => { ipcMain.handle('dropbox:configure', async (_, config: Partial<DropboxSyncConfig>) => {
const engine = getDropboxSyncEngine(); 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 () => { ipcMain.handle('dropbox:isConfigured', async () => {

View File

@@ -46,6 +46,31 @@ const App: React.FC = () => {
setMedia(media as MediaData[]); 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 // Check sync status
const syncConfigured = await window.electronAPI?.sync.isConfigured(); const syncConfigured = await window.electronAPI?.sync.isConfigured();
setSyncConfigured(syncConfigured || false); setSyncConfigured(syncConfigured || false);
@@ -136,10 +161,12 @@ const App: React.FC = () => {
); );
unsubscribers.push( unsubscribers.push(
window.electronAPI?.on('sync:failed', () => { window.electronAPI?.on('sync:failed', (errorMsg: unknown) => {
setSyncStatus('error'); setSyncStatus('error');
showToast.dismiss(); 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);
}) || (() => {}) }) || (() => {})
); );

View File

@@ -185,6 +185,7 @@ export const SettingsView: React.FC = () => {
autoSync: true, autoSync: true,
syncInterval: 5, syncInterval: 5,
}); });
useAppStore.getState().setSyncConfigured(true);
showToast.success('Cloud sync configured'); showToast.success('Cloud sync configured');
} else { } else {
showToast.success('Credentials saved'); showToast.success('Credentials saved');
@@ -232,6 +233,7 @@ export const SettingsView: React.FC = () => {
case 'turso': case 'turso':
newCreds.tursoUrl = ''; newCreds.tursoUrl = '';
newCreds.tursoToken = ''; newCreds.tursoToken = '';
useAppStore.getState().setSyncConfigured(false);
break; break;
case 'dropbox': case 'dropbox':
newCreds.dropboxAccessToken = ''; newCreds.dropboxAccessToken = '';