parent
70ef4d6a74
commit
cea548b821
|
@ -1,4 +1,4 @@
|
||||||
use activitypub::{Object, activity::Create};
|
use activitypub::{Object, activity::{Create, Delete}};
|
||||||
|
|
||||||
use activity_pub::Id;
|
use activity_pub::Id;
|
||||||
|
|
||||||
|
@ -29,9 +29,10 @@ pub trait Notify<C> {
|
||||||
fn notify(&self, conn: &C);
|
fn notify(&self, conn: &C);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Deletable<C> {
|
pub trait Deletable<C, A> {
|
||||||
/// true if success
|
fn delete(&self, conn: &C) -> A;
|
||||||
fn delete_activity(conn: &C, id: Id) -> bool;
|
fn delete_id(id: String, conn: &C);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait WithInbox {
|
pub trait WithInbox {
|
||||||
|
|
|
@ -48,19 +48,6 @@ impl Like {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&self, conn: &PgConnection) -> activity::Undo {
|
|
||||||
diesel::delete(self).execute(conn).unwrap();
|
|
||||||
|
|
||||||
let mut act = activity::Undo::default();
|
|
||||||
act.undo_props.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).expect("Like::delete: actor error");
|
|
||||||
act.undo_props.set_object_object(self.into_activity(conn)).expect("Like::delete: object error");
|
|
||||||
act.object_props.set_id_string(format!("{}#delete", self.ap_url)).expect("Like::delete: id error");
|
|
||||||
act.object_props.set_to_link(Id::new(PUBLIC_VISIBILTY.to_string())).expect("Like::delete: to error");
|
|
||||||
act.object_props.set_cc_link_vec::<Id>(vec![]).expect("Like::delete: cc error");
|
|
||||||
|
|
||||||
act
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_activity(&self, conn: &PgConnection) -> activity::Like {
|
pub fn into_activity(&self, conn: &PgConnection) -> activity::Like {
|
||||||
let mut act = activity::Like::default();
|
let mut act = activity::Like::default();
|
||||||
act.like_props.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).expect("Like::into_activity: actor error");
|
act.like_props.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).expect("Like::into_activity: actor error");
|
||||||
|
@ -100,13 +87,23 @@ impl Notify<PgConnection> for Like {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deletable<PgConnection> for Like {
|
impl Deletable<PgConnection, activity::Undo> for Like {
|
||||||
fn delete_activity(conn: &PgConnection, id: Id) -> bool {
|
fn delete(&self, conn: &PgConnection) -> activity::Undo {
|
||||||
|
diesel::delete(self).execute(conn).unwrap();
|
||||||
|
|
||||||
|
let mut act = activity::Undo::default();
|
||||||
|
act.undo_props.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).expect("Like::delete: actor error");
|
||||||
|
act.undo_props.set_object_object(self.into_activity(conn)).expect("Like::delete: object error");
|
||||||
|
act.object_props.set_id_string(format!("{}#delete", self.ap_url)).expect("Like::delete: id error");
|
||||||
|
act.object_props.set_to_link(Id::new(PUBLIC_VISIBILTY.to_string())).expect("Like::delete: to error");
|
||||||
|
act.object_props.set_cc_link_vec::<Id>(vec![]).expect("Like::delete: cc error");
|
||||||
|
|
||||||
|
act
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_id(id: String, conn: &PgConnection) {
|
||||||
if let Some(like) = Like::find_by_ap_url(conn, id.into()) {
|
if let Some(like) = Like::find_by_ap_url(conn, id.into()) {
|
||||||
like.delete(conn);
|
like.delete(conn);
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use activitypub::{
|
use activitypub::{
|
||||||
activity::Create,
|
activity::{Create, Delete},
|
||||||
link,
|
link,
|
||||||
object::Article
|
object::{Article, Tombstone}
|
||||||
};
|
};
|
||||||
use chrono::{NaiveDateTime, TimeZone, Utc};
|
use chrono::{NaiveDateTime, TimeZone, Utc};
|
||||||
use diesel::{self, PgConnection, RunQueryDsl, QueryDsl, ExpressionMethods, BelongingToDsl, dsl::any};
|
use diesel::{self, PgConnection, RunQueryDsl, QueryDsl, ExpressionMethods, BelongingToDsl, dsl::any};
|
||||||
|
@ -10,7 +10,7 @@ use serde_json;
|
||||||
|
|
||||||
use plume_common::activity_pub::{
|
use plume_common::activity_pub::{
|
||||||
PUBLIC_VISIBILTY, Id, IntoId,
|
PUBLIC_VISIBILTY, Id, IntoId,
|
||||||
inbox::FromActivity
|
inbox::{Deletable, FromActivity}
|
||||||
};
|
};
|
||||||
use {BASE_URL, ap_url};
|
use {BASE_URL, ap_url};
|
||||||
use blogs::Blog;
|
use blogs::Blog;
|
||||||
|
@ -273,6 +273,27 @@ impl FromActivity<Article, PgConnection> for Post {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deletable<PgConnection, Delete> for Post {
|
||||||
|
fn delete(&self, conn: &PgConnection) -> Delete {
|
||||||
|
let mut act = Delete::default();
|
||||||
|
act.delete_props.set_actor_link(self.get_authors(conn)[0].clone().into_id()).expect("Post::delete: actor error");
|
||||||
|
|
||||||
|
let mut tombstone = Tombstone::default();
|
||||||
|
tombstone.object_props.set_id_string(self.ap_url.clone()).expect("Post::delete: object.id error");
|
||||||
|
act.delete_props.set_object_object(tombstone).expect("Post::delete: object error");
|
||||||
|
|
||||||
|
act.object_props.set_id_string(format!("{}#delete", self.ap_url)).expect("Post::delete: id error");
|
||||||
|
act.object_props.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILTY)]).expect("Post::delete: to error");
|
||||||
|
|
||||||
|
diesel::delete(self).execute(conn).expect("Post::delete: DB error");
|
||||||
|
act
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_id(id: String, conn: &PgConnection) {
|
||||||
|
Post::find_by_ap_url(conn, id).map(|p| p.delete(conn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IntoId for Post {
|
impl IntoId for Post {
|
||||||
fn into_id(self) -> Id {
|
fn into_id(self) -> Id {
|
||||||
Id::new(self.ap_url.clone())
|
Id::new(self.ap_url.clone())
|
||||||
|
|
|
@ -59,19 +59,6 @@ impl Reshare {
|
||||||
User::get(conn, self.user_id)
|
User::get(conn, self.user_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&self, conn: &PgConnection) -> Undo {
|
|
||||||
diesel::delete(self).execute(conn).unwrap();
|
|
||||||
|
|
||||||
let mut act = Undo::default();
|
|
||||||
act.undo_props.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).unwrap();
|
|
||||||
act.undo_props.set_object_object(self.into_activity(conn)).unwrap();
|
|
||||||
act.object_props.set_id_string(format!("{}#delete", self.ap_url)).expect("Reshare::delete: id error");
|
|
||||||
act.object_props.set_to_link(Id::new(PUBLIC_VISIBILTY.to_string())).expect("Reshare::delete: to error");
|
|
||||||
act.object_props.set_cc_link_vec::<Id>(vec![]).expect("Reshare::delete: cc error");
|
|
||||||
|
|
||||||
act
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_activity(&self, conn: &PgConnection) -> Announce {
|
pub fn into_activity(&self, conn: &PgConnection) -> Announce {
|
||||||
let mut act = Announce::default();
|
let mut act = Announce::default();
|
||||||
act.announce_props.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).unwrap();
|
act.announce_props.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).unwrap();
|
||||||
|
@ -111,13 +98,23 @@ impl Notify<PgConnection> for Reshare {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deletable<PgConnection> for Reshare {
|
impl Deletable<PgConnection, Undo> for Reshare {
|
||||||
fn delete_activity(conn: &PgConnection, id: Id) -> bool {
|
fn delete(&self, conn: &PgConnection) -> Undo {
|
||||||
if let Some(reshare) = Reshare::find_by_ap_url(conn, id.into()) {
|
diesel::delete(self).execute(conn).unwrap();
|
||||||
|
|
||||||
|
let mut act = Undo::default();
|
||||||
|
act.undo_props.set_actor_link(User::get(conn, self.user_id).unwrap().into_id()).unwrap();
|
||||||
|
act.undo_props.set_object_object(self.into_activity(conn)).unwrap();
|
||||||
|
act.object_props.set_id_string(format!("{}#delete", self.ap_url)).expect("Reshare::delete: id error");
|
||||||
|
act.object_props.set_to_link(Id::new(PUBLIC_VISIBILTY.to_string())).expect("Reshare::delete: to error");
|
||||||
|
act.object_props.set_cc_link_vec::<Id>(vec![]).expect("Reshare::delete: cc error");
|
||||||
|
|
||||||
|
act
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_id(id: String, conn: &PgConnection) {
|
||||||
|
if let Some(reshare) = Reshare::find_by_ap_url(conn, id) {
|
||||||
reshare.delete(conn);
|
reshare.delete(conn);
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -420,3 +420,6 @@ msgstr ""
|
||||||
|
|
||||||
msgid "Read the detailed rules"
|
msgid "Read the detailed rules"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Delete this article"
|
||||||
|
msgstr ""
|
||||||
|
|
11
src/inbox.rs
11
src/inbox.rs
|
@ -1,4 +1,4 @@
|
||||||
use activitypub::activity::{Announce, Create, Like, Undo};
|
use activitypub::{activity::{Announce, Create, Delete, Like, Undo}, object::Tombstone};
|
||||||
use diesel::PgConnection;
|
use diesel::PgConnection;
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
@ -32,6 +32,11 @@ pub trait Inbox {
|
||||||
Err(InboxError::InvalidType)?
|
Err(InboxError::InvalidType)?
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Delete" => {
|
||||||
|
let act: Delete = serde_json::from_value(act.clone())?;
|
||||||
|
Post::delete_id(act.delete_props.object_object::<Tombstone>()?.object_props.id_string()?, conn);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
"Follow" => {
|
"Follow" => {
|
||||||
Follow::from_activity(conn, serde_json::from_value(act.clone())?, actor_id);
|
Follow::from_activity(conn, serde_json::from_value(act.clone())?, actor_id);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -44,11 +49,11 @@ pub trait Inbox {
|
||||||
let act: Undo = serde_json::from_value(act.clone())?;
|
let act: Undo = serde_json::from_value(act.clone())?;
|
||||||
match act.undo_props.object["type"].as_str().unwrap() {
|
match act.undo_props.object["type"].as_str().unwrap() {
|
||||||
"Like" => {
|
"Like" => {
|
||||||
likes::Like::delete_activity(conn, Id::new(act.undo_props.object_object::<Like>()?.object_props.id_string()?));
|
likes::Like::delete_id(act.undo_props.object_object::<Like>()?.object_props.id_string()?, conn);
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
"Announce" => {
|
"Announce" => {
|
||||||
Reshare::delete_activity(conn, Id::new(act.undo_props.object_object::<Announce>()?.object_props.id_string()?));
|
Reshare::delete_id(act.undo_props.object_object::<Announce>()?.object_props.id_string()?, conn);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
_ => Err(InboxError::CantUndo)?
|
_ => Err(InboxError::CantUndo)?
|
||||||
|
|
|
@ -68,6 +68,7 @@ fn main() {
|
||||||
routes::posts::new,
|
routes::posts::new,
|
||||||
routes::posts::new_auth,
|
routes::posts::new_auth,
|
||||||
routes::posts::create,
|
routes::posts::create,
|
||||||
|
routes::posts::delete,
|
||||||
|
|
||||||
routes::reshares::create,
|
routes::reshares::create,
|
||||||
routes::reshares::create_auth,
|
routes::reshares::create_auth,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use rocket::{State, response::{Redirect, Flash}};
|
use rocket::{State, response::{Redirect, Flash}};
|
||||||
use workerpool::{Pool, thunk::*};
|
use workerpool::{Pool, thunk::*};
|
||||||
|
|
||||||
use plume_common::activity_pub::{broadcast, inbox::Notify};
|
use plume_common::activity_pub::{broadcast, inbox::{Notify, Deletable}};
|
||||||
use plume_common::utils;
|
use plume_common::utils;
|
||||||
use plume_models::{
|
use plume_models::{
|
||||||
blogs::Blog,
|
blogs::Blog,
|
||||||
|
|
|
@ -8,7 +8,7 @@ use std::{collections::HashMap, borrow::Cow};
|
||||||
use validator::{Validate, ValidationError, ValidationErrors};
|
use validator::{Validate, ValidationError, ValidationErrors};
|
||||||
use workerpool::{Pool, thunk::*};
|
use workerpool::{Pool, thunk::*};
|
||||||
|
|
||||||
use plume_common::activity_pub::{broadcast, ActivityStream, ApRequest};
|
use plume_common::activity_pub::{broadcast, ActivityStream, ApRequest, inbox::Deletable};
|
||||||
use plume_common::utils;
|
use plume_common::utils;
|
||||||
use plume_models::{
|
use plume_models::{
|
||||||
blogs::*,
|
blogs::*,
|
||||||
|
@ -53,10 +53,11 @@ fn details_response(blog: String, slug: String, conn: DbConn, user: Option<User>
|
||||||
"has_liked": user.clone().map(|u| u.has_liked(&*conn, &post)).unwrap_or(false),
|
"has_liked": user.clone().map(|u| u.has_liked(&*conn, &post)).unwrap_or(false),
|
||||||
"n_reshares": post.get_reshares(&*conn).len(),
|
"n_reshares": post.get_reshares(&*conn).len(),
|
||||||
"has_reshared": user.clone().map(|u| u.has_reshared(&*conn, &post)).unwrap_or(false),
|
"has_reshared": user.clone().map(|u| u.has_reshared(&*conn, &post)).unwrap_or(false),
|
||||||
"account": user,
|
"account": &user,
|
||||||
"date": &post.creation_date.timestamp(),
|
"date": &post.creation_date.timestamp(),
|
||||||
"previous": query.and_then(|q| q.responding_to.map(|r| Comment::get(&*conn, r).expect("Error retrieving previous comment").to_json(&*conn, &vec![]))),
|
"previous": query.and_then(|q| q.responding_to.map(|r| Comment::get(&*conn, r).expect("Error retrieving previous comment").to_json(&*conn, &vec![]))),
|
||||||
"user_fqn": user.map(|u| u.get_fqn(&*conn)).unwrap_or(String::new())
|
"user_fqn": user.clone().map(|u| u.get_fqn(&*conn)).unwrap_or(String::new()),
|
||||||
|
"is_author": user.map(|u| post.get_authors(&*conn).into_iter().any(|a| u.id == a.id)).unwrap_or(false)
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -176,3 +177,23 @@ fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: D
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/~/<blog_name>/<slug>/delete")]
|
||||||
|
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())
|
||||||
|
.and_then(|blog| Post::find_by_slug(&*conn, slug.clone(), blog.id));
|
||||||
|
|
||||||
|
if let Some(post) = post {
|
||||||
|
if !post.get_authors(&*conn).into_iter().any(|a| a.id == user.id) {
|
||||||
|
Redirect::to(uri!(details: blog = blog_name.clone(), slug = slug.clone()))
|
||||||
|
} else {
|
||||||
|
let audience = user.get_followers(&*conn);
|
||||||
|
let delete_activity = post.delete(&*conn);
|
||||||
|
worker.execute(Thunk::of(move || broadcast(&user, delete_activity, audience)));
|
||||||
|
|
||||||
|
Redirect::to(uri!(super::blogs::details: name = blog_name))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Redirect::to(uri!(super::blogs::details: name = blog_name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use rocket::{State, response::{Redirect, Flash}};
|
use rocket::{State, response::{Redirect, Flash}};
|
||||||
use workerpool::{Pool, thunk::*};
|
use workerpool::{Pool, thunk::*};
|
||||||
|
|
||||||
use plume_common::activity_pub::{broadcast, inbox::Notify};
|
use plume_common::activity_pub::{broadcast, inbox::{Deletable, Notify}};
|
||||||
use plume_common::utils;
|
use plume_common::utils;
|
||||||
use plume_models::{
|
use plume_models::{
|
||||||
blogs::Blog,
|
blogs::Blog,
|
||||||
|
|
|
@ -215,7 +215,7 @@ fn create_admin(instance: Instance, conn: DbConn) {
|
||||||
fn check_native_deps() {
|
fn check_native_deps() {
|
||||||
let mut not_found = Vec::new();
|
let mut not_found = Vec::new();
|
||||||
if !try_run("psql") {
|
if !try_run("psql") {
|
||||||
not_found.push(("PostgreSQL", "sudo apt install postgres"));
|
not_found.push(("PostgreSQL", "sudo apt install postgresql"));
|
||||||
}
|
}
|
||||||
if !try_run("gettext") {
|
if !try_run("gettext") {
|
||||||
not_found.push(("GetText", "sudo apt install gettext"))
|
not_found.push(("GetText", "sudo apt install gettext"))
|
||||||
|
|
|
@ -22,6 +22,10 @@
|
||||||
}}</a></span>
|
}}</a></span>
|
||||||
—
|
—
|
||||||
<span class="date">{{ date | date(format="%B %e, %Y") }}</span>
|
<span class="date">{{ date | date(format="%B %e, %Y") }}</span>
|
||||||
|
—
|
||||||
|
{% if is_author %}
|
||||||
|
<a href="{{ article.url}}delete">{{ "Delete this article" | _ }}</a>
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
<article>
|
<article>
|
||||||
{{ article.post.content | safe }}
|
{{ article.post.content | safe }}
|
||||||
|
|
Loading…
Reference in New Issue