Files
tomdebone 3064d3d8b7 feat(pds-server): app.bsky.actor.profile get/set XRPC endpoints
Read / read-modify-write the authenticated user's profile record
through the standard atproto repo-write path. Auth is checked via
the existing bearer-token helper; the request body's
`display_name` / `description` / `avatar_blob_cid` /
`banner_blob_cid` overlay the existing record (None fields
preserve the old value).

Blob CID ownership: any supplied avatar/banner CID is looked up
in the `blobs` table with `WHERE cid = $1 AND did = $2`,
rejecting with 400 if the blob isn't owned by the authenticated
user. The resolved `mime_type` / `size` is written into the
record so consumers reading `size` for layout decisions get the
real value (previously hardcoded to "image/png" / 0).

Best-effort push to the AppView via `AppViewPushClient::push_profile`
so the `profiles` cache reflects the new avatar / display name
without waiting for the Jetstream replay path.

Wire shape:

  GET  /xrpc/app.bsky.actor.profile.get
       → { did, handle, profile: { displayName, description, ... } | null }

  POST /xrpc/app.bsky.actor.profile.set
       body: { displayName, description, avatarBlobCid, bannerBlobCid }
       → same shape as get

Includes `merge_profile_fields` testable helper (4 unit tests
locking the camelCase wire shape and the merge semantics).

The AppView-side indexer arm and the Tauri UI land in the
following two commits.
2026-07-18 17:56:28 +02:00

26 lines
656 B
JSON

{
"lexicon": 1,
"id": "app.bsky.actor.profile",
"defs": {
"main": {
"type": "record",
"key": "tid",
"record": {
"type": "object",
"properties": {
"displayName": { "type": "string", "maxLength": 64, "maxGraphemes": 64 },
"description": { "type": "string", "maxLength": 300, "maxGraphemes": 300 },
"avatar": {
"type": "blob",
"accept": ["image/png", "image/jpeg", "image/webp", "image/gif"]
},
"banner": {
"type": "blob",
"accept": ["image/png", "image/jpeg", "image/webp"]
}
}
}
}
}
}