From 8cfdd4c1281414d3e49cd75d71e71a541ffa2afe Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Sat, 13 Jun 2026 20:18:39 -0300 Subject: [PATCH] Centralize AT Protocol NSIDs in BlueskyLexicon The Bluesky lexicon identifiers (createRecord, createSession, feed.post, facet types, etc.) were repeated as magic strings across BlueskyPublisher, BlueskyAnalytics, ConnectionVerifier and BlueskyController, where a typo fails silently at runtime as "Invalid request". Define them once as named constants so a typo is an undefined-constant error instead. Tests keep the literal NSIDs as the independent contract. --- .../Controllers/Auth/BlueskyController.php | 5 ++- app/Services/Social/BlueskyAnalytics.php | 4 +- app/Services/Social/BlueskyLexicon.php | 37 +++++++++++++++++++ app/Services/Social/BlueskyPublisher.php | 18 ++++----- app/Services/Social/ConnectionVerifier.php | 6 +-- 5 files changed, 54 insertions(+), 16 deletions(-) create mode 100644 app/Services/Social/BlueskyLexicon.php diff --git a/app/Http/Controllers/Auth/BlueskyController.php b/app/Http/Controllers/Auth/BlueskyController.php index 4a74d99a..e262daaa 100644 --- a/app/Http/Controllers/Auth/BlueskyController.php +++ b/app/Http/Controllers/Auth/BlueskyController.php @@ -6,6 +6,7 @@ use App\Enums\SocialAccount\Platform as SocialPlatform; use App\Enums\SocialAccount\Status; +use App\Services\Social\BlueskyLexicon; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Http; @@ -57,7 +58,7 @@ public function store(Request $request): View|RedirectResponse try { // Authenticate with Bluesky - $response = Http::post("{$service}/xrpc/com.atproto.server.createSession", [ + $response = Http::post("{$service}/xrpc/".BlueskyLexicon::CREATE_SESSION, [ 'identifier' => $request->identifier, 'password' => $request->password, ]); @@ -81,7 +82,7 @@ public function store(Request $request): View|RedirectResponse // Get profile $profileResponse = Http::withToken(data_get($data, 'accessJwt')) - ->get("{$service}/xrpc/app.bsky.actor.getProfile", [ + ->get("{$service}/xrpc/".BlueskyLexicon::GET_PROFILE, [ 'actor' => data_get($data, 'did'), ]); diff --git a/app/Services/Social/BlueskyAnalytics.php b/app/Services/Social/BlueskyAnalytics.php index 0ad9d750..aecb1198 100644 --- a/app/Services/Social/BlueskyAnalytics.php +++ b/app/Services/Social/BlueskyAnalytics.php @@ -21,7 +21,7 @@ public function fetchPostMetrics(PostPlatform $postPlatform): array } $did = $account->platform_user_id; - $atUri = "at://{$did}/app.bsky.feed.post/{$postPlatform->platform_post_id}"; + $atUri = "at://{$did}/".BlueskyLexicon::FEED_POST."/{$postPlatform->platform_post_id}"; // Read counts (likes, reposts, replies, quotes) live on the AT // Protocol AppView, not the PDS. The user's PDS requires Bearer auth @@ -29,7 +29,7 @@ public function fetchPostMetrics(PostPlatform $postPlatform): array $appView = (string) config('trypost.platforms.bluesky.public_appview'); $response = $this->socialHttp() - ->get("{$appView}/xrpc/app.bsky.feed.getPosts", [ + ->get("{$appView}/xrpc/".BlueskyLexicon::GET_POSTS, [ 'uris' => [$atUri], ]); diff --git a/app/Services/Social/BlueskyLexicon.php b/app/Services/Social/BlueskyLexicon.php new file mode 100644 index 00000000..048f87ae --- /dev/null +++ b/app/Services/Social/BlueskyLexicon.php @@ -0,0 +1,37 @@ + 0) { $embed = [ - '$type' => 'app.bsky.embed.images', + '$type' => BlueskyLexicon::EMBED_IMAGES, 'images' => $images, ]; } @@ -66,7 +66,7 @@ public function publish(PostPlatform $postPlatform): array // Create post record $record = [ - '$type' => 'app.bsky.feed.post', + '$type' => BlueskyLexicon::FEED_POST, 'text' => $text, 'createdAt' => now()->toIso8601ZuluString(), ]; @@ -80,9 +80,9 @@ public function publish(PostPlatform $postPlatform): array } $response = $this->socialHttp()->withToken($account->access_token) - ->post("{$service}/xrpc/com.atproto.repo.createRecord", [ + ->post("{$service}/xrpc/".BlueskyLexicon::CREATE_RECORD, [ 'repo' => $account->platform_user_id, - 'collection' => 'app.bsky.feed.post', + 'collection' => BlueskyLexicon::FEED_POST, 'record' => $record, ]); @@ -140,7 +140,7 @@ private function uploadBlob(SocialAccount $account, string $service, string $url $response = $this->socialHttp()->withToken($account->access_token) ->withHeaders(['Content-Type' => $mimeType]) ->withBody($stream, $mimeType) - ->post("{$service}/xrpc/com.atproto.repo.uploadBlob"); + ->post("{$service}/xrpc/".BlueskyLexicon::UPLOAD_BLOB); if (is_resource($stream)) { fclose($stream); @@ -192,7 +192,7 @@ private function parseFacets(string $text): array ], 'features' => [ [ - '$type' => 'app.bsky.richtext.facet#link', + '$type' => BlueskyLexicon::FACET_LINK, 'uri' => $url, ], ], @@ -232,7 +232,7 @@ private function parseFacets(string $text): array ], 'features' => [ [ - '$type' => 'app.bsky.richtext.facet#mention', + '$type' => BlueskyLexicon::FACET_MENTION, 'did' => $did, ], ], @@ -260,7 +260,7 @@ private function parseFacets(string $text): array ], 'features' => [ [ - '$type' => 'app.bsky.richtext.facet#tag', + '$type' => BlueskyLexicon::FACET_TAG, 'tag' => $tag, ], ], @@ -283,7 +283,7 @@ private function resolveHandleToDid(string $handle): ?string try { $response = $this->socialHttp()->get( - "{$appView}/xrpc/com.atproto.identity.resolveHandle", + "{$appView}/xrpc/".BlueskyLexicon::RESOLVE_HANDLE, ['handle' => $handle], ); diff --git a/app/Services/Social/ConnectionVerifier.php b/app/Services/Social/ConnectionVerifier.php index 7f25319f..74cd90e8 100644 --- a/app/Services/Social/ConnectionVerifier.php +++ b/app/Services/Social/ConnectionVerifier.php @@ -167,7 +167,7 @@ private function refreshBlueskyToken(SocialAccount $account): void try { $response = $client->send(fn () => Http::withToken($account->refresh_token) - ->post("{$service}/xrpc/com.atproto.server.refreshSession")); + ->post("{$service}/xrpc/".BlueskyLexicon::REFRESH_SESSION)); $data = $response->json(); $account->update([ @@ -185,7 +185,7 @@ private function refreshBlueskyToken(SocialAccount $account): void if (isset($account->meta['password'])) { try { - $reauth = $client->send(fn () => Http::post("{$service}/xrpc/com.atproto.server.createSession", [ + $reauth = $client->send(fn () => Http::post("{$service}/xrpc/".BlueskyLexicon::CREATE_SESSION, [ 'identifier' => $account->meta['identifier'], 'password' => decrypt($account->meta['password']), ])); @@ -490,7 +490,7 @@ private function verifyBluesky(SocialAccount $account): bool $service = $account->meta['service'] ?? config('trypost.platforms.bluesky.default_service'); $response = Http::withToken($account->access_token) - ->get("{$service}/xrpc/app.bsky.actor.getProfile", [ + ->get("{$service}/xrpc/".BlueskyLexicon::GET_PROFILE, [ 'actor' => $account->platform_user_id, ]);