diff --git a/.gitignore b/.gitignore index 0a373d8..37def24 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules -exporterTenant.json \ No newline at end of file +sharedTenantExportedWallet.json +DB_per_wallet_exportedWallet.json \ No newline at end of file diff --git a/README.md b/README.md index cc49d1c..f2af6ce 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,20 @@ pnpm cli --strategy export \ --tenant-id ``` +### Export Database Per Wallet + +Export a tenant wallet from DB per wallet to a JSON file: +```bash +pnpm cli --strategy export \ + --wallet-id \ + --wallet-key \ + --storage-type \ + [--postgres-host ] \ + [--postgres-username ] \ + [--postgres-password ] \ + --tenant-id +``` + ### Convert Single Wallet to Multi-Wallet Convert profiles in a sub-wallet to individual wallets: diff --git a/src/exporter.ts b/src/exporter.ts index cc28c6f..6558908 100644 --- a/src/exporter.ts +++ b/src/exporter.ts @@ -6,6 +6,7 @@ import { } from "@credo-ts/askar/build/utils" import { writeFileSync } from "fs" import { join } from "path" +import { AskarMultiWalletDatabaseScheme } from "@credo-ts/askar" /** * Class responsible for exporting wallet data. @@ -14,6 +15,8 @@ export class Exporter { private walletConfig: WalletConfig private fileSystem: FileSystem private profileId: string + private tenantId: string + private databaseScheme: string /** * Constructor for the Exporter class. @@ -25,20 +28,24 @@ export class Exporter { walletConfig, fileSystem, tenantId, + databaseScheme }: { walletConfig: WalletConfig fileSystem: FileSystem tenantId: string + databaseScheme: string }) { this.walletConfig = walletConfig this.fileSystem = fileSystem this.profileId = `tenant-${tenantId}` + this.tenantId = tenantId + this.databaseScheme = databaseScheme } /** * Export the wallet data to a JSON file. */ - public async export(): Promise { + public async export(strategy:string): Promise { try { const askarWalletConfig = await this.getAskarWalletConfig(this.walletConfig) const store = await Store.open({ @@ -47,12 +54,17 @@ export class Exporter { passKey: askarWalletConfig.passKey, }) console.log("🚀 Store opened:", store) - await this.getDecodedItemsAndTags(store) + if (this.databaseScheme === AskarMultiWalletDatabaseScheme.ProfilePerWallet) { + await this.getDecodedItemsAndTags(store); + } else if (this.databaseScheme === AskarMultiWalletDatabaseScheme.DatabasePerWallet) { + await this.processStoreData(store, askarWalletConfig); + } else { + console.warn(`Unknown strategy`); + } } catch (error) { console.error("🚀 ~ Exporter ~ export ~ error:", error) } } - /** * Get decoded items and tags from the store and write them to a JSON file. * @param store - The store instance. @@ -78,11 +90,74 @@ export class Exporter { } // Write filtered data to a JSON file - const outputPath = join(__dirname, "../exportedTenantWallet.json") + const outputPath = join(__dirname, "../sharedTenantExportedWallet.json") writeFileSync(outputPath, JSON.stringify(filteredData, null, 2)) console.log(`Filtered data written to ${outputPath}`) } + private async processStoreData(store: any, askarWalletConfig): Promise { + const data = await this.fetchDataFromStore(store); + const tenantRecord = this.findTenantRecord(data, this.tenantId); + + if (!tenantRecord) { + throw new Error('Tenant not found in the base wallet'); + } + + const { walletId, walletKey } = this.extractWalletConfig(tenantRecord); + console.log(`Wallet ID: ${walletId}`); + console.log(`Wallet Key: ${walletKey}`); + + const baseUri = askarWalletConfig.uri.uri.split('/').slice(0, -1).join('/'); + const tenantUri = `${baseUri}/${walletId}`; + + const tenantStore = await Store.open({ + uri: tenantUri, + keyMethod: keyDerivationMethodToStoreKeyMethod(KeyDerivationMethod.Raw), + passKey: walletKey, + }); + + const tenantData = await this.fetchDataFromStore(tenantStore); + this.processTenantData(tenantData); + } + + private async fetchDataFromStore(store: any){ + try { + const scan = store.scan({}); + const records = await scan.fetchAll(); + return records; + } catch (error) { + console.error("Error fetching data from store:", error); + throw new Error("Failed to fetch data from store"); + } + } + + private findTenantRecord(data, tenantId: string) { + return data.find(record => record.category === 'TenantRecord' && record.name === tenantId); + } + + private extractWalletConfig(record): { walletId: string, walletKey: string } { + const value = JSON.parse(record.value); + return { + walletId: value.config.walletConfig.id, + walletKey: value.config.walletConfig.key, + }; + } + + private processTenantData(data): void { + const filteredData = {}; + + for (const entry of data) { + if (!filteredData[entry.category]) { + filteredData[entry.category] = []; + } + filteredData[entry.category].push(entry); + } + + const outputPath = join(__dirname, "../DB_per_wallet_exportedWallet.json") + writeFileSync(outputPath, JSON.stringify(filteredData, null, 2)) + console.log(`Filtered data written to ${outputPath}`) + } + /** * Get the Askar wallet configuration. * @param walletConfig - The wallet configuration. diff --git a/src/index.ts b/src/index.ts index 89cf3c1..4b5a2d7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,7 @@ program "--strategy ", "Specify strategy to be used. Choose from 'mt-convert-to-mw', or 'import-tenant'.", (value) => { - if (!["mt-convert-to-mw", "import-tenant","export"].includes(value)) { + if (!["mt-convert-to-mw", "import-tenant", "export"].includes(value)) { throw new Error( "Invalid strategy. Choose from 'mt-convert-to-mw', or 'import-tenant'." ) @@ -49,6 +49,19 @@ program "Specify password for postgres storage." ) .option("--tenant-id ", "Specify tenant-id to be migrated.") + .option( + "--database-scheme ", + "Specify database scheme to be migrated. Choose from 'DatabasePerWallet' or 'ProfilePerWallet'.", + (value) => { + if (!["DatabasePerWallet", "ProfilePerWallet"].includes(value)) { + throw new Error( + "Invalid database scheme. Choose from 'DatabasePerWallet' or 'ProfilePerWallet'." + ) + } + return value + }, + "ProfilePerWallet" + ) const main = async () => { const options = program.opts() @@ -57,13 +70,10 @@ const main = async () => { let method let exporterMethod + let storageType:| AskarWalletPostgresStorageConfig| AskarWalletSqliteStorageConfig switch (options.strategy) { case "export": - let storageType: - | AskarWalletPostgresStorageConfig - | AskarWalletSqliteStorageConfig - if (options.storageType === "postgres") { storageType = { type: "postgres", @@ -89,10 +99,10 @@ const main = async () => { storage: storageType, }, tenantId: options.tenantId, + databaseScheme: options.databaseScheme, }) await exporterMethod.export() break - case "mt-convert-to-mw": let storage: | AskarWalletPostgresStorageConfig