Maintain local ⭤ remote in sync with automatic AT Protocol parity for Laravel (alpha & unstable)
1<?php
2
3namespace SocialDept\AtpParity\Commands;
4
5use Illuminate\Console\Command;
6use SocialDept\AtpParity\Events\ImportProgress;
7use SocialDept\AtpParity\Export\ExportService;
8
9use function Laravel\Prompts\error;
10use function Laravel\Prompts\info;
11use function Laravel\Prompts\note;
12
13class ExportCommand extends Command
14{
15 protected $signature = 'parity:export
16 {did : The DID to export}
17 {--output= : Output CAR file path}
18 {--import : Import records to database instead of saving CAR file}
19 {--collection=* : Specific collections to import (with --import)}
20 {--since= : Only export changes since this revision}
21 {--status : Show repository status instead of exporting}';
22
23 protected $description = 'Export an AT Protocol repository as CAR file or import to database';
24
25 public function handle(ExportService $service): int
26 {
27 $did = $this->argument('did');
28
29 if (! str_starts_with($did, 'did:')) {
30 error("Invalid DID format: {$did}");
31
32 return self::FAILURE;
33 }
34
35 if ($this->option('status')) {
36 return $this->handleStatus($service, $did);
37 }
38
39 if ($this->option('import')) {
40 return $this->handleImport($service, $did);
41 }
42
43 return $this->handleExport($service, $did);
44 }
45
46 protected function handleStatus(ExportService $service, string $did): int
47 {
48 info("Getting repository status for {$did}...");
49
50 try {
51 $commit = $service->getLatestCommit($did);
52 $status = $service->getRepoStatus($did);
53
54 $this->table(['Property', 'Value'], [
55 ['DID', $did],
56 ['Latest CID', $commit['cid'] ?? 'N/A'],
57 ['Latest Rev', $commit['rev'] ?? 'N/A'],
58 ['Active', ($status['active'] ?? false) ? 'Yes' : 'No'],
59 ['Status', $status['status'] ?? 'N/A'],
60 ]);
61
62 return self::SUCCESS;
63 } catch (\Throwable $e) {
64 error("Failed to get status: {$e->getMessage()}");
65
66 return self::FAILURE;
67 }
68 }
69
70 protected function handleExport(ExportService $service, string $did): int
71 {
72 $output = $this->option('output') ?? "{$did}.car";
73 $since = $this->option('since');
74
75 // Sanitize filename if using DID as filename
76 $output = str_replace([':', '/'], ['_', '_'], $output);
77
78 info("Exporting repository {$did} to {$output}...");
79
80 $result = $service->exportToFile($did, $output, $since);
81
82 if ($result->isFailed()) {
83 error("Export failed: {$result->error}");
84
85 return self::FAILURE;
86 }
87
88 $size = $this->formatBytes($result->size);
89 info("Exported {$size} to {$output}");
90
91 return self::SUCCESS;
92 }
93
94 protected function handleImport(ExportService $service, string $did): int
95 {
96 $collections = $this->option('collection') ?: null;
97 $collectionDisplay = $collections ? implode(', ', $collections) : 'all registered';
98
99 info("Exporting and importing {$did} ({$collectionDisplay})...");
100
101 $result = $service->exportAndImport(
102 $did,
103 $collections,
104 function (ImportProgress $progress) {
105 $this->output->write("\r");
106 $this->output->write(" [{$progress->collection}] {$progress->recordsSynced} records synced");
107 }
108 );
109
110 $this->output->write("\n");
111
112 if ($result->isFailed()) {
113 error("Import failed: {$result->error}");
114
115 return self::FAILURE;
116 }
117
118 info("Imported {$result->size} records");
119
120 return self::SUCCESS;
121 }
122
123 protected function formatBytes(int $bytes): string
124 {
125 $units = ['B', 'KB', 'MB', 'GB'];
126 $unit = 0;
127
128 while ($bytes >= 1024 && $unit < count($units) - 1) {
129 $bytes /= 1024;
130 $unit++;
131 }
132
133 return round($bytes, 2).' '.$units[$unit];
134 }
135}