Dummy implementation to compile posts.rs

This commit is contained in:
Kitaiti Makoto 2022-01-23 08:33:15 +09:00
parent c36b544c20
commit 2ee19a866d

View File

@ -3,20 +3,29 @@ use crate::{
post_authors::*, safe_string::SafeString, schema::posts, tags::*, timeline::*, users::User, post_authors::*, safe_string::SafeString, schema::posts, tags::*, timeline::*, users::User,
Connection, Error, PostEvent::*, Result, CONFIG, POST_CHAN, Connection, Error, PostEvent::*, Result, CONFIG, POST_CHAN,
}; };
use activitypub::{ use activitystreams::{
activity::{Create, Delete, Update}, activity::{Create, Delete, Update},
base::{AnyBase, AsBase, Base},
iri, // CustomObject,
link, link,
object::{Article, Image, Tombstone}, object::{kind::ImageType, ApObject, Article, Image, Object, Tombstone},
CustomObject, prelude::*,
primitives::OneOrMany,
time::OffsetDateTime,
}; };
use chrono::{NaiveDateTime, TimeZone, Utc}; use chrono::{NaiveDateTime, Utc};
use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use plume_common::{ use plume_common::{
activity_pub::{ activity_pub::{
inbox::{AsActor, AsObject, FromId}, inbox::{AsActor, AsObject, FromId},
sign::Signer, sign::Signer,
Hashtag, Id, IntoId, Licensed, Source, PUBLIC_VISIBILITY, // Hashtag, Id, IntoId, /Licensed, Source, PUBLIC_VISIBILITY,
Hashtag,
Id,
IntoId,
// Source,
PUBLIC_VISIBILITY,
}, },
utils::{iri_percent_encode_seg, md_to_html}, utils::{iri_percent_encode_seg, md_to_html},
}; };
@ -24,7 +33,43 @@ use riker::actors::{Publish, Tell};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
pub type LicensedArticle = CustomObject<Licensed, Article>; // pub type LicensedArticle = CustomObject<Licensed, Article>;
#[derive(
Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize,
)]
enum LinkType {
Link,
}
impl std::fmt::Display for LinkType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, stringify!(Link))
}
}
impl Default for LinkType {
fn default() -> Self {
LinkType::Link
}
}
#[derive(
Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize,
)]
enum SourceType {
Source,
}
impl std::fmt::Display for SourceType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, stringify!(Source))
}
}
impl Default for SourceType {
fn default() -> Self {
SourceType::Source
}
}
static BLOG_FQN_CACHE: Lazy<Mutex<HashMap<i32, String>>> = Lazy::new(|| Mutex::new(HashMap::new())); static BLOG_FQN_CACHE: Lazy<Mutex<HashMap<i32, String>>> = Lazy::new(|| Mutex::new(HashMap::new()));
@ -339,7 +384,8 @@ impl Post {
})) }))
} }
pub fn to_activity(&self, conn: &Connection) -> Result<LicensedArticle> { pub fn to_activity(&self, conn: &Connection) -> Result<ApObject<Article>> {
// pub fn to_activity(&self, conn: &Connection) -> Result<LicensedArticle> {
let cc = self.get_receivers_urls(conn)?; let cc = self.get_receivers_urls(conn)?;
let to = vec![PUBLIC_VISIBILITY.to_string()]; let to = vec![PUBLIC_VISIBILITY.to_string()];
@ -353,9 +399,9 @@ impl Post {
.collect::<Vec<serde_json::Value>>(); .collect::<Vec<serde_json::Value>>();
mentions_json.append(&mut tags_json); mentions_json.append(&mut tags_json);
let mut article = Article::default(); let mut article = ApObject::new(Article::new());
article.object_props.set_name_string(self.title.clone())?; article.set_name(self.title.clone());
article.object_props.set_id_string(self.ap_url.clone())?; article.set_id(iri!(self.ap_url));
let mut authors = self let mut authors = self
.get_authors(conn)? .get_authors(conn)?
@ -363,82 +409,110 @@ impl Post {
.map(|x| Id::new(x.ap_url)) .map(|x| Id::new(x.ap_url))
.collect::<Vec<Id>>(); .collect::<Vec<Id>>();
authors.push(self.get_blog(conn)?.into_id()); // add the blog URL here too authors.push(self.get_blog(conn)?.into_id()); // add the blog URL here too
article // article.set_attributed_to(authors.into());
.object_props article.set_content(self.content.get().clone());
.set_attributed_to_link_vec::<Id>(authors)?; article.set_source(
article // FIXME
.object_props // article.ap_object_props.set_source_object(Source {
.set_content_string(self.content.get().clone())?; // content: self.source.clone(),
article.ap_object_props.set_source_object(Source { // media_type: String::from("text/markdown"),
content: self.source.clone(), // })?;
media_type: String::from("text/markdown"), AnyBase::from_base(
})?; *ApObject::new(Object::new())
article .set_content(self.source.clone())
.object_props .set_media_type("text/markdown".parse().expect("Unreachable"))
.set_published_utctime(Utc.from_utc_datetime(&self.creation_date))?; .base_ref(),
article ),
.object_props );
.set_summary_string(self.subtitle.clone())?; article.set_published(
article.object_props.tag = Some(json!(mentions_json)); OffsetDateTime::from_unix_timestamp_nanos(self.creation_date.timestamp_nanos().into())
.expect("Unreachable"),
);
article.set_summary(self.subtitle.clone());
article.set_tag(AnyBase::from_arbitrary_json(json!(mentions_json))?);
if let Some(media_id) = self.cover_id { if let Some(media_id) = self.cover_id {
let media = Media::get(conn, media_id)?; let media = Media::get(conn, media_id)?;
let mut cover = Image::default(); let mut cover = Image::new();
cover.object_props.set_url_string(media.url()?)?; cover.set_url(media.url()?);
if media.sensitive { if media.sensitive {
cover cover.set_summary(media.content_warning.unwrap_or_default());
.object_props
.set_summary_string(media.content_warning.unwrap_or_default())?;
} }
cover.object_props.set_content_string(media.alt_text)?; cover.set_content(media.alt_text);
cover cover.set_attributed_to(iri!(User::get(conn, media.owner_id)?.into_id()));
.object_props let base = Base::retract(cover)?.into_generic()?;
.set_attributed_to_link_vec(vec![User::get(conn, media.owner_id)?.into_id()])?; article.set_icon(AnyBase::from_base(base));
article.object_props.set_icon_object(cover)?;
} }
article.object_props.set_url_string(self.ap_url.clone())?; article.set_url(self.ap_url.clone());
article let tos = Vec::with_capacity(to.len());
.object_props for addr in to.into_iter() {
.set_to_link_vec::<Id>(to.into_iter().map(Id::new).collect())?; tos.push(iri!(addr));
article }
.object_props article.set_many_tos(tos);
.set_cc_link_vec::<Id>(cc.into_iter().map(Id::new).collect())?; let ccs = Vec::with_capacity(cc.len());
let mut license = Licensed::default(); for addr in cc.into_iter() {
license.set_license_string(self.license.clone())?; ccs.push(iri!(addr));
Ok(LicensedArticle::new(article, license)) }
article.set_many_ccs(ccs);
// let mut license = Licensed::default();
// license.set_license_string(self.license.clone())?;
// Ok(LicensedArticle::new(article, license))
Ok(article)
} }
pub fn create_activity(&self, conn: &Connection) -> Result<Create> { pub fn create_activity(&self, conn: &Connection) -> Result<Create> {
let article = self.to_activity(conn)?; let article = self.to_activity(conn)?;
let mut act = Create::default(); let base = Base::retract(article)?.into_generic()?;
act.object_props let mut act = Create::new::<_, OneOrMany<AnyBase>>(
.set_id_string(format!("{}/activity", self.ap_url))?; iri!(Id::new(self.get_authors(conn)?[0].clone().ap_url)),
act.object_props base.into(),
.set_to_link_vec::<Id>(article.object.object_props.to_link_vec()?)?; );
act.object_props let id_string = format!("{}/activity", self.ap_url);
.set_cc_link_vec::<Id>(article.object.object_props.cc_link_vec()?)?; act.set_id(iri!(id_string));
act.create_props act.set_many_tos(
.set_actor_link(Id::new(self.get_authors(conn)?[0].clone().ap_url))?; article
act.create_props.set_object_object(article)?; .to()
.expect("exists")
.into_iter()
.map(|addr| AnyBase::from_xsd_any_uri(*addr.id().expect("exists"))),
);
act.set_many_ccs(
article
.cc()
.expect("exists")
.into_iter()
.map(|addr| AnyBase::from_xsd_any_uri(*addr.id().expect("exists"))),
);
Ok(act) Ok(act)
} }
pub fn update_activity(&self, conn: &Connection) -> Result<Update> { pub fn update_activity(&self, conn: &Connection) -> Result<Update> {
let article = self.to_activity(conn)?; let article = self.to_activity(conn)?;
let mut act = Update::default(); let base = Base::retract(article)?.into_generic()?;
act.object_props.set_id_string(format!( let mut act = Update::new::<_, OneOrMany<AnyBase>>(
iri!(Id::new(self.get_authors(conn)?[0].clone().ap_url)),
base.into(),
);
act.set_id(iri!(format!(
"{}/update-{}", "{}/update-{}",
self.ap_url, self.ap_url,
Utc::now().timestamp() Utc::now().timestamp()
))?; )));
act.object_props act.set_many_tos(
.set_to_link_vec::<Id>(article.object.object_props.to_link_vec()?)?; article
act.object_props .to()
.set_cc_link_vec::<Id>(article.object.object_props.cc_link_vec()?)?; .expect("exists")
act.update_props .into_iter()
.set_actor_link(Id::new(self.get_authors(conn)?[0].clone().ap_url))?; .map(|addr| AnyBase::from_xsd_any_uri(*addr.id().expect("exists"))),
act.update_props.set_object_object(article)?; );
act.set_many_ccs(
article
.cc()
.expect("exists")
.into_iter()
.map(|addr| AnyBase::from_xsd_any_uri(*addr.id().expect("exists"))),
);
Ok(act) Ok(act)
} }
@ -447,10 +521,8 @@ impl Post {
.into_iter() .into_iter()
.map(|m| { .map(|m| {
( (
m.link_props m.href()
.href_string() .and_then(|ap_url| User::find_by_ap_url(conn, ap_url.as_str()).ok())
.ok()
.and_then(|ap_url| User::find_by_ap_url(conn, &ap_url).ok())
.map(|u| u.id), .map(|u| u.id),
m, m,
) )
@ -566,18 +638,17 @@ impl Post {
} }
pub fn build_delete(&self, conn: &Connection) -> Result<Delete> { pub fn build_delete(&self, conn: &Connection) -> Result<Delete> {
let mut act = Delete::default(); let mut tombstone = Tombstone::new();
act.delete_props let ap_url = iri!(self.ap_url);
.set_actor_link(self.get_authors(conn)?[0].clone().into_id())?; tombstone.set_id(ap_url);
let base = Base::retract(tombstone)?.into_generic()?;
let mut act = Delete::new::<_, OneOrMany<AnyBase>>(
iri!(self.get_authors(conn)?[0].clone().into_id()),
base.into(),
);
let mut tombstone = Tombstone::default(); act.set_id(format!("{}#delete", self.ap_url).parse()?);
tombstone.object_props.set_id_string(self.ap_url.clone())?; act.set_many_tos(vec![iri!(Id::new(PUBLIC_VISIBILITY))]);
act.delete_props.set_object_object(tombstone)?;
act.object_props
.set_id_string(format!("{}#delete", self.ap_url))?;
act.object_props
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILITY)])?;
Ok(act) Ok(act)
} }
@ -614,54 +685,96 @@ impl Post {
impl FromId<DbConn> for Post { impl FromId<DbConn> for Post {
type Error = Error; type Error = Error;
type Object = LicensedArticle; // type Object = LicensedArticle;
type Object = Article;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> { fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
Self::find_by_ap_url(conn, id) Self::find_by_ap_url(conn, id)
} }
fn from_activity(conn: &DbConn, article: LicensedArticle) -> Result<Self> { // fn from_activity(conn: &DbConn, article: LicensedArticle) -> Result<Self> {
fn from_activity(conn: &DbConn, article: Article) -> Result<Self> {
let conn = conn; let conn = conn;
let license = article.custom_props.license_string().unwrap_or_default(); // let license = article.license().unwrap_or_default();
let article = article.object;
let (blog, authors) = article let (blog, authors) = article.attributed_to().into_iter().fold(
.object_props (None, vec![]),
.attributed_to_link_vec::<Id>()? |(blog, mut authors), link| {
.into_iter() let href = link
.fold((None, vec![]), |(blog, mut authors), link| { .as_one()
let url = link; .expect("exists and only")
match User::from_id(conn, &url, None, CONFIG.proxy()) { .extend::<link::Link<LinkType>, LinkType>()
.expect("exists")
.expect("object")
.href()
.expect("exists")
.as_str();
match User::from_id(conn, href, None, CONFIG.proxy()) {
Ok(u) => { Ok(u) => {
authors.push(u); authors.push(u);
(blog, authors) (blog, authors)
} }
Err(_) => ( Err(_) => (
blog.or_else(|| Blog::from_id(conn, &url, None, CONFIG.proxy()).ok()), blog.or_else(|| Blog::from_id(conn, href, None, CONFIG.proxy()).ok()),
authors, authors,
), ),
} }
}); },
);
let cover = article let cover = article.icon().and_then(|img| {
.object_props Media::from_activity(
.icon_object::<Image>() conn,
&img.as_one()
.expect("possible")
.extend::<Image, ImageType>()
.expect("possilbe")
.expect("exists"),
)
.ok() .ok()
.and_then(|img| Media::from_activity(conn, &img).ok().map(|m| m.id)); .map(|m| m.id)
});
let title = article.object_props.name_string()?; let title = article
.name()
.expect("exists")
.as_single_xsd_string()
.expect("exists and only")
.to_string();
let ap_url = article let ap_url = article
.object_props .url()
.url_string() .map(|url| url.as_single_id().expect("exists"))
.or_else(|_| article.object_props.id_string())?; .or_else(|| article.id())
let post = Post::from_db(conn, &ap_url) .expect("exists")
.to_string();
let post = Post::from_db(conn, ap_url.as_str())
.and_then(|mut post| { .and_then(|mut post| {
let mut updated = false; let mut updated = false;
let slug = Self::slug(&title); let slug = Self::slug(&title);
let content = SafeString::new(&article.object_props.content_string()?); let content = SafeString::new(
let subtitle = article.object_props.summary_string()?; &article
let source = article.ap_object_props.source_object::<Source>()?.content; .content()
.expect("exists and only")
.as_one()
.expect("only")
.as_xsd_string()
.expect("possible"),
);
let subtitle = article
.summary()
.expect("exists and only")
.as_one()
.expect("possible")
.as_xsd_string()
.expect("possible")
.to_string();
let source = ApObject::new(article)
.source()
.ok_or(Error::MissingApProperty)?
.as_xsd_string()
.expect("possible")
.to_string();
if post.slug != slug { if post.slug != slug {
post.slug = slug.to_string(); post.slug = slug.to_string();
updated = true; updated = true;
@ -674,10 +787,10 @@ impl FromId<DbConn> for Post {
post.content = content; post.content = content;
updated = true; updated = true;
} }
if post.license != license { // if post.license != license {
post.license = license.clone(); // post.license = license.clone();
updated = true; // updated = true;
} // }
if post.subtitle != subtitle { if post.subtitle != subtitle {
post.subtitle = subtitle; post.subtitle = subtitle;
updated = true; updated = true;
@ -698,20 +811,50 @@ impl FromId<DbConn> for Post {
Ok(post) Ok(post)
}) })
.or_else(|_| { .or_else(|_| {
let published = article.published().ok_or(Error::MissingApProperty)?;
let published_secs = published.unix_timestamp();
let published_nanos =
published.unix_timestamp_nanos() - (published_secs * 1000 * 1000) as i128;
use std::convert::TryInto;
let creation_date = NaiveDateTime::from_timestamp(
published_secs,
published_nanos.try_into().expect("Unreachable"),
);
Post::insert( Post::insert(
conn, conn,
NewPost { NewPost {
blog_id: blog.ok_or(Error::NotFound)?.id, blog_id: blog.ok_or(Error::NotFound)?.id,
slug: Self::slug(&title).to_string(), slug: Self::slug(&title).to_string(),
title, title,
content: SafeString::new(&article.object_props.content_string()?), content: SafeString::new(
&article
.content()
.expect("exists and only")
.as_one()
.expect("only")
.as_xsd_string()
.expect("possible"),
),
published: true, published: true,
license, // license,
license: "".into(),
// FIXME: This is wrong: with this logic, we may use the display URL as the AP ID. We need two different fields // FIXME: This is wrong: with this logic, we may use the display URL as the AP ID. We need two different fields
ap_url, ap_url,
creation_date: Some(article.object_props.published_utctime()?.naive_utc()), creation_date: Some(creation_date),
subtitle: article.object_props.summary_string()?, subtitle: article
source: article.ap_object_props.source_object::<Source>()?.content, .summary()
.expect("exists and only")
.as_one()
.expect("possible")
.as_xsd_string()
.expect("possible")
.to_string(),
source: ApObject::new(article)
.source()
.ok_or(Error::MissingApProperty)?
.as_xsd_string()
.expect("possible")
.to_string(),
cover_id: cover, cover_id: cover,
}, },
) )
@ -735,24 +878,27 @@ impl FromId<DbConn> for Post {
.2 .2
.into_iter() .into_iter()
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
if let Some(serde_json::Value::Array(tags)) = article.object_props.tag { if let Some(tags) = article.tag() {
for tag in tags { for tag in tags.iter() {
serde_json::from_value::<link::Mention>(tag.clone()) Mention::from_activity(
.map(|m| Mention::from_activity(conn, &m, post.id, true, true)) conn,
.ok(); &tag.extend::<link::Mention, link::kind::MentionType>()
.expect("possible")
.expect("exists"),
post.id,
true,
true,
)
.ok();
serde_json::from_value::<Hashtag>(tag.clone()) // TODO
.map_err(Error::from) // let tag_name = tag.as_xsd_string().ok_or(Error::MissingApProperty)?;
.and_then(|t| { // Ok(Tag::from_activity(
let tag_name = t.name_string()?; // conn,
Ok(Tag::from_activity( // &tag,
conn, // post.id,
&t, // hashtags.remove(&tag_name),
post.id, // ));
hashtags.remove(&tag_name),
))
})
.ok();
} }
} }
@ -806,33 +952,54 @@ pub struct PostUpdate {
impl FromId<DbConn> for PostUpdate { impl FromId<DbConn> for PostUpdate {
type Error = Error; type Error = Error;
type Object = LicensedArticle; // type Object = LicensedArticle;
type Object = Article;
fn from_db(_: &DbConn, _: &str) -> Result<Self> { fn from_db(_: &DbConn, _: &str) -> Result<Self> {
// Always fail because we always want to deserialize the AP object // Always fail because we always want to deserialize the AP object
Err(Error::NotFound) Err(Error::NotFound)
} }
fn from_activity(conn: &DbConn, updated: LicensedArticle) -> Result<Self> { // fn from_activity(conn: &DbConn, updated: LicensedArticle) -> Result<Self> {
fn from_activity(conn: &DbConn, updated: Article) -> Result<Self> {
Ok(PostUpdate { Ok(PostUpdate {
ap_url: updated.object.object_props.id_string()?, ap_url: updated.id().ok_or(Error::MissingApProperty)?.to_string(),
title: updated.object.object_props.name_string().ok(), title: updated
subtitle: updated.object.object_props.summary_string().ok(), .name()
content: updated.object.object_props.content_string().ok(), .map(|name| name.as_single_xsd_string().expect("exists").into()),
cover: updated subtitle: updated
.object .summary()
.object_props .map(|summary| summary.as_single_xsd_string().expect("exists").into()),
.icon_object::<Image>() content: updated
.content()
.map(|content| content.as_single_xsd_string().expect("exists").into()),
cover: updated.icon().map_or(None, |img| {
Media::from_activity(
conn,
&img.as_one()
.expect("possible")
.extend::<Image, ImageType>()
.expect("possible")
.expect("exists"),
)
.map(|m| m.id)
.ok() .ok()
.and_then(|img| Media::from_activity(conn, &img).ok().map(|m| m.id)), }),
source: updated source: ApObject::new(updated).source().map(|x| {
.object x.extend::<Object<SourceType>, SourceType>()
.ap_object_props .expect("possible")
.source_object::<Source>() .expect("exists")
.ok() .content()
.map(|x| x.content), .expect("exists and only")
license: updated.custom_props.license_string().ok(), .as_one()
tags: updated.object.object_props.tag, .expect("only")
.xsd_string()
.expect("possible")
}),
license: None, // updated.custom_props.license_string().ok(), // FIXME
tags: updated.tag().and_then(|tags| {
serde_json::to_value(tags.as_single_base().expect("possible")).ok()
}),
}) })
} }
@ -1021,10 +1188,12 @@ mod tests {
article.object_props.set_id_string("Yo".into()).unwrap(); article.object_props.set_id_string("Yo".into()).unwrap();
let mut license = Licensed::default(); let mut license = Licensed::default();
license.set_license_string("WTFPL".into()).unwrap(); license.set_license_string("WTFPL".into()).unwrap();
let full_article = LicensedArticle::new(article, license); // let full_article = LicensedArticle::new(article, license);
let full_article = Article::new(article, license);
let json = serde_json::to_value(full_article).unwrap(); let json = serde_json::to_value(full_article).unwrap();
let article_from_json: LicensedArticle = serde_json::from_value(json).unwrap(); // let article_from_json: LicensedArticle = serde_json::from_value(json).unwrap();
let article_from_json: Article = serde_json::from_value(json).unwrap();
assert_eq!( assert_eq!(
"Yo", "Yo",
&article_from_json.object.object_props.id_string().unwrap() &article_from_json.object.object_props.id_string().unwrap()
@ -1051,7 +1220,8 @@ mod tests {
"published": "2014-12-12T12:12:12Z", "published": "2014-12-12T12:12:12Z",
"to": [plume_common::activity_pub::PUBLIC_VISIBILITY] "to": [plume_common::activity_pub::PUBLIC_VISIBILITY]
}); });
let article: LicensedArticle = serde_json::from_value(json).unwrap(); // let article: LicensedArticle = serde_json::from_value(json).unwrap();
let article: Article = serde_json::from_value(json).unwrap();
assert_eq!( assert_eq!(
"https://plu.me/~/Blog/my-article", "https://plu.me/~/Blog/my-article",
&article.object.object_props.id_string().unwrap() &article.object.object_props.id_string().unwrap()