diff --git a/profiles/src/apub/actions/apub/note.rs b/profiles/src/apub/actions/apub/note.rs index 0543593..4596534 100644 --- a/profiles/src/apub/actions/apub/note.rs +++ b/profiles/src/apub/actions/apub/note.rs @@ -54,6 +54,7 @@ pub(super) fn note( } let published = note.published().req("published")?; + let updated = note.updated(); let attributed_to = note .attributed_to() .req("attributed to")? @@ -176,6 +177,7 @@ pub(super) fn note( title: title.to_owned(), description, published: Some(published.into()), + updated: updated.map(|u| u.into()), files: existing_files, visibility: find_visibility(note), local_only: false, diff --git a/profiles/src/apub/actions/apub/person/mod.rs b/profiles/src/apub/actions/apub/person/mod.rs index c758898..7b55999 100644 --- a/profiles/src/apub/actions/apub/person/mod.rs +++ b/profiles/src/apub/actions/apub/person/mod.rs @@ -97,6 +97,7 @@ pub(crate) fn person( let public_key = person.ext_one.public_key.public_key_pem.clone(); let public_key_id = person.ext_one.public_key.id.clone(); let published = person.published().req("published")?.into(); + let updated = person.updated().map(|u| u.into()); let login_required = person .to() @@ -136,6 +137,7 @@ pub(crate) fn person( login_required: Some(login_required), icon, banner, + updated, }))) } else { Ok(Ok(Box::new(CreateProfile { @@ -153,6 +155,7 @@ pub(crate) fn person( icon, banner, published, + updated, }))) } } @@ -220,6 +223,7 @@ pub(crate) fn update_person( .map(|s| s.to_owned()); let public_key_id = person.ext_one.public_key.id.clone(); let public_key = person.ext_one.public_key.public_key_pem.clone(); + let updated = person.updated().map(|u| u.into()); let login_required = person .to() @@ -252,6 +256,7 @@ pub(crate) fn update_person( login_required: Some(login_required), icon, banner, + updated, }))) } diff --git a/profiles/src/apub/actions/mod.rs b/profiles/src/apub/actions/mod.rs index 23e6138..c06b363 100644 --- a/profiles/src/apub/actions/mod.rs +++ b/profiles/src/apub/actions/mod.rs @@ -272,6 +272,7 @@ pub struct CreateSubmission { local_only: bool, logged_in_only: bool, sensitive: bool, + updated: Option>, } impl CreateSubmission { @@ -287,6 +288,7 @@ impl CreateSubmission { local_only: false, logged_in_only: false, sensitive: false, + updated: None, } } } @@ -470,6 +472,7 @@ pub struct CreateProfile { icon: Option, banner: Option, published: DateTime, + updated: Option>, } impl CreateProfile { @@ -485,6 +488,7 @@ impl CreateProfile { icon: None, banner: None, published: Utc::now(), + updated: None, } } } @@ -504,6 +508,7 @@ pub struct UpdateProfile { login_required: Option, icon: Option, banner: Option, + updated: Option>, } impl UpdateProfile { @@ -530,6 +535,7 @@ impl UpdateProfile { login_required: None, icon: None, banner: None, + updated: Some(Utc::now()), } } @@ -544,6 +550,7 @@ impl UpdateProfile { login_required: None, icon: Some(icon), banner: None, + updated: Some(Utc::now()), } } @@ -558,6 +565,7 @@ impl UpdateProfile { login_required: None, icon: None, banner: Some(banner), + updated: Some(Utc::now()), } } @@ -572,6 +580,7 @@ impl UpdateProfile { login_required: Some(login_required), icon: None, banner: None, + updated: Some(Utc::now()), } } } diff --git a/profiles/src/apub/actions/profile.rs b/profiles/src/apub/actions/profile.rs index 80132d8..d425db7 100644 --- a/profiles/src/apub/actions/profile.rs +++ b/profiles/src/apub/actions/profile.rs @@ -30,6 +30,9 @@ impl Action for CreateProfile { if let Some(icon) = self.icon { changes.icon(icon); } + if let Some(updated) = self.updated { + changes.updated(updated); + } let profile = if changes.any_changes() { ctx.store.profiles.update(&changes)? @@ -88,6 +91,9 @@ impl Action for UpdateProfile { if let Some(description_source) = &self.description_source { changes.description_source(description_source); } + if let Some(updated) = self.updated { + changes.updated(updated); + } let previous_banner = profile.banner(); let previous_icon = profile.icon(); diff --git a/profiles/src/apub/actions/submission.rs b/profiles/src/apub/actions/submission.rs index c5cc644..5c6b3d8 100644 --- a/profiles/src/apub/actions/submission.rs +++ b/profiles/src/apub/actions/submission.rs @@ -27,6 +27,9 @@ impl Action for CreateSubmission { if let Some(description) = &self.description { changes.description(&hyaenidae_content::html(description)); } + if let Some(updated) = self.updated { + changes.updated(updated); + } for file in &self.files { changes.add_file(*file); diff --git a/profiles/src/apub/results/profile.rs b/profiles/src/apub/results/profile.rs index 0791116..3d44537 100644 --- a/profiles/src/apub/results/profile.rs +++ b/profiles/src/apub/results/profile.rs @@ -149,6 +149,9 @@ impl Outbound for ProfileCreated { let image = build_image(banner, ctx)?; person.set_image(image); } + if let Some(updated) = profile.updated() { + person.set_updated(updated.into()); + } if !profile.login_required() { person.add_to(public()); } @@ -265,6 +268,9 @@ impl Outbound for ProfileUpdated { let image = build_image(banner, ctx)?; person.set_image(image); } + if let Some(updated) = profile.updated() { + person.set_updated(updated.into()); + } if !profile.login_required() { person.add_to(public()); } diff --git a/profiles/src/store/profile.rs b/profiles/src/store/profile.rs index e4cabb4..dfdf9f0 100644 --- a/profiles/src/store/profile.rs +++ b/profiles/src/store/profile.rs @@ -14,6 +14,7 @@ pub struct ProfileChanges { login_required: Option, icon: Option, banner: Option, + updated: Option>, } #[derive(Clone, Debug)] @@ -35,6 +36,7 @@ pub struct Profile { icon: Option, banner: Option, published: DateTime, + updated: Option>, login_required: bool, suspended: bool, } @@ -73,9 +75,9 @@ struct StoredProfile { #[serde(skip_serializing_if = "Option::is_none")] banner: Option, published: DateTime, + updated: Option>, login_required: bool, created_at: DateTime, - updated_at: DateTime, #[serde(skip_serializing_if = "Option::is_none")] suspended_at: Option>, } @@ -116,12 +118,18 @@ impl ProfileChanges { self } + pub(crate) fn updated(&mut self, updated: DateTime) -> &mut Self { + self.updated = Some(updated); + self + } + pub(crate) fn any_changes(&self) -> bool { self.display_name.is_some() || self.description.is_some() || self.login_required.is_some() || self.icon.is_some() || self.banner.is_some() + || self.updated.is_some() } } @@ -142,6 +150,7 @@ impl Profile { login_required: None, icon: None, banner: None, + updated: None, } } @@ -196,6 +205,10 @@ impl Profile { self.published } + pub fn updated(&self) -> Option> { + self.updated + } + pub fn login_required(&self) -> bool { self.login_required } @@ -253,9 +266,9 @@ impl Store { icon: None, banner: None, published, + updated: None, login_required: true, created_at: now, - updated_at: now, suspended_at: None, }; serde_json::to_writer(writer, &stored_profile)?; @@ -365,6 +378,13 @@ impl Store { let mut stored_profile: StoredProfile = serde_json::from_slice(&stored_profile_ivec)?; + if let Some(updated) = profile_changes.updated { + let previously_updated = stored_profile.updated.unwrap_or(stored_profile.published); + if updated < previously_updated { + return Err(StoreError::Outdated); + } + } + if let Some(login_required) = profile_changes.login_required { stored_profile.login_required = login_required; } @@ -386,7 +406,9 @@ impl Store { if let Some(banner) = profile_changes.banner { stored_profile.banner = Some(banner); } - stored_profile.updated_at = Utc::now().into(); + if let Some(updated) = profile_changes.updated { + stored_profile.updated = Some(updated); + } let stored_profile_vec = serde_json::to_vec(&stored_profile)?; @@ -533,7 +555,7 @@ impl Store { stored_profile.description = None; stored_profile.display_name = None; stored_profile.suspended_at = Some(now); - stored_profile.updated_at = now; + stored_profile.updated = Some(now); let stored_profile_vec = serde_json::to_vec(&stored_profile)?; if self @@ -616,6 +638,7 @@ impl From for Profile { icon: sp.icon, banner: sp.banner, published: sp.published, + updated: sp.updated, login_required: sp.login_required, suspended: sp.suspended_at.is_some(), }