Maintain local ⭤ remote in sync with automatic AT Protocol parity for Laravel (alpha & unstable)
1<?php
2
3namespace SocialDept\AtpParity\Concerns;
4
5use SocialDept\AtpParity\Contracts\RecordMapper;
6use SocialDept\AtpParity\MapperRegistry;
7use SocialDept\AtpSchema\Data\Data;
8
9/**
10 * Trait for Eloquent models that map to AT Protocol records.
11 *
12 * @mixin \Illuminate\Database\Eloquent\Model
13 */
14trait HasAtpRecord
15{
16 /**
17 * Get the AT Protocol URI for this model.
18 */
19 public function getAtpUri(): ?string
20 {
21 $column = config('parity.columns.uri', 'atp_uri');
22
23 return $this->getAttribute($column);
24 }
25
26 /**
27 * Get the AT Protocol CID for this model.
28 */
29 public function getAtpCid(): ?string
30 {
31 $column = config('parity.columns.cid', 'atp_cid');
32
33 return $this->getAttribute($column);
34 }
35
36 /**
37 * Get the DID from the AT Protocol URI.
38 */
39 public function getAtpDid(): ?string
40 {
41 $uri = $this->getAtpUri();
42
43 if (! $uri) {
44 return null;
45 }
46
47 // at://did:plc:xxx/app.bsky.feed.post/rkey
48 if (preg_match('#^at://([^/]+)/#', $uri, $matches)) {
49 return $matches[1];
50 }
51
52 return null;
53 }
54
55 /**
56 * Get the collection (lexicon NSID) from the AT Protocol URI.
57 */
58 public function getAtpCollection(): ?string
59 {
60 $uri = $this->getAtpUri();
61
62 if (! $uri) {
63 return null;
64 }
65
66 // at://did:plc:xxx/app.bsky.feed.post/rkey
67 if (preg_match('#^at://[^/]+/([^/]+)/#', $uri, $matches)) {
68 return $matches[1];
69 }
70
71 return null;
72 }
73
74 /**
75 * Get the rkey from the AT Protocol URI.
76 */
77 public function getAtpRkey(): ?string
78 {
79 $uri = $this->getAtpUri();
80
81 if (! $uri) {
82 return null;
83 }
84
85 // at://did:plc:xxx/app.bsky.feed.post/rkey
86 if (preg_match('#^at://[^/]+/[^/]+/([^/]+)$#', $uri, $matches)) {
87 return $matches[1];
88 }
89
90 return null;
91 }
92
93 /**
94 * Check if this model has been synced to AT Protocol.
95 */
96 public function hasAtpRecord(): bool
97 {
98 return $this->getAtpUri() !== null;
99 }
100
101 /**
102 * Get the mapper for this model.
103 */
104 public function getAtpMapper(): ?RecordMapper
105 {
106 return app(MapperRegistry::class)->forModel(static::class);
107 }
108
109 /**
110 * Convert this model to an AT Protocol record DTO.
111 */
112 public function toAtpRecord(): ?Data
113 {
114 $mapper = $this->getAtpMapper();
115
116 if (! $mapper) {
117 return null;
118 }
119
120 return $mapper->toRecord($this);
121 }
122
123 /**
124 * Scope to query models that have been synced to AT Protocol.
125 */
126 public function scopeWithAtpRecord($query)
127 {
128 $column = config('parity.columns.uri', 'atp_uri');
129
130 return $query->whereNotNull($column);
131 }
132
133 /**
134 * Scope to query models that have not been synced to AT Protocol.
135 */
136 public function scopeWithoutAtpRecord($query)
137 {
138 $column = config('parity.columns.uri', 'atp_uri');
139
140 return $query->whereNull($column);
141 }
142
143 /**
144 * Scope to find by AT Protocol URI.
145 */
146 public function scopeWhereAtpUri($query, string $uri)
147 {
148 $column = config('parity.columns.uri', 'atp_uri');
149
150 return $query->where($column, $uri);
151 }
152}