~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2

server: A login with no Bluesky profile makes a bare grain profile

Merged chadtmiller.com asked to merge into feature/rust from grain.social/grain:login/bare-profile

Only what this pull adds. It is built on grain runs on a Rust server modelled on knotless, without hatk.

crates/grain-serve/src/login/profile.rs+11−5
@@ -8,7 +8,10 @@
//! profile its `displayName`, `description` and `avatar` (the blob as the
//! Bluesky profile names it), with the `createdAt` of the grain profile
//! there is, or now. One there is is put over, swapped on its CID; else
//! one is made at `self`. Both are read from the account's PDS, which has
//! one is made at `self`. An account with no Bluesky profile and no grain
//! profile gets a bare one, its `createdAt` alone, so every account that
//! logs in has the profile that makes grain track it; one with an unnamed
//! grain profile and no Bluesky profile is left as it is. Both are read from the account's PDS, which has
//! them before the index may; the write goes through the web session
//! ([`crate::write`]), so it is in the index once it is made.
@@ -28,7 +31,8 @@ const SELF: &str = "self";
pub enum Seeded {
/// The grain profile has a name already.
Named,
/// There is no Bluesky profile to make one from.
/// There is no Bluesky profile to name it from, and a grain profile
/// there is was left as it is.
NoBluesky,
/// A grain profile was made, at this URI.
Made(String),
@@ -58,10 +62,12 @@ pub async fn seeded(app: &App, did: &str) -> anyhow::Result<Seeded> {
if grain.as_ref().is_some_and(|(_, g)| named(g)) {
return Ok(Seeded::Named);
}
let Some((_, bsky)) = crate::sync::get_record(&pds, did, BSKY, SELF).await? else {
return Ok(Seeded::NoBluesky);
let bsky = crate::sync::get_record(&pds, did, BSKY, SELF).await?;
let record = match (&bsky, &grain) {
(None, Some(_)) => return Ok(Seeded::NoBluesky),
(None, None) => from_bluesky(&json!({}), None),
(Some((_, bsky)), _) => from_bluesky(bsky, grain.as_ref().map(|(_, g)| g)),
};
let record = from_bluesky(&bsky, grain.as_ref().map(|(_, g)| g));
match grain {
Some((cid, _)) => {
let swaps = [Some(cid), None];
crates/grain-serve/tests/login.rs+18−2
@@ -760,7 +760,7 @@ async fn a_login_seeds_a_grain_profile_from_bluesky() {
assert!(at.get("description").is_none(), "{at}");
// A grain profile with a name is left as it is; with no Bluesky
// profile, nothing is made.
// profile and no grain profile, a bare one is made, and then left.
let c = net::account().await;
let named = json!({ "$type": "social.grain.actor.profile", "displayName": "Carol" });
made(&c, "social.grain.actor.profile", named).await;
@@ -776,8 +776,24 @@ async fn a_login_seeds_a_grain_profile_from_bluesky() {
site.log_in(&mut jar, &d).await;
assert_eq!(seeded(&site.app, &c.did).await.unwrap(), Seeded::Named);
assert_eq!(grain_profile(&c).await.unwrap()["displayName"], "Carol");
let bare = format!("at://{}/social.grain.actor.profile/self", d.did);
for _ in 0..200 {
let held = site
.index
.get::<grain_serve::index::records::Profile>(&bare);
if held.unwrap().is_some() {
break;
}
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
}
assert!(site.index.tracks(&d.did));
let at = grain_profile(&d).await.unwrap();
assert_eq!(at.as_object().unwrap().len(), 2, "{at}");
assert!(grain::lexicon::valid_datetime(
at["createdAt"].as_str().unwrap()
));
assert_eq!(seeded(&site.app, &d.did).await.unwrap(), Seeded::NoBluesky);
assert!(grain_profile(&d).await.is_none());
assert_eq!(grain_profile(&d).await.unwrap(), at);
// An account with no web session is not seeded, and says why.
let none = seeded(&site.app, "did:plc:nobody").await.unwrap_err();