Browse Source
- Add Capacitor-specific DatabaseBackupService implementation - Update PlatformServiceFactory to correctly load platform services - Fix Filesystem API usage in Capacitor backup service - Add detailed logging throughout backup process - Improve error handling and cleanup of temporary files - Update web platform backup implementation - Add proper TypeScript types and documentation This commit implements a robust platform-specific backup service that: - Uses Capacitor's Filesystem and Share APIs for mobile platforms - Properly handles file paths and URIs - Includes comprehensive logging for debugging - Cleans up temporary files after sharing - Maintains consistent interface across platformsdb-backup-cross-platform
33 changed files with 1155 additions and 157 deletions
@ -1 +1,5 @@ |
|||||
PLATFORM=mobile |
PLATFORM=mobile |
||||
|
VITE_ENDORSER_API_URL=https://test-api.endorser.ch/api/v2/claim |
||||
|
VITE_PARTNER_API_URL=https://test-api.partner.ch/api/v2 |
||||
|
VITE_IMAGE_API_URL=https://test-api.images.ch/api/v2 |
||||
|
VITE_PUSH_SERVER_URL=https://test-api.push.ch/api/v2 |
Binary file not shown.
@ -1,2 +1,2 @@ |
|||||
#Fri Mar 21 07:27:50 UTC 2025 |
#Thu Apr 03 08:01:00 UTC 2025 |
||||
gradle.version=8.2.1 |
gradle.version=8.11.1 |
||||
|
Binary file not shown.
@ -0,0 +1,288 @@ |
|||||
|
/** |
||||
|
* @file service.ts |
||||
|
* @description Service interfaces for Decentralized Identifiers (DIDs) |
||||
|
* |
||||
|
* This module defines the service interfaces used in the TimeSafari application. |
||||
|
* Services are associated with DIDs to provide additional functionality and endpoints. |
||||
|
* |
||||
|
* Architecture: |
||||
|
* 1. Base IService interface defines common service properties |
||||
|
* 2. Specialized interfaces extend IService for specific service types |
||||
|
* 3. Services are stored in IIdentifier.services array |
||||
|
* 4. Services are loaded and managed by PlatformServiceFactory |
||||
|
* |
||||
|
* Service Types: |
||||
|
* - EndorserService: Handles claims and endorsements |
||||
|
* - PushNotificationService: Manages web push notifications |
||||
|
* - ProfileService: Handles user profiles and settings |
||||
|
* - BackupService: Manages data backup and restore |
||||
|
* |
||||
|
* @see IIdentifier |
||||
|
* @see PlatformServiceFactory |
||||
|
* @see DatabaseBackupService |
||||
|
*/ |
||||
|
|
||||
|
/** |
||||
|
* Base interface for all DID services |
||||
|
* |
||||
|
* This interface defines the core properties that all services must implement. |
||||
|
* It follows the W3C DID specification for service endpoints. |
||||
|
* |
||||
|
* @example |
||||
|
* const service: IService = { |
||||
|
* id: 'endorser-service', |
||||
|
* type: 'EndorserService', |
||||
|
* serviceEndpoint: 'https://api.endorser.ch', |
||||
|
* description: 'Endorser service for claims and endorsements', |
||||
|
* metadata: { |
||||
|
* version: '1.0.0', |
||||
|
* capabilities: ['claims', 'endorsements'], |
||||
|
* config: { apiServer: 'https://api.endorser.ch' } |
||||
|
* } |
||||
|
* }; |
||||
|
*/ |
||||
|
export interface IService { |
||||
|
/** |
||||
|
* Unique identifier for the service |
||||
|
* @example 'endorser-service' |
||||
|
* @example 'push-notification-service' |
||||
|
*/ |
||||
|
id: string; |
||||
|
|
||||
|
/** |
||||
|
* Type of service |
||||
|
* @example 'EndorserService' |
||||
|
* @example 'PushNotificationService' |
||||
|
*/ |
||||
|
type: string; |
||||
|
|
||||
|
/** |
||||
|
* Endpoint URL for the service |
||||
|
* @example 'https://api.endorser.ch' |
||||
|
* @example 'https://push.timesafari.app' |
||||
|
*/ |
||||
|
serviceEndpoint: string; |
||||
|
|
||||
|
/** |
||||
|
* Optional human-readable description of the service |
||||
|
* @example 'Service for handling claims and endorsements' |
||||
|
*/ |
||||
|
description?: string; |
||||
|
|
||||
|
/** |
||||
|
* Optional metadata for service configuration |
||||
|
*/ |
||||
|
metadata?: { |
||||
|
/** |
||||
|
* Service version in semantic versioning format |
||||
|
* @example '1.0.0' |
||||
|
*/ |
||||
|
version?: string; |
||||
|
|
||||
|
/** |
||||
|
* Array of service capabilities |
||||
|
* @example ['claims', 'endorsements'] |
||||
|
* @example ['notifications', 'alerts'] |
||||
|
*/ |
||||
|
capabilities?: string[]; |
||||
|
|
||||
|
/** |
||||
|
* Service-specific configuration |
||||
|
* @example { apiServer: 'https://api.endorser.ch' } |
||||
|
*/ |
||||
|
config?: Record<string, unknown>; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Service for handling claims and endorsements |
||||
|
* |
||||
|
* This service provides endpoints for: |
||||
|
* - Submitting claims |
||||
|
* - Managing endorsements |
||||
|
* - Checking rate limits |
||||
|
* |
||||
|
* @example |
||||
|
* const endorserService: IEndorserService = { |
||||
|
* id: 'endorser-service', |
||||
|
* type: 'EndorserService', |
||||
|
* serviceEndpoint: 'https://api.endorser.ch', |
||||
|
* metadata: { |
||||
|
* version: '1.0.0', |
||||
|
* capabilities: ['claims', 'endorsements'], |
||||
|
* config: { |
||||
|
* apiServer: 'https://api.endorser.ch', |
||||
|
* rateLimits: { |
||||
|
* claimsPerDay: 100, |
||||
|
* endorsementsPerDay: 1000 |
||||
|
* } |
||||
|
* } |
||||
|
* } |
||||
|
* }; |
||||
|
*/ |
||||
|
export interface IEndorserService extends IService { |
||||
|
/** @override */ |
||||
|
type: "EndorserService"; |
||||
|
|
||||
|
/** @override */ |
||||
|
metadata: { |
||||
|
version: string; |
||||
|
capabilities: ["claims", "endorsements"]; |
||||
|
config: { |
||||
|
/** |
||||
|
* API server URL |
||||
|
* @example 'https://api.endorser.ch' |
||||
|
*/ |
||||
|
apiServer: string; |
||||
|
|
||||
|
/** |
||||
|
* Optional rate limits |
||||
|
*/ |
||||
|
rateLimits?: { |
||||
|
/** |
||||
|
* Maximum claims per day |
||||
|
* @default 100 |
||||
|
*/ |
||||
|
claimsPerDay: number; |
||||
|
|
||||
|
/** |
||||
|
* Maximum endorsements per day |
||||
|
* @default 1000 |
||||
|
*/ |
||||
|
endorsementsPerDay: number; |
||||
|
}; |
||||
|
}; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Service for managing web push notifications |
||||
|
* |
||||
|
* This service provides endpoints for: |
||||
|
* - Registering push subscriptions |
||||
|
* - Sending push notifications |
||||
|
* - Managing notification preferences |
||||
|
* |
||||
|
* @example |
||||
|
* const pushService: IPushNotificationService = { |
||||
|
* id: 'push-service', |
||||
|
* type: 'PushNotificationService', |
||||
|
* serviceEndpoint: 'https://push.timesafari.app', |
||||
|
* metadata: { |
||||
|
* version: '1.0.0', |
||||
|
* capabilities: ['notifications'], |
||||
|
* config: { |
||||
|
* pushServer: 'https://push.timesafari.app', |
||||
|
* vapidPublicKey: '...' |
||||
|
* } |
||||
|
* } |
||||
|
* }; |
||||
|
*/ |
||||
|
export interface IPushNotificationService extends IService { |
||||
|
/** @override */ |
||||
|
type: "PushNotificationService"; |
||||
|
|
||||
|
/** @override */ |
||||
|
metadata: { |
||||
|
version: string; |
||||
|
capabilities: ["notifications"]; |
||||
|
config: { |
||||
|
/** |
||||
|
* Push server URL |
||||
|
* @example 'https://push.timesafari.app' |
||||
|
*/ |
||||
|
pushServer: string; |
||||
|
|
||||
|
/** |
||||
|
* Optional VAPID public key for push notifications |
||||
|
*/ |
||||
|
vapidPublicKey?: string; |
||||
|
}; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Service for managing user profiles and settings |
||||
|
* |
||||
|
* This service provides endpoints for: |
||||
|
* - Managing user profiles |
||||
|
* - Updating user settings |
||||
|
* - Retrieving user preferences |
||||
|
* |
||||
|
* @example |
||||
|
* const profileService: IProfileService = { |
||||
|
* id: 'profile-service', |
||||
|
* type: 'ProfileService', |
||||
|
* serviceEndpoint: 'https://partner-api.endorser.ch', |
||||
|
* metadata: { |
||||
|
* version: '1.0.0', |
||||
|
* capabilities: ['profile', 'settings'], |
||||
|
* config: { |
||||
|
* partnerApiServer: 'https://partner-api.endorser.ch' |
||||
|
* } |
||||
|
* } |
||||
|
* }; |
||||
|
*/ |
||||
|
export interface IProfileService extends IService { |
||||
|
/** @override */ |
||||
|
type: "ProfileService"; |
||||
|
|
||||
|
/** @override */ |
||||
|
metadata: { |
||||
|
version: string; |
||||
|
capabilities: ["profile", "settings"]; |
||||
|
config: { |
||||
|
/** |
||||
|
* Partner API server URL |
||||
|
* @example 'https://partner-api.endorser.ch' |
||||
|
*/ |
||||
|
partnerApiServer: string; |
||||
|
}; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Service for managing data backup and restore operations |
||||
|
* |
||||
|
* This service provides endpoints for: |
||||
|
* - Creating backups |
||||
|
* - Restoring from backups |
||||
|
* - Managing backup storage |
||||
|
* |
||||
|
* @example |
||||
|
* const backupService: IBackupService = { |
||||
|
* id: 'backup-service', |
||||
|
* type: 'BackupService', |
||||
|
* serviceEndpoint: 'https://backup.timesafari.app', |
||||
|
* metadata: { |
||||
|
* version: '1.0.0', |
||||
|
* capabilities: ['backup', 'restore'], |
||||
|
* config: { |
||||
|
* storageType: 'cloud', |
||||
|
* encryptionKey: '...' |
||||
|
* } |
||||
|
* } |
||||
|
* }; |
||||
|
*/ |
||||
|
export interface IBackupService extends IService { |
||||
|
/** @override */ |
||||
|
type: "BackupService"; |
||||
|
|
||||
|
/** @override */ |
||||
|
metadata: { |
||||
|
version: string; |
||||
|
capabilities: ["backup", "restore"]; |
||||
|
config: { |
||||
|
/** |
||||
|
* Storage type for backups |
||||
|
* @default 'local' |
||||
|
*/ |
||||
|
storageType: "local" | "cloud"; |
||||
|
|
||||
|
/** |
||||
|
* Optional encryption key for backups |
||||
|
*/ |
||||
|
encryptionKey?: string; |
||||
|
}; |
||||
|
}; |
||||
|
} |
@ -0,0 +1,69 @@ |
|||||
|
/** |
||||
|
* @file DatabaseBackupService.ts |
||||
|
* @description Capacitor-specific implementation of DatabaseBackupService |
||||
|
* |
||||
|
* This implementation handles database backup operations specifically for Capacitor |
||||
|
* platforms (Android/iOS). It uses the Filesystem and Share plugins to save and |
||||
|
* share the backup file. |
||||
|
*/ |
||||
|
|
||||
|
import { DatabaseBackupService as BaseDatabaseBackupService } from "../../services/DatabaseBackupService"; |
||||
|
import { Filesystem, Directory } from "@capacitor/filesystem"; |
||||
|
import { Share } from "@capacitor/share"; |
||||
|
import { log, error } from "../../utils/logger"; |
||||
|
|
||||
|
export class DatabaseBackupService extends BaseDatabaseBackupService { |
||||
|
/** |
||||
|
* Handles the backup process for Capacitor platforms |
||||
|
* |
||||
|
* @param base64Data - Backup data in base64 format |
||||
|
* @param arrayBuffer - Backup data as ArrayBuffer |
||||
|
* @param blob - Backup data as Blob |
||||
|
*/ |
||||
|
protected async handleBackup( |
||||
|
base64Data: string, |
||||
|
arrayBuffer: ArrayBuffer, |
||||
|
blob: Blob |
||||
|
): Promise<void> { |
||||
|
try { |
||||
|
log("Starting Capacitor backup process"); |
||||
|
|
||||
|
// Create a temporary file
|
||||
|
const fileName = `timesafari-backup-${new Date().toISOString()}.json`; |
||||
|
const filePath = `backups/${fileName}`; |
||||
|
|
||||
|
log("Writing backup file"); |
||||
|
const result = await Filesystem.writeFile({ |
||||
|
path: filePath, |
||||
|
data: base64Data, |
||||
|
directory: Directory.Cache, |
||||
|
recursive: true |
||||
|
}); |
||||
|
|
||||
|
log("Getting file path"); |
||||
|
const fileInfo = await Filesystem.stat({ |
||||
|
path: filePath, |
||||
|
directory: Directory.Cache |
||||
|
}); |
||||
|
|
||||
|
log("Sharing backup file"); |
||||
|
await Share.share({ |
||||
|
title: "TimeSafari Backup", |
||||
|
text: "Your TimeSafari backup file", |
||||
|
url: fileInfo.uri, |
||||
|
dialogTitle: "Share TimeSafari Backup" |
||||
|
}); |
||||
|
|
||||
|
log("Backup shared successfully"); |
||||
|
|
||||
|
// Clean up the temporary file
|
||||
|
await Filesystem.deleteFile({ |
||||
|
path: filePath, |
||||
|
directory: Directory.Cache |
||||
|
}); |
||||
|
} catch (err) { |
||||
|
error("Error during Capacitor backup:", err); |
||||
|
throw err; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -1,26 +1,95 @@ |
|||||
/** |
/** |
||||
* @file DatabaseBackupService.ts |
* @file DatabaseBackupService.ts |
||||
* @description Base service class for handling database backup operations |
* @description Base service class for handling database backup operations |
||||
* @author Matthew Raymer |
* |
||||
* @version 1.0.0 |
* This service implements the Template Method pattern to provide a common interface |
||||
|
* for database backup operations across different platforms. It defines the structure |
||||
|
* of backup operations while delegating platform-specific implementations to subclasses. |
||||
|
* |
||||
|
* Build Process Integration: |
||||
|
* 1. Platform-Specific Implementation: |
||||
|
* - Each platform (web, electron, capacitor) has its own implementation |
||||
|
* - Implementations are loaded dynamically via PlatformServiceFactory |
||||
|
* - Located in ./platforms/{platform}/DatabaseBackupService.ts |
||||
|
* |
||||
|
* 2. Build Configuration: |
||||
|
* - Vite config files (vite.config.*.mts) set VITE_PLATFORM |
||||
|
* - PlatformServiceFactory uses this to load correct implementation |
||||
|
* - Build process creates separate chunks for each platform |
||||
|
* |
||||
|
* 3. Data Handling: |
||||
|
* - Supports multiple data formats (base64, ArrayBuffer, Blob) |
||||
|
* - Platform implementations handle format conversion |
||||
|
* - Ensures consistent backup format across platforms |
||||
|
* |
||||
|
* Usage: |
||||
|
* - Create backup: DatabaseBackupService.createAndShareBackup(data) |
||||
|
* - Platform-specific: new WebDatabaseBackupService().handleBackup() |
||||
|
* |
||||
|
* @see PlatformServiceFactory.ts |
||||
|
* @see vite.config.web.mts |
||||
|
* @see vite.config.electron.mts |
||||
|
* @see vite.config.capacitor.mts |
||||
*/ |
*/ |
||||
|
|
||||
import { PlatformServiceFactory } from "./PlatformServiceFactory"; |
import { PlatformServiceFactory } from "./PlatformServiceFactory"; |
||||
|
import { log, error } from "../utils/logger"; |
||||
|
|
||||
export class DatabaseBackupService { |
export class DatabaseBackupService { |
||||
protected async handleBackup(): Promise<void> { |
/** |
||||
|
* Template method that must be implemented by platform-specific services |
||||
|
* @param base64Data - Backup data in base64 format |
||||
|
* @param arrayBuffer - Backup data as ArrayBuffer |
||||
|
* @param blob - Backup data as Blob |
||||
|
* @throws Error if not implemented by subclass |
||||
|
*/ |
||||
|
protected async handleBackup( |
||||
|
_base64Data: string, |
||||
|
_arrayBuffer: ArrayBuffer, |
||||
|
_blob: Blob, |
||||
|
): Promise<void> { |
||||
throw new Error( |
throw new Error( |
||||
"handleBackup must be implemented by platform-specific service", |
"handleBackup must be implemented by platform-specific service", |
||||
); |
); |
||||
} |
} |
||||
|
|
||||
|
/** |
||||
|
* Factory method to create and share a backup |
||||
|
* Uses PlatformServiceFactory to get platform-specific implementation |
||||
|
* |
||||
|
* @param base64Data - Backup data in base64 format |
||||
|
* @param arrayBuffer - Backup data as ArrayBuffer |
||||
|
* @param blob - Backup data as Blob |
||||
|
* @returns Promise that resolves when backup is complete |
||||
|
*/ |
||||
public static async createAndShareBackup( |
public static async createAndShareBackup( |
||||
base64Data: string, |
base64Data: string, |
||||
arrayBuffer: ArrayBuffer, |
arrayBuffer: ArrayBuffer, |
||||
blob: Blob, |
blob: Blob |
||||
): Promise<void> { |
): Promise<void> { |
||||
|
try { |
||||
|
log('Creating platform-specific backup service'); |
||||
|
const backupService = await this.getPlatformSpecificBackupService(); |
||||
|
log('Backup service created successfully'); |
||||
|
|
||||
|
log('Executing platform-specific backup'); |
||||
|
await backupService.handleBackup(base64Data, arrayBuffer, blob); |
||||
|
log('Backup completed successfully'); |
||||
|
} catch (err) { |
||||
|
error('Error during backup creation:', err); |
||||
|
if (err instanceof Error) { |
||||
|
error('Error details:', { |
||||
|
name: err.name, |
||||
|
message: err.message, |
||||
|
stack: err.stack |
||||
|
}); |
||||
|
} |
||||
|
throw err; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static async getPlatformSpecificBackupService(): Promise<DatabaseBackupService> { |
||||
const factory = PlatformServiceFactory.getInstance(); |
const factory = PlatformServiceFactory.getInstance(); |
||||
const service = await factory.createDatabaseBackupService(); |
return await factory.createDatabaseBackupService(); |
||||
await service.handleBackup(base64Data, arrayBuffer, blob); |
|
||||
} |
} |
||||
} |
} |
||||
|
@ -1,27 +0,0 @@ |
|||||
import { defineConfig, loadEnv } from "vite"; |
|
||||
import baseConfig from "./vite.config.base"; |
|
||||
|
|
||||
export default defineConfig(({ mode }) => { |
|
||||
const env = loadEnv(mode, process.cwd(), ''); |
|
||||
|
|
||||
return { |
|
||||
...baseConfig, |
|
||||
define: { |
|
||||
'import.meta.env.VITE_PLATFORM': JSON.stringify('web'), |
|
||||
}, |
|
||||
build: { |
|
||||
...baseConfig.build, |
|
||||
outDir: 'dist/web', |
|
||||
rollupOptions: { |
|
||||
...baseConfig.build.rollupOptions, |
|
||||
output: { |
|
||||
...baseConfig.build.rollupOptions.output, |
|
||||
manualChunks: { |
|
||||
// Web-specific chunk splitting
|
|
||||
vendor: ['vue', 'vue-router', 'pinia'], |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
}); |
|
Loading…
Reference in new issue