Federate article updating
This commit is contained in:
parent
7152d714ae
commit
413e34ac0e
@ -71,6 +71,17 @@ macro_rules! insert {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! update {
|
||||||
|
($table:ident) => {
|
||||||
|
pub fn update(&self, conn: &PgConnection) -> Self {
|
||||||
|
diesel::update(self)
|
||||||
|
.set(self)
|
||||||
|
.get_result(conn)
|
||||||
|
.expect(concat!("Error updating ", stringify!($table)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
sql_function!(nextval, nextval_t, (seq: ::diesel::sql_types::Text) -> ::diesel::sql_types::BigInt);
|
sql_function!(nextval, nextval_t, (seq: ::diesel::sql_types::Text) -> ::diesel::sql_types::BigInt);
|
||||||
sql_function!(setval, setval_t, (seq: ::diesel::sql_types::Text, val: ::diesel::sql_types::BigInt) -> ::diesel::sql_types::BigInt);
|
sql_function!(setval, setval_t, (seq: ::diesel::sql_types::Text, val: ::diesel::sql_types::BigInt) -> ::diesel::sql_types::BigInt);
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use activitypub::{
|
use activitypub::{
|
||||||
activity::{Create, Delete},
|
activity::{Create, Delete, Update},
|
||||||
link,
|
link,
|
||||||
object::{Article, Tombstone}
|
object::{Article, Tombstone}
|
||||||
};
|
};
|
||||||
@ -25,7 +25,7 @@ use users::User;
|
|||||||
use schema::posts;
|
use schema::posts;
|
||||||
use safe_string::SafeString;
|
use safe_string::SafeString;
|
||||||
|
|
||||||
#[derive(Queryable, Identifiable, Serialize, Clone)]
|
#[derive(Queryable, Identifiable, Serialize, Clone, AsChangeset)]
|
||||||
pub struct Post {
|
pub struct Post {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub blog_id: i32,
|
pub blog_id: i32,
|
||||||
@ -58,6 +58,7 @@ pub struct NewPost {
|
|||||||
impl Post {
|
impl Post {
|
||||||
insert!(posts, NewPost);
|
insert!(posts, NewPost);
|
||||||
get!(posts);
|
get!(posts);
|
||||||
|
update!(posts);
|
||||||
find_by!(posts, find_by_slug, slug as String, blog_id as i32);
|
find_by!(posts, find_by_slug, slug as String, blog_id as i32);
|
||||||
find_by!(posts, find_by_ap_url, ap_url as String);
|
find_by!(posts, find_by_ap_url, ap_url as String);
|
||||||
|
|
||||||
@ -234,39 +235,77 @@ impl Post {
|
|||||||
let mut tags_json = Tag::for_post(conn, self.id).into_iter().map(|t| json!(t.into_activity(conn))).collect::<Vec<serde_json::Value>>();
|
let mut tags_json = Tag::for_post(conn, self.id).into_iter().map(|t| json!(t.into_activity(conn))).collect::<Vec<serde_json::Value>>();
|
||||||
|
|
||||||
let mut article = Article::default();
|
let mut article = Article::default();
|
||||||
article.object_props.set_name_string(self.title.clone()).expect("Article::into_activity: name error");
|
article.object_props.set_name_string(self.title.clone()).expect("Post::into_activity: name error");
|
||||||
article.object_props.set_id_string(self.ap_url.clone()).expect("Article::into_activity: id error");
|
article.object_props.set_id_string(self.ap_url.clone()).expect("Post::into_activity: id error");
|
||||||
|
|
||||||
let mut authors = self.get_authors(conn).into_iter().map(|x| Id::new(x.ap_url)).collect::<Vec<Id>>();
|
let mut authors = self.get_authors(conn).into_iter().map(|x| Id::new(x.ap_url)).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.object_props.set_attributed_to_link_vec::<Id>(authors).expect("Article::into_activity: attributedTo error");
|
article.object_props.set_attributed_to_link_vec::<Id>(authors).expect("Post::into_activity: attributedTo error");
|
||||||
article.object_props.set_content_string(self.content.get().clone()).expect("Article::into_activity: content error");
|
article.object_props.set_content_string(self.content.get().clone()).expect("Post::into_activity: content error");
|
||||||
article.ap_object_props.set_source_object(Source {
|
article.ap_object_props.set_source_object(Source {
|
||||||
content: self.source.clone(),
|
content: self.source.clone(),
|
||||||
media_type: String::from("text/markdown"),
|
media_type: String::from("text/markdown"),
|
||||||
}).expect("Article::into_activity: source error");
|
}).expect("Post::into_activity: source error");
|
||||||
article.object_props.set_published_utctime(Utc.from_utc_datetime(&self.creation_date)).expect("Article::into_activity: published error");
|
article.object_props.set_published_utctime(Utc.from_utc_datetime(&self.creation_date)).expect("Post::into_activity: published error");
|
||||||
article.object_props.set_summary_string(self.subtitle.clone()).expect("Article::into_activity: summary error");
|
article.object_props.set_summary_string(self.subtitle.clone()).expect("Post::into_activity: summary error");
|
||||||
article.object_props.tag = Some(json!(mentions_json.append(&mut tags_json)));
|
article.object_props.tag = Some(json!(mentions_json.append(&mut tags_json)));
|
||||||
article.object_props.set_url_string(self.ap_url.clone()).expect("Article::into_activity: url error");
|
article.object_props.set_url_string(self.ap_url.clone()).expect("Post::into_activity: url error");
|
||||||
article.object_props.set_to_link_vec::<Id>(to.into_iter().map(Id::new).collect()).expect("Article::into_activity: to error");
|
article.object_props.set_to_link_vec::<Id>(to.into_iter().map(Id::new).collect()).expect("Post::into_activity: to error");
|
||||||
article.object_props.set_cc_link_vec::<Id>(vec![]).expect("Article::into_activity: cc error");
|
article.object_props.set_cc_link_vec::<Id>(vec![]).expect("Post::into_activity: cc error");
|
||||||
article
|
article
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_activity(&self, conn: &PgConnection) -> Create {
|
pub fn create_activity(&self, conn: &PgConnection) -> Create {
|
||||||
let article = self.into_activity(conn);
|
let article = self.into_activity(conn);
|
||||||
let mut act = Create::default();
|
let mut act = Create::default();
|
||||||
act.object_props.set_id_string(format!("{}/activity", self.ap_url)).expect("Article::create_activity: id error");
|
act.object_props.set_id_string(format!("{}/activity", self.ap_url)).expect("Post::create_activity: id error");
|
||||||
act.object_props.set_to_link_vec::<Id>(article.object_props.to_link_vec().expect("Article::create_activity: Couldn't copy 'to'"))
|
act.object_props.set_to_link_vec::<Id>(article.object_props.to_link_vec().expect("Post::create_activity: Couldn't copy 'to'"))
|
||||||
.expect("Article::create_activity: to error");
|
.expect("Post::create_activity: to error");
|
||||||
act.object_props.set_cc_link_vec::<Id>(article.object_props.cc_link_vec().expect("Article::create_activity: Couldn't copy 'cc'"))
|
act.object_props.set_cc_link_vec::<Id>(article.object_props.cc_link_vec().expect("Post::create_activity: Couldn't copy 'cc'"))
|
||||||
.expect("Article::create_activity: cc error");
|
.expect("Post::create_activity: cc error");
|
||||||
act.create_props.set_actor_link(Id::new(self.get_authors(conn)[0].clone().ap_url)).expect("Article::create_activity: actor error");
|
act.create_props.set_actor_link(Id::new(self.get_authors(conn)[0].clone().ap_url)).expect("Post::create_activity: actor error");
|
||||||
act.create_props.set_object_object(article).expect("Article::create_activity: object error");
|
act.create_props.set_object_object(article).expect("Post::create_activity: object error");
|
||||||
act
|
act
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_activity(&self, conn: &PgConnection) -> Update {
|
||||||
|
let article = self.into_activity(conn);
|
||||||
|
let mut act = Update::default();
|
||||||
|
act.object_props.set_id_string(format!("{}/update-{}", self.ap_url, Utc::now().timestamp())).expect("Post::update_activity: id error");
|
||||||
|
act.object_props.set_to_link_vec::<Id>(article.object_props.to_link_vec().expect("Post::update_activity: Couldn't copy 'to'"))
|
||||||
|
.expect("Post::update_activity: to error");
|
||||||
|
act.object_props.set_cc_link_vec::<Id>(article.object_props.cc_link_vec().expect("Post::update_activity: Couldn't copy 'cc'"))
|
||||||
|
.expect("Post::update_activity: cc error");
|
||||||
|
act.update_props.set_actor_link(Id::new(self.get_authors(conn)[0].clone().ap_url)).expect("Post::update_activity: actor error");
|
||||||
|
act.update_props.set_object_object(article).expect("Article::update_activity: object error");
|
||||||
|
act
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_update(&mut self, conn: &PgConnection, updated: Article) {
|
||||||
|
if let Ok(title) = updated.object_props.name_string() {
|
||||||
|
self.slug = title.to_kebab_case();
|
||||||
|
self.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(content) = updated.object_props.content_string() {
|
||||||
|
self.content = SafeString::new(&content);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(subtitle) = updated.object_props.summary_string() {
|
||||||
|
self.subtitle = subtitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(ap_url) = updated.object_props.url_string() {
|
||||||
|
self.ap_url = ap_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(source) = updated.ap_object_props.source_object::<Source>() {
|
||||||
|
self.source = source.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update(conn);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_json(&self, conn: &PgConnection) -> serde_json::Value {
|
pub fn to_json(&self, conn: &PgConnection) -> serde_json::Value {
|
||||||
let blog = self.get_blog(conn);
|
let blog = self.get_blog(conn);
|
||||||
json!({
|
json!({
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use activitypub::{activity::{Announce, Create, Delete, Like, Undo}, object::Tombstone};
|
use activitypub::{activity::{Announce, Create, Delete, Like, Undo, Update}, object::Tombstone};
|
||||||
use diesel::PgConnection;
|
use diesel::PgConnection;
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
@ -63,6 +63,11 @@ pub trait Inbox {
|
|||||||
_ => Err(InboxError::CantUndo)?
|
_ => Err(InboxError::CantUndo)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"Update" => {
|
||||||
|
let act: Update = serde_json::from_value(act.clone())?;
|
||||||
|
Post::handle_update(conn, act.update_props.object_object()?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
_ => Err(InboxError::InvalidType)?
|
_ => Err(InboxError::InvalidType)?
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -90,12 +90,116 @@ fn new(blog: String, user: User, conn: DbConn) -> Template {
|
|||||||
Template::render("posts/new", json!({
|
Template::render("posts/new", json!({
|
||||||
"account": user.to_json(&*conn),
|
"account": user.to_json(&*conn),
|
||||||
"instance": Instance::get_local(&*conn),
|
"instance": Instance::get_local(&*conn),
|
||||||
|
"editing": false,
|
||||||
"errors": null,
|
"errors": null,
|
||||||
"form": null
|
"form": null
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/~/<blog>/<slug>/edit")]
|
||||||
|
fn edit(blog: String, slug: String, user: User, conn: DbConn) -> Template {
|
||||||
|
let b = Blog::find_by_fqn(&*conn, blog.to_string());
|
||||||
|
let post = b.clone().and_then(|blog| Post::find_by_slug(&*conn, slug, blog.id)).expect("Post to edit not found");
|
||||||
|
|
||||||
|
if !user.is_author_in(&*conn, b.clone().unwrap()) {
|
||||||
|
Template::render("errors/403", json!({
|
||||||
|
"error_message": "You are not author in this blog."
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Template::render("posts/new", json!({
|
||||||
|
"account": user.to_json(&*conn),
|
||||||
|
"instance": Instance::get_local(&*conn),
|
||||||
|
"editing": true,
|
||||||
|
"errors": null,
|
||||||
|
"form": NewPostForm {
|
||||||
|
title: post.title.clone(),
|
||||||
|
subtitle: post.subtitle.clone(),
|
||||||
|
content: post.source.clone(),
|
||||||
|
tags: Tag::for_post(&*conn, post.id)
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| t.tag)
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", "),
|
||||||
|
license: post.license.clone(),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/~/<blog>/<slug>/edit", data = "<data>")]
|
||||||
|
fn update(blog: String, slug: String, user: User, conn: DbConn, data: LenientForm<NewPostForm>, worker: State<Pool<ThunkWorker<()>>>) -> Result<Redirect, Template> {
|
||||||
|
let b = Blog::find_by_fqn(&*conn, blog.to_string());
|
||||||
|
let mut post = b.clone().and_then(|blog| Post::find_by_slug(&*conn, slug, blog.id)).expect("Post to update not found");
|
||||||
|
|
||||||
|
let form = data.get();
|
||||||
|
let new_slug = form.title.to_string().to_kebab_case();
|
||||||
|
|
||||||
|
let mut errors = match form.validate() {
|
||||||
|
Ok(_) => ValidationErrors::new(),
|
||||||
|
Err(e) => e
|
||||||
|
};
|
||||||
|
if let Some(_) = Post::find_by_slug(&*conn, new_slug.clone(), b.unwrap().id) {
|
||||||
|
errors.add("title", ValidationError {
|
||||||
|
code: Cow::from("existing_slug"),
|
||||||
|
message: Some(Cow::from("A post with the same title already exists.")),
|
||||||
|
params: HashMap::new()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.is_empty() {
|
||||||
|
if !user.is_author_in(&*conn, b.clone().unwrap()) {
|
||||||
|
// actually it's not "Ok"…
|
||||||
|
Ok(Redirect::to(uri!(super::blogs::details: name = blog)))
|
||||||
|
} else {
|
||||||
|
let (content, mentions) = utils::md_to_html(form.content.to_string().as_ref());
|
||||||
|
|
||||||
|
let license = if form.license.len() > 0 {
|
||||||
|
form.license.to_string()
|
||||||
|
} else {
|
||||||
|
Instance::get_local(&*conn).map(|i| i.default_license).unwrap_or(String::from("CC-0"))
|
||||||
|
};
|
||||||
|
|
||||||
|
post.slug = new_slug.clone();
|
||||||
|
post.title = form.title.clone();
|
||||||
|
post.subtitle = form.subtitle.clone();
|
||||||
|
post.content = SafeString::new(&content);
|
||||||
|
post.source = form.content.clone();
|
||||||
|
post.license = license;
|
||||||
|
post.update(&*conn);
|
||||||
|
let post = post.update_ap_url(&*conn);
|
||||||
|
|
||||||
|
for m in mentions.into_iter() {
|
||||||
|
Mention::from_activity(&*conn, Mention::build_activity(&*conn, m), post.id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_tags = Tag::for_post(&*conn, post.id).into_iter().map(|t| t.tag).collect::<Vec<_>>();
|
||||||
|
let tags = form.tags.split(",").map(|t| t.trim().to_camel_case()).filter(|t| t.len() > 0 && !old_tags.contains(t));
|
||||||
|
for tag in tags {
|
||||||
|
Tag::insert(&*conn, NewTag {
|
||||||
|
tag: tag,
|
||||||
|
is_hastag: false,
|
||||||
|
post_id: post.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let act = post.update_activity(&*conn);
|
||||||
|
let followers = user.get_followers(&*conn);
|
||||||
|
worker.execute(Thunk::of(move || broadcast(&user, act, followers)));
|
||||||
|
|
||||||
|
Ok(Redirect::to(uri!(details: blog = blog, slug = slug)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(Template::render("posts/new", json!({
|
||||||
|
"account": user.to_json(&*conn),
|
||||||
|
"instance": Instance::get_local(&*conn),
|
||||||
|
"editing": false,
|
||||||
|
"errors": errors.inner(),
|
||||||
|
"form": form
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(FromForm, Validate, Serialize)]
|
#[derive(FromForm, Validate, Serialize)]
|
||||||
struct NewPostForm {
|
struct NewPostForm {
|
||||||
#[validate(custom(function = "valid_slug", message = "Invalid title"))]
|
#[validate(custom(function = "valid_slug", message = "Invalid title"))]
|
||||||
@ -187,12 +291,14 @@ fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: D
|
|||||||
Err(Template::render("posts/new", json!({
|
Err(Template::render("posts/new", json!({
|
||||||
"account": user.to_json(&*conn),
|
"account": user.to_json(&*conn),
|
||||||
"instance": Instance::get_local(&*conn),
|
"instance": Instance::get_local(&*conn),
|
||||||
|
"editing": false,
|
||||||
"errors": errors.inner(),
|
"errors": errors.inner(),
|
||||||
"form": form
|
"form": form
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[get("/~/<blog_name>/<slug>/delete")]
|
#[get("/~/<blog_name>/<slug>/delete")]
|
||||||
fn delete(blog_name: String, slug: String, conn: DbConn, user: User, worker: State<Pool<ThunkWorker<()>>>) -> Redirect {
|
fn delete(blog_name: String, slug: String, conn: DbConn, user: User, worker: State<Pool<ThunkWorker<()>>>) -> Redirect {
|
||||||
let post = Blog::find_by_fqn(&*conn, blog_name.clone())
|
let post = Blog::find_by_fqn(&*conn, blog_name.clone())
|
||||||
|
Loading…
Reference in New Issue
Block a user