Something went wrong. Try again.
atproto Thingiverse but good
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173//! Same-origin client calls for the publish flow.//!//! Thin typed wrappers over [`PolymodelClient`]'s raw app-request helpers for//! the draft store, raw-byte file staging, and image upload. Every call rides//! the browser cookie session (same-origin), so these are authenticated without//! any explicit token handling.//!//! [`resolve_strong_ref`] is the exception: an unauthenticated, identity-resolving//! `get_record` that reads a thing record directly from the target repo's own//! PDS, so a cross-repo "Works with" link resolves.
use http::Method;use jacquard::client::{AgentSessionExt, BasicClient};use jacquard::common::types::string::AtUri;use polymodel_api::com_atproto::repo::strong_ref::StrongRef;use polymodel_api::space_polymodel::library::Image;use polymodel_api::space_polymodel::library::publish_thing::PublishThingOutput;use polymodel_api::space_polymodel::library::stage_file::StageFileOutput;use polymodel_api::space_polymodel::library::thing::Thing;
use crate::client::{PolymodelClient, RawError};use crate::publish::draft::{ DeleteDraftResponse, DraftIdResponse, DraftSummary, DraftThingInput, ImageUploadResponse,};
/// A resolved compatibility target: the `StrongRef` to persist plus the thing's/// display name, shown on the chip.pub struct ResolvedRef { pub strong_ref: StrongRef, pub name: Option<String>,}
/// Resolve a thing `AtUri` to a current `StrongRef` (uri + cid) by reading the/// record directly from the target repo's own PDS.////// Uses an unauthenticated, identity-resolving jacquard agent: `get_record`/// resolves the URI's authority (DID or handle) to its PDS and reads from there,/// so a thing in *another* maker's repo resolves (unlike the same-origin proxy,/// which is pinned to the signed-in user's PDS). A record with no `cid` is an/// error — a `StrongRef` requires one.pub async fn resolve_strong_ref(uri: &AtUri) -> Result<ResolvedRef, RawError> { let agent = BasicClient::unauthenticated(); let response = agent .get_record::<Thing, _>(uri) .await .map_err(|error| RawError::Transport(error.to_string()))?; // `into_output()` fails for typed XRPC errors — a `RecordNotFound` for a // missing record, or a decode/validation error otherwise. Both are surfaced // as a decode failure carrying the real cause rather than a fake HTTP status. let output = response .into_output() .map_err(|error| RawError::Decode(error.to_string()))?; let cid = output .cid .ok_or_else(|| RawError::Decode("record has no cid".to_string()))?; let name = { let n = output.value.name.as_str().trim(); (!n.is_empty()).then(|| n.to_string()) }; Ok(ResolvedRef { strong_ref: StrongRef { cid, uri: output.uri, extra_data: None, }, name, })}
/// stageFile XRPC path (raw-body upload), reused from the existing write path.const STAGE_FILE_PATH: &str = "/xrpc/space.polymodel.library.stageFile";const DRAFTS_PATH: &str = "/app/drafts";const IMAGES_PATH: &str = "/app/images";
/// Create a new server-side draft; returns the server-assigned opaque id.pub async fn create_draft( client: &PolymodelClient, draft: &DraftThingInput,) -> Result<String, RawError> { let response: DraftIdResponse = client .app_json(Method::POST, DRAFTS_PATH, Some(draft)) .await?; Ok(response.draft_id)}
/// Upsert an existing draft by id (autosave).pub async fn put_draft( client: &PolymodelClient, draft_id: &str, draft: &DraftThingInput,) -> Result<(), RawError> { let _: DraftIdResponse = client .app_json(Method::PUT, &draft_path(draft_id), Some(draft)) .await?; Ok(())}
/// Fetch a single draft payload (resume).pub async fn get_draft( client: &PolymodelClient, draft_id: &str,) -> Result<DraftThingInput, RawError> { client .app_json::<(), _>(Method::GET, &draft_path(draft_id), None) .await}
/// List the signed-in actor's draft summaries.pub async fn list_drafts(client: &PolymodelClient) -> Result<Vec<DraftSummary>, RawError> { client .app_json::<(), _>(Method::GET, DRAFTS_PATH, None) .await}
/// Delete a draft; returns whether a row existed.pub async fn delete_draft(client: &PolymodelClient, draft_id: &str) -> Result<bool, RawError> { let response: DeleteDraftResponse = client .app_json::<(), _>(Method::DELETE, &draft_path(draft_id), None) .await?; Ok(response.deleted)}
/// Assemble + publish a draft to the actor's PDS; the server deletes the draft/// only on success.pub async fn publish_draft( client: &PolymodelClient, draft_id: &str,) -> Result<PublishThingOutput, RawError> { let path = format!("{DRAFTS_PATH}/{draft_id}/publish"); client.app_json::<(), _>(Method::POST, &path, None).await}
/// Stage a geometry file: raw bytes → blobs on the actor's PDS + reassembly/// metadata. Returns the `uploadId`/`file` the part references.pub async fn stage_file( client: &PolymodelClient, filename: &str, content_type: &str, bytes: Vec<u8>,) -> Result<StageFileOutput, RawError> { client .app_post_bytes( STAGE_FILE_PATH, content_type, &[("x-polymodel-filename", filename.to_string())], bytes, ) .await}
/// Upload an image and receive a fully-formed `#image` (blobref + server-decoded/// aspect ratio + the given alt text).pub async fn upload_image( client: &PolymodelClient, alt: &str, content_type: &str, bytes: Vec<u8>,) -> Result<Image, RawError> { let response: ImageUploadResponse = client .app_post_bytes( IMAGES_PATH, content_type, &[("x-polymodel-alt", alt.to_string())], bytes, ) .await?; Ok(response.image)}
fn draft_path(draft_id: &str) -> String { format!("{DRAFTS_PATH}/{draft_id}")}