feat: sync wired up
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
@@ -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);
|
||||||
}) || (() => {})
|
}) || (() => {})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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 = '';
|
||||||
|
|||||||
Reference in New Issue
Block a user