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,
Connection, Error, PostEvent::*, Result, CONFIG, POST_CHAN,
};
use activitypub::{
use activitystreams::{
activity::{Create, Delete, Update},
base::{AnyBase, AsBase, Base},
iri, // CustomObject,
link,
object::{Article, Image, Tombstone},
CustomObject,
object::{kind::ImageType, ApObject, Article, Image, Object, Tombstone},
prelude::*,
primitives::OneOrMany,
time::OffsetDateTime,
};
use chrono::{NaiveDateTime, TimeZone, Utc};
use chrono::{NaiveDateTime, Utc};
use diesel::{self, BelongingToDsl, ExpressionMethods, QueryDsl, RunQueryDsl};
use once_cell::sync::Lazy;
use plume_common::{
activity_pub::{
inbox::{AsActor, AsObject, FromId},
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},
};
@ -24,7 +33,43 @@ use riker::actors::{Publish, Tell};
use std::collections::{HashMap, HashSet};
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()));
@ -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 to = vec![PUBLIC_VISIBILITY.to_string()];
@ -353,9 +399,9 @@ impl Post {
.collect::<Vec<serde_json::Value>>();
mentions_json.append(&mut tags_json);
let mut article = Article::default();
article.object_props.set_name_string(self.title.clone())?;
article.object_props.set_id_string(self.ap_url.clone())?;
let mut article = ApObject::new(Article::new());
article.set_name(self.title.clone());
article.set_id(iri!(self.ap_url));
let mut authors = self
.get_authors(conn)?
@ -363,82 +409,110 @@ impl Post {
.map(|x| Id::new(x.ap_url))
.collect::<Vec<Id>>();
authors.push(self.get_blog(conn)?.into_id()); // add the blog URL here too
article
.object_props
.set_attributed_to_link_vec::<Id>(authors)?;
article
.object_props
.set_content_string(self.content.get().clone())?;
article.ap_object_props.set_source_object(Source {
content: self.source.clone(),
media_type: String::from("text/markdown"),
})?;
article
.object_props
.set_published_utctime(Utc.from_utc_datetime(&self.creation_date))?;
article
.object_props
.set_summary_string(self.subtitle.clone())?;
article.object_props.tag = Some(json!(mentions_json));
// article.set_attributed_to(authors.into());
article.set_content(self.content.get().clone());
article.set_source(
// FIXME
// article.ap_object_props.set_source_object(Source {
// content: self.source.clone(),
// media_type: String::from("text/markdown"),
// })?;
AnyBase::from_base(
*ApObject::new(Object::new())
.set_content(self.source.clone())
.set_media_type("text/markdown".parse().expect("Unreachable"))
.base_ref(),
),
);
article.set_published(
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 {
let media = Media::get(conn, media_id)?;
let mut cover = Image::default();
cover.object_props.set_url_string(media.url()?)?;
let mut cover = Image::new();
cover.set_url(media.url()?);
if media.sensitive {
cover
.object_props
.set_summary_string(media.content_warning.unwrap_or_default())?;
cover.set_summary(media.content_warning.unwrap_or_default());
}
cover.object_props.set_content_string(media.alt_text)?;
cover
.object_props
.set_attributed_to_link_vec(vec![User::get(conn, media.owner_id)?.into_id()])?;
article.object_props.set_icon_object(cover)?;
cover.set_content(media.alt_text);
cover.set_attributed_to(iri!(User::get(conn, media.owner_id)?.into_id()));
let base = Base::retract(cover)?.into_generic()?;
article.set_icon(AnyBase::from_base(base));
}
article.object_props.set_url_string(self.ap_url.clone())?;
article
.object_props
.set_to_link_vec::<Id>(to.into_iter().map(Id::new).collect())?;
article
.object_props
.set_cc_link_vec::<Id>(cc.into_iter().map(Id::new).collect())?;
let mut license = Licensed::default();
license.set_license_string(self.license.clone())?;
Ok(LicensedArticle::new(article, license))
article.set_url(self.ap_url.clone());
let tos = Vec::with_capacity(to.len());
for addr in to.into_iter() {
tos.push(iri!(addr));
}
article.set_many_tos(tos);
let ccs = Vec::with_capacity(cc.len());
for addr in cc.into_iter() {
ccs.push(iri!(addr));
}
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> {
let article = self.to_activity(conn)?;
let mut act = Create::default();
act.object_props
.set_id_string(format!("{}/activity", self.ap_url))?;
act.object_props
.set_to_link_vec::<Id>(article.object.object_props.to_link_vec()?)?;
act.object_props
.set_cc_link_vec::<Id>(article.object.object_props.cc_link_vec()?)?;
act.create_props
.set_actor_link(Id::new(self.get_authors(conn)?[0].clone().ap_url))?;
act.create_props.set_object_object(article)?;
let base = Base::retract(article)?.into_generic()?;
let mut act = Create::new::<_, OneOrMany<AnyBase>>(
iri!(Id::new(self.get_authors(conn)?[0].clone().ap_url)),
base.into(),
);
let id_string = format!("{}/activity", self.ap_url);
act.set_id(iri!(id_string));
act.set_many_tos(
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)
}
pub fn update_activity(&self, conn: &Connection) -> Result<Update> {
let article = self.to_activity(conn)?;
let mut act = Update::default();
act.object_props.set_id_string(format!(
let base = Base::retract(article)?.into_generic()?;
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-{}",
self.ap_url,
Utc::now().timestamp()
))?;
act.object_props
.set_to_link_vec::<Id>(article.object.object_props.to_link_vec()?)?;
act.object_props
.set_cc_link_vec::<Id>(article.object.object_props.cc_link_vec()?)?;
act.update_props
.set_actor_link(Id::new(self.get_authors(conn)?[0].clone().ap_url))?;
act.update_props.set_object_object(article)?;
)));
act.set_many_tos(
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)
}
@ -447,10 +521,8 @@ impl Post {
.into_iter()
.map(|m| {
(
m.link_props
.href_string()
.ok()
.and_then(|ap_url| User::find_by_ap_url(conn, &ap_url).ok())
m.href()
.and_then(|ap_url| User::find_by_ap_url(conn, ap_url.as_str()).ok())
.map(|u| u.id),
m,
)
@ -566,18 +638,17 @@ impl Post {
}
pub fn build_delete(&self, conn: &Connection) -> Result<Delete> {
let mut act = Delete::default();
act.delete_props
.set_actor_link(self.get_authors(conn)?[0].clone().into_id())?;
let mut tombstone = Tombstone::new();
let ap_url = iri!(self.ap_url);
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();
tombstone.object_props.set_id_string(self.ap_url.clone())?;
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)])?;
act.set_id(format!("{}#delete", self.ap_url).parse()?);
act.set_many_tos(vec![iri!(Id::new(PUBLIC_VISIBILITY))]);
Ok(act)
}
@ -614,54 +685,96 @@ impl Post {
impl FromId<DbConn> for Post {
type Error = Error;
type Object = LicensedArticle;
// type Object = LicensedArticle;
type Object = Article;
fn from_db(conn: &DbConn, id: &str) -> Result<Self> {
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 license = article.custom_props.license_string().unwrap_or_default();
let article = article.object;
// let license = article.license().unwrap_or_default();
let (blog, authors) = article
.object_props
.attributed_to_link_vec::<Id>()?
.into_iter()
.fold((None, vec![]), |(blog, mut authors), link| {
let url = link;
match User::from_id(conn, &url, None, CONFIG.proxy()) {
let (blog, authors) = article.attributed_to().into_iter().fold(
(None, vec![]),
|(blog, mut authors), link| {
let href = link
.as_one()
.expect("exists and only")
.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) => {
authors.push(u);
(blog, authors)
}
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,
),
}
});
},
);
let cover = article
.object_props
.icon_object::<Image>()
let cover = article.icon().and_then(|img| {
Media::from_activity(
conn,
&img.as_one()
.expect("possible")
.extend::<Image, ImageType>()
.expect("possilbe")
.expect("exists"),
)
.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
.object_props
.url_string()
.or_else(|_| article.object_props.id_string())?;
let post = Post::from_db(conn, &ap_url)
.url()
.map(|url| url.as_single_id().expect("exists"))
.or_else(|| article.id())
.expect("exists")
.to_string();
let post = Post::from_db(conn, ap_url.as_str())
.and_then(|mut post| {
let mut updated = false;
let slug = Self::slug(&title);
let content = SafeString::new(&article.object_props.content_string()?);
let subtitle = article.object_props.summary_string()?;
let source = article.ap_object_props.source_object::<Source>()?.content;
let content = SafeString::new(
&article
.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 {
post.slug = slug.to_string();
updated = true;
@ -674,10 +787,10 @@ impl FromId<DbConn> for Post {
post.content = content;
updated = true;
}
if post.license != license {
post.license = license.clone();
updated = true;
}
// if post.license != license {
// post.license = license.clone();
// updated = true;
// }
if post.subtitle != subtitle {
post.subtitle = subtitle;
updated = true;
@ -698,20 +811,50 @@ impl FromId<DbConn> for Post {
Ok(post)
})
.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(
conn,
NewPost {
blog_id: blog.ok_or(Error::NotFound)?.id,
slug: Self::slug(&title).to_string(),
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,
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
ap_url,
creation_date: Some(article.object_props.published_utctime()?.naive_utc()),
subtitle: article.object_props.summary_string()?,
source: article.ap_object_props.source_object::<Source>()?.content,
creation_date: Some(creation_date),
subtitle: article
.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,
},
)
@ -735,24 +878,27 @@ impl FromId<DbConn> for Post {
.2
.into_iter()
.collect::<HashSet<_>>();
if let Some(serde_json::Value::Array(tags)) = article.object_props.tag {
for tag in tags {
serde_json::from_value::<link::Mention>(tag.clone())
.map(|m| Mention::from_activity(conn, &m, post.id, true, true))
.ok();
if let Some(tags) = article.tag() {
for tag in tags.iter() {
Mention::from_activity(
conn,
&tag.extend::<link::Mention, link::kind::MentionType>()
.expect("possible")
.expect("exists"),
post.id,
true,
true,
)
.ok();
serde_json::from_value::<Hashtag>(tag.clone())
.map_err(Error::from)
.and_then(|t| {
let tag_name = t.name_string()?;
Ok(Tag::from_activity(
conn,
&t,
post.id,
hashtags.remove(&tag_name),
))
})
.ok();
// TODO
// let tag_name = tag.as_xsd_string().ok_or(Error::MissingApProperty)?;
// Ok(Tag::from_activity(
// conn,
// &tag,
// post.id,
// hashtags.remove(&tag_name),
// ));
}
}
@ -806,33 +952,54 @@ pub struct PostUpdate {
impl FromId<DbConn> for PostUpdate {
type Error = Error;
type Object = LicensedArticle;
// type Object = LicensedArticle;
type Object = Article;
fn from_db(_: &DbConn, _: &str) -> Result<Self> {
// Always fail because we always want to deserialize the AP object
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 {
ap_url: updated.object.object_props.id_string()?,
title: updated.object.object_props.name_string().ok(),
subtitle: updated.object.object_props.summary_string().ok(),
content: updated.object.object_props.content_string().ok(),
cover: updated
.object
.object_props
.icon_object::<Image>()
ap_url: updated.id().ok_or(Error::MissingApProperty)?.to_string(),
title: updated
.name()
.map(|name| name.as_single_xsd_string().expect("exists").into()),
subtitle: updated
.summary()
.map(|summary| summary.as_single_xsd_string().expect("exists").into()),
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()
.and_then(|img| Media::from_activity(conn, &img).ok().map(|m| m.id)),
source: updated
.object
.ap_object_props
.source_object::<Source>()
.ok()
.map(|x| x.content),
license: updated.custom_props.license_string().ok(),
tags: updated.object.object_props.tag,
}),
source: ApObject::new(updated).source().map(|x| {
x.extend::<Object<SourceType>, SourceType>()
.expect("possible")
.expect("exists")
.content()
.expect("exists and only")
.as_one()
.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();
let mut license = Licensed::default();
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 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!(
"Yo",
&article_from_json.object.object_props.id_string().unwrap()
@ -1051,7 +1220,8 @@ mod tests {
"published": "2014-12-12T12:12:12Z",
"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!(
"https://plu.me/~/Blog/my-article",
&article.object.object_props.id_string().unwrap()