Maintain local ⭤ remote in sync with automatic AT Protocol parity for Laravel (alpha & unstable)
1<?php
2
3namespace SocialDept\AtpParity\Export;
4
5use BackedEnum;
6use Generator;
7use SocialDept\AtpClient\Facades\Atp;
8use SocialDept\AtpParity\Import\ImportService;
9use SocialDept\AtpParity\MapperRegistry;
10use Throwable;
11
12/**
13 * Service for exporting AT Protocol repositories.
14 */
15class ExportService
16{
17 public function __construct(
18 protected MapperRegistry $registry,
19 protected ImportService $importService
20 ) {}
21
22 /**
23 * Download a user's repository as CAR data.
24 */
25 public function downloadRepo(string $did, ?string $since = null): RepoExport
26 {
27 $response = Atp::public()->atproto->sync->getRepo($did, $since);
28 $carData = $response->body();
29
30 return new RepoExport(
31 did: $did,
32 carData: $carData,
33 size: strlen($carData),
34 );
35 }
36
37 /**
38 * Export a repository to a local file.
39 */
40 public function exportToFile(string $did, string $path, ?string $since = null): ExportResult
41 {
42 try {
43 $export = $this->downloadRepo($did, $since);
44
45 if (! $export->saveTo($path)) {
46 return ExportResult::failed("Failed to write to file: {$path}");
47 }
48
49 return ExportResult::success($path, $export->size);
50 } catch (Throwable $e) {
51 return ExportResult::failed($e->getMessage());
52 }
53 }
54
55 /**
56 * Export and import records from a repository.
57 *
58 * This downloads the repository and imports records using the normal import pipeline.
59 * It's useful for bulk importing all records from a user.
60 *
61 * @param array<string>|null $collections Specific collections to import (null = all registered)
62 */
63 public function exportAndImport(
64 string $did,
65 ?array $collections = null,
66 ?callable $onProgress = null
67 ): ExportResult {
68 try {
69 // Use the import service to import the user's records
70 $result = $this->importService->importUser($did, $collections, $onProgress);
71
72 if ($result->isFailed()) {
73 return ExportResult::failed($result->error ?? 'Import failed');
74 }
75
76 return ExportResult::success(
77 path: "imported:{$did}",
78 size: $result->recordsSynced
79 );
80 } catch (Throwable $e) {
81 return ExportResult::failed($e->getMessage());
82 }
83 }
84
85 /**
86 * List available blobs for a repository.
87 *
88 * @return Generator<string> Yields blob CIDs
89 */
90 public function listBlobs(string $did, ?string $since = null): Generator
91 {
92 $cursor = null;
93
94 do {
95 $response = Atp::public()->atproto->sync->listBlobs(
96 did: $did,
97 since: $since,
98 limit: 500,
99 cursor: $cursor,
100 );
101
102 foreach ($response->cids as $cid) {
103 yield $cid;
104 }
105
106 $cursor = $response->cursor;
107 } while ($cursor !== null);
108 }
109
110 /**
111 * Download a specific blob.
112 */
113 public function downloadBlob(string $did, string $cid): string
114 {
115 $response = Atp::public()->atproto->sync->getBlob($did, $cid);
116
117 return $response->body();
118 }
119
120 /**
121 * Get the latest commit for a repository.
122 */
123 public function getLatestCommit(string $did): array
124 {
125 $commit = Atp::public()->atproto->sync->getLatestCommit($did);
126
127 return [
128 'cid' => $commit->cid,
129 'rev' => $commit->rev,
130 ];
131 }
132
133 /**
134 * Get the hosting status for a repository.
135 */
136 public function getRepoStatus(string $did): array
137 {
138 $status = Atp::public()->atproto->sync->getRepoStatus($did);
139
140 return $status->toArray();
141 }
142}