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.
This commit is contained in:
Paulo Castellano 2026-06-13 20:18:39 -03:00
parent c86596ada3
commit 8cfdd4c128
5 changed files with 54 additions and 16 deletions

View file

@ -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'),
]);

View file

@ -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],
]);

View file

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\Services\Social;
/**
* AT Protocol / Bluesky lexicon identifiers (NSIDs) used across the Bluesky
* services. Centralized so a typo surfaces as an undefined-constant error
* instead of a silent runtime "Invalid request" from the API.
*/
final class BlueskyLexicon
{
public const RESOLVE_HANDLE = 'com.atproto.identity.resolveHandle';
public const CREATE_RECORD = 'com.atproto.repo.createRecord';
public const UPLOAD_BLOB = 'com.atproto.repo.uploadBlob';
public const CREATE_SESSION = 'com.atproto.server.createSession';
public const REFRESH_SESSION = 'com.atproto.server.refreshSession';
public const FEED_POST = 'app.bsky.feed.post';
public const GET_POSTS = 'app.bsky.feed.getPosts';
public const GET_PROFILE = 'app.bsky.actor.getProfile';
public const EMBED_IMAGES = 'app.bsky.embed.images';
public const FACET_LINK = 'app.bsky.richtext.facet#link';
public const FACET_MENTION = 'app.bsky.richtext.facet#mention';
public const FACET_TAG = 'app.bsky.richtext.facet#tag';
}

View file

@ -54,7 +54,7 @@ public function publish(PostPlatform $postPlatform): array
if (count($images) > 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],
);

View file

@ -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,
]);