Avoid panics (#392)
- Use `Result` as much as possible - Display errors instead of panicking TODO (maybe in another PR? this one is already quite big): - Find a way to merge Ructe/ErrorPage types, so that we can have routes returning `Result<X, ErrorPage>` instead of panicking when we have an `Error` - Display more details about the error, to make it easier to debug (sorry, this isn't going to be fun to review, the diff is huge, but it is always the same changes)
This commit is contained in:
@@ -8,6 +8,7 @@ use rocket::{
|
||||
|
||||
use db_conn::DbConn;
|
||||
use schema::api_tokens;
|
||||
use {Error, Result};
|
||||
|
||||
#[derive(Clone, Queryable)]
|
||||
pub struct ApiToken {
|
||||
@@ -63,22 +64,39 @@ impl ApiToken {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'r> FromRequest<'a, 'r> for ApiToken {
|
||||
type Error = ();
|
||||
#[derive(Debug)]
|
||||
pub enum TokenError {
|
||||
/// The Authorization header was not present
|
||||
NoHeader,
|
||||
|
||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<ApiToken, ()> {
|
||||
/// The type of the token was not specified ("Basic" or "Bearer" for instance)
|
||||
NoType,
|
||||
|
||||
/// No value was provided
|
||||
NoValue,
|
||||
|
||||
/// Error while connecting to the database to retrieve all the token metadata
|
||||
DbError,
|
||||
}
|
||||
|
||||
impl<'a, 'r> FromRequest<'a, 'r> for ApiToken {
|
||||
type Error = TokenError;
|
||||
|
||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<ApiToken, TokenError> {
|
||||
let headers: Vec<_> = request.headers().get("Authorization").collect();
|
||||
if headers.len() != 1 {
|
||||
return Outcome::Failure((Status::BadRequest, ()));
|
||||
return Outcome::Failure((Status::BadRequest, TokenError::NoHeader));
|
||||
}
|
||||
|
||||
let mut parsed_header = headers[0].split(' ');
|
||||
let auth_type = parsed_header.next().expect("Expect a token type");
|
||||
let val = parsed_header.next().expect("Expect a token value");
|
||||
let auth_type = parsed_header.next()
|
||||
.map_or_else(|| Outcome::Failure((Status::BadRequest, TokenError::NoType)), |t| Outcome::Success(t))?;
|
||||
let val = parsed_header.next()
|
||||
.map_or_else(|| Outcome::Failure((Status::BadRequest, TokenError::NoValue)), |t| Outcome::Success(t))?;
|
||||
|
||||
if auth_type == "Bearer" {
|
||||
let conn = request.guard::<DbConn>().expect("Couldn't connect to DB");
|
||||
if let Some(token) = ApiToken::find_by_value(&*conn, val) {
|
||||
let conn = request.guard::<DbConn>().map_failure(|_| (Status::InternalServerError, TokenError::DbError))?;
|
||||
if let Ok(token) = ApiToken::find_by_value(&*conn, val) {
|
||||
return Outcome::Success(token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use canapi::{Error, Provider};
|
||||
use canapi::{Error as ApiError, Provider};
|
||||
use chrono::NaiveDateTime;
|
||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||
|
||||
use plume_api::apps::AppEndpoint;
|
||||
use plume_common::utils::random_hex;
|
||||
use schema::apps;
|
||||
use Connection;
|
||||
use {Connection, Error, Result, ApiResult};
|
||||
|
||||
#[derive(Clone, Queryable)]
|
||||
pub struct App {
|
||||
@@ -31,7 +31,7 @@ pub struct NewApp {
|
||||
impl Provider<Connection> for App {
|
||||
type Data = AppEndpoint;
|
||||
|
||||
fn get(_conn: &Connection, _id: i32) -> Result<AppEndpoint, Error> {
|
||||
fn get(_conn: &Connection, _id: i32) -> ApiResult<AppEndpoint> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ impl Provider<Connection> for App {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn create(conn: &Connection, data: AppEndpoint) -> Result<AppEndpoint, Error> {
|
||||
fn create(conn: &Connection, data: AppEndpoint) -> ApiResult<AppEndpoint> {
|
||||
let client_id = random_hex();
|
||||
|
||||
let client_secret = random_hex();
|
||||
@@ -52,7 +52,7 @@ impl Provider<Connection> for App {
|
||||
redirect_uri: data.redirect_uri,
|
||||
website: data.website,
|
||||
},
|
||||
);
|
||||
).map_err(|_| ApiError::NotFound("Couldn't register app".into()))?;
|
||||
|
||||
Ok(AppEndpoint {
|
||||
id: Some(app.id),
|
||||
@@ -64,7 +64,7 @@ impl Provider<Connection> for App {
|
||||
})
|
||||
}
|
||||
|
||||
fn update(_conn: &Connection, _id: i32, _new_data: AppEndpoint) -> Result<AppEndpoint, Error> {
|
||||
fn update(_conn: &Connection, _id: i32, _new_data: AppEndpoint) -> ApiResult<AppEndpoint> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||
|
||||
use schema::blog_authors;
|
||||
use {Error, Result};
|
||||
|
||||
#[derive(Clone, Queryable, Identifiable)]
|
||||
pub struct BlogAuthor {
|
||||
|
||||
+197
-267
@@ -26,7 +26,7 @@ use safe_string::SafeString;
|
||||
use schema::blogs;
|
||||
use search::Searcher;
|
||||
use users::User;
|
||||
use {Connection, BASE_URL, USE_HTTPS};
|
||||
use {Connection, BASE_URL, USE_HTTPS, Error, Result};
|
||||
|
||||
pub type CustomGroup = CustomObject<ApSignature, Group>;
|
||||
|
||||
@@ -67,11 +67,11 @@ impl Blog {
|
||||
find_by!(blogs, find_by_ap_url, ap_url as &str);
|
||||
find_by!(blogs, find_by_name, actor_id as &str, instance_id as i32);
|
||||
|
||||
pub fn get_instance(&self, conn: &Connection) -> Instance {
|
||||
Instance::get(conn, self.instance_id).expect("Blog::get_instance: instance not found error")
|
||||
pub fn get_instance(&self, conn: &Connection) -> Result<Instance> {
|
||||
Instance::get(conn, self.instance_id)
|
||||
}
|
||||
|
||||
pub fn list_authors(&self, conn: &Connection) -> Vec<User> {
|
||||
pub fn list_authors(&self, conn: &Connection) -> Result<Vec<User>> {
|
||||
use schema::blog_authors;
|
||||
use schema::users;
|
||||
let authors_ids = blog_authors::table
|
||||
@@ -80,19 +80,19 @@ impl Blog {
|
||||
users::table
|
||||
.filter(users::id.eq_any(authors_ids))
|
||||
.load::<User>(conn)
|
||||
.expect("Blog::list_authors: author loading error")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn count_authors(&self, conn: &Connection) -> i64 {
|
||||
pub fn count_authors(&self, conn: &Connection) -> Result<i64> {
|
||||
use schema::blog_authors;
|
||||
blog_authors::table
|
||||
.filter(blog_authors::blog_id.eq(self.id))
|
||||
.count()
|
||||
.get_result(conn)
|
||||
.expect("Blog::count_authors: count loading error")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn find_for_author(conn: &Connection, author: &User) -> Vec<Blog> {
|
||||
pub fn find_for_author(conn: &Connection, author: &User) -> Result<Vec<Blog>> {
|
||||
use schema::blog_authors;
|
||||
let author_ids = blog_authors::table
|
||||
.filter(blog_authors::author_id.eq(author.id))
|
||||
@@ -100,62 +100,40 @@ impl Blog {
|
||||
blogs::table
|
||||
.filter(blogs::id.eq_any(author_ids))
|
||||
.load::<Blog>(conn)
|
||||
.expect("Blog::find_for_author: blog loading error")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn find_local(conn: &Connection, name: &str) -> Option<Blog> {
|
||||
Blog::find_by_name(conn, name, Instance::local_id(conn))
|
||||
pub fn find_local(conn: &Connection, name: &str) -> Result<Blog> {
|
||||
Blog::find_by_name(conn, name, Instance::get_local(conn)?.id)
|
||||
}
|
||||
|
||||
pub fn find_by_fqn(conn: &Connection, fqn: &str) -> Option<Blog> {
|
||||
if fqn.contains('@') {
|
||||
// remote blog
|
||||
match Instance::find_by_domain(
|
||||
conn,
|
||||
fqn.split('@')
|
||||
.last()
|
||||
.expect("Blog::find_by_fqn: unreachable"),
|
||||
) {
|
||||
Some(instance) => match Blog::find_by_name(
|
||||
pub fn find_by_fqn(conn: &Connection, fqn: &str) -> Result<Blog> {
|
||||
let mut split_fqn = fqn.split('@');
|
||||
let actor = split_fqn.next().ok_or(Error::InvalidValue)?;
|
||||
if let Some(domain) = split_fqn.next() { // remote blog
|
||||
Instance::find_by_domain(conn, domain)
|
||||
.and_then(|instance| Blog::find_by_name(conn, actor, instance.id))
|
||||
.or_else(|_| Blog::fetch_from_webfinger(conn, fqn))
|
||||
} else { // local blog
|
||||
Blog::find_local(conn, actor)
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_from_webfinger(conn: &Connection, acct: &str) -> Result<Blog> {
|
||||
resolve(acct.to_owned(), *USE_HTTPS)?.links
|
||||
.into_iter()
|
||||
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
|
||||
.ok_or(Error::Webfinger)
|
||||
.and_then(|l| {
|
||||
Blog::fetch_from_url(
|
||||
conn,
|
||||
fqn.split('@')
|
||||
.nth(0)
|
||||
.expect("Blog::find_by_fqn: unreachable"),
|
||||
instance.id,
|
||||
) {
|
||||
Some(u) => Some(u),
|
||||
None => Blog::fetch_from_webfinger(conn, fqn),
|
||||
},
|
||||
None => Blog::fetch_from_webfinger(conn, fqn),
|
||||
}
|
||||
} else {
|
||||
// local blog
|
||||
Blog::find_local(conn, fqn)
|
||||
}
|
||||
&l.href?
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn fetch_from_webfinger(conn: &Connection, acct: &str) -> Option<Blog> {
|
||||
match resolve(acct.to_owned(), *USE_HTTPS) {
|
||||
Ok(wf) => wf
|
||||
.links
|
||||
.into_iter()
|
||||
.find(|l| l.mime_type == Some(String::from("application/activity+json")))
|
||||
.and_then(|l| {
|
||||
Blog::fetch_from_url(
|
||||
conn,
|
||||
&l.href
|
||||
.expect("Blog::fetch_from_webfinger: href not found error"),
|
||||
)
|
||||
}),
|
||||
Err(details) => {
|
||||
println!("{:?}", details);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_from_url(conn: &Connection, url: &str) -> Option<Blog> {
|
||||
let req = Client::new()
|
||||
fn fetch_from_url(conn: &Connection, url: &str) -> Result<Blog> {
|
||||
let mut res = Client::new()
|
||||
.get(url)
|
||||
.header(
|
||||
ACCEPT,
|
||||
@@ -164,139 +142,109 @@ impl Blog {
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
).expect("Blog::fetch_from_url: accept_header generation error"),
|
||||
)?,
|
||||
)
|
||||
.send();
|
||||
match req {
|
||||
Ok(mut res) => {
|
||||
let text = &res
|
||||
.text()
|
||||
.expect("Blog::fetch_from_url: body reading error");
|
||||
let ap_sign: ApSignature =
|
||||
serde_json::from_str(text).expect("Blog::fetch_from_url: body parsing error");
|
||||
let mut json: CustomGroup =
|
||||
serde_json::from_str(text).expect("Blog::fetch_from_url: body parsing error");
|
||||
json.custom_props = ap_sign; // without this workaround, publicKey is not correctly deserialized
|
||||
Some(Blog::from_activity(
|
||||
conn,
|
||||
&json,
|
||||
Url::parse(url)
|
||||
.expect("Blog::fetch_from_url: url parsing error")
|
||||
.host_str()
|
||||
.expect("Blog::fetch_from_url: host extraction error"),
|
||||
))
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
.send()?;
|
||||
|
||||
let text = &res.text()?;
|
||||
let ap_sign: ApSignature =
|
||||
serde_json::from_str(text)?;
|
||||
let mut json: CustomGroup =
|
||||
serde_json::from_str(text)?;
|
||||
json.custom_props = ap_sign; // without this workaround, publicKey is not correctly deserialized
|
||||
Blog::from_activity(
|
||||
conn,
|
||||
&json,
|
||||
Url::parse(url)?.host_str()?,
|
||||
)
|
||||
}
|
||||
|
||||
fn from_activity(conn: &Connection, acct: &CustomGroup, inst: &str) -> Blog {
|
||||
let instance = match Instance::find_by_domain(conn, inst) {
|
||||
Some(instance) => instance,
|
||||
None => {
|
||||
Instance::insert(
|
||||
conn,
|
||||
NewInstance {
|
||||
public_domain: inst.to_owned(),
|
||||
name: inst.to_owned(),
|
||||
local: false,
|
||||
// We don't really care about all the following for remote instances
|
||||
long_description: SafeString::new(""),
|
||||
short_description: SafeString::new(""),
|
||||
default_license: String::new(),
|
||||
open_registrations: true,
|
||||
short_description_html: String::new(),
|
||||
long_description_html: String::new(),
|
||||
},
|
||||
)
|
||||
}
|
||||
};
|
||||
fn from_activity(conn: &Connection, acct: &CustomGroup, inst: &str) -> Result<Blog> {
|
||||
let instance = Instance::find_by_domain(conn, inst).or_else(|_|
|
||||
Instance::insert(
|
||||
conn,
|
||||
NewInstance {
|
||||
public_domain: inst.to_owned(),
|
||||
name: inst.to_owned(),
|
||||
local: false,
|
||||
// We don't really care about all the following for remote instances
|
||||
long_description: SafeString::new(""),
|
||||
short_description: SafeString::new(""),
|
||||
default_license: String::new(),
|
||||
open_registrations: true,
|
||||
short_description_html: String::new(),
|
||||
long_description_html: String::new(),
|
||||
},
|
||||
)
|
||||
)?;
|
||||
Blog::insert(
|
||||
conn,
|
||||
NewBlog {
|
||||
actor_id: acct
|
||||
.object
|
||||
.ap_actor_props
|
||||
.preferred_username_string()
|
||||
.expect("Blog::from_activity: preferredUsername error"),
|
||||
.preferred_username_string()?,
|
||||
title: acct
|
||||
.object
|
||||
.object_props
|
||||
.name_string()
|
||||
.expect("Blog::from_activity: name error"),
|
||||
.name_string()?,
|
||||
outbox_url: acct
|
||||
.object
|
||||
.ap_actor_props
|
||||
.outbox_string()
|
||||
.expect("Blog::from_activity: outbox error"),
|
||||
.outbox_string()?,
|
||||
inbox_url: acct
|
||||
.object
|
||||
.ap_actor_props
|
||||
.inbox_string()
|
||||
.expect("Blog::from_activity: inbox error"),
|
||||
.inbox_string()?,
|
||||
summary: acct
|
||||
.object
|
||||
.object_props
|
||||
.summary_string()
|
||||
.expect("Blog::from_activity: summary error"),
|
||||
.summary_string()?,
|
||||
instance_id: instance.id,
|
||||
ap_url: acct
|
||||
.object
|
||||
.object_props
|
||||
.id_string()
|
||||
.expect("Blog::from_activity: id error"),
|
||||
.id_string()?,
|
||||
public_key: acct
|
||||
.custom_props
|
||||
.public_key_publickey()
|
||||
.expect("Blog::from_activity: publicKey error")
|
||||
.public_key_pem_string()
|
||||
.expect("Blog::from_activity: publicKey.publicKeyPem error"),
|
||||
.public_key_publickey()?
|
||||
.public_key_pem_string()?,
|
||||
private_key: None,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_activity(&self, _conn: &Connection) -> CustomGroup {
|
||||
pub fn to_activity(&self, _conn: &Connection) -> Result<CustomGroup> {
|
||||
let mut blog = Group::default();
|
||||
blog.ap_actor_props
|
||||
.set_preferred_username_string(self.actor_id.clone())
|
||||
.expect("Blog::to_activity: preferredUsername error");
|
||||
.set_preferred_username_string(self.actor_id.clone())?;
|
||||
blog.object_props
|
||||
.set_name_string(self.title.clone())
|
||||
.expect("Blog::to_activity: name error");
|
||||
.set_name_string(self.title.clone())?;
|
||||
blog.ap_actor_props
|
||||
.set_outbox_string(self.outbox_url.clone())
|
||||
.expect("Blog::to_activity: outbox error");
|
||||
.set_outbox_string(self.outbox_url.clone())?;
|
||||
blog.ap_actor_props
|
||||
.set_inbox_string(self.inbox_url.clone())
|
||||
.expect("Blog::to_activity: inbox error");
|
||||
.set_inbox_string(self.inbox_url.clone())?;
|
||||
blog.object_props
|
||||
.set_summary_string(self.summary.clone())
|
||||
.expect("Blog::to_activity: summary error");
|
||||
.set_summary_string(self.summary.clone())?;
|
||||
blog.object_props
|
||||
.set_id_string(self.ap_url.clone())
|
||||
.expect("Blog::to_activity: id error");
|
||||
.set_id_string(self.ap_url.clone())?;
|
||||
|
||||
let mut public_key = PublicKey::default();
|
||||
public_key
|
||||
.set_id_string(format!("{}#main-key", self.ap_url))
|
||||
.expect("Blog::to_activity: publicKey.id error");
|
||||
.set_id_string(format!("{}#main-key", self.ap_url))?;
|
||||
public_key
|
||||
.set_owner_string(self.ap_url.clone())
|
||||
.expect("Blog::to_activity: publicKey.owner error");
|
||||
.set_owner_string(self.ap_url.clone())?;
|
||||
public_key
|
||||
.set_public_key_pem_string(self.public_key.clone())
|
||||
.expect("Blog::to_activity: publicKey.publicKeyPem error");
|
||||
.set_public_key_pem_string(self.public_key.clone())?;
|
||||
let mut ap_signature = ApSignature::default();
|
||||
ap_signature
|
||||
.set_public_key_publickey(public_key)
|
||||
.expect("Blog::to_activity: publicKey error");
|
||||
.set_public_key_publickey(public_key)?;
|
||||
|
||||
CustomGroup::new(blog, ap_signature)
|
||||
Ok(CustomGroup::new(blog, ap_signature))
|
||||
}
|
||||
|
||||
pub fn update_boxes(&self, conn: &Connection) {
|
||||
let instance = self.get_instance(conn);
|
||||
pub fn update_boxes(&self, conn: &Connection) -> Result<()> {
|
||||
let instance = self.get_instance(conn)?;
|
||||
if self.outbox_url.is_empty() {
|
||||
diesel::update(self)
|
||||
.set(blogs::outbox_url.eq(instance.compute_box(
|
||||
@@ -304,8 +252,7 @@ impl Blog {
|
||||
&self.actor_id,
|
||||
"outbox",
|
||||
)))
|
||||
.execute(conn)
|
||||
.expect("Blog::update_boxes: outbox update error");
|
||||
.execute(conn)?;
|
||||
}
|
||||
|
||||
if self.inbox_url.is_empty() {
|
||||
@@ -315,49 +262,45 @@ impl Blog {
|
||||
&self.actor_id,
|
||||
"inbox",
|
||||
)))
|
||||
.execute(conn)
|
||||
.expect("Blog::update_boxes: inbox update error");
|
||||
.execute(conn)?;
|
||||
}
|
||||
|
||||
if self.ap_url.is_empty() {
|
||||
diesel::update(self)
|
||||
.set(blogs::ap_url.eq(instance.compute_box(BLOG_PREFIX, &self.actor_id, "")))
|
||||
.execute(conn)
|
||||
.expect("Blog::update_boxes: ap_url update error");
|
||||
.execute(conn)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn outbox(&self, conn: &Connection) -> ActivityStream<OrderedCollection> {
|
||||
pub fn outbox(&self, conn: &Connection) -> Result<ActivityStream<OrderedCollection>> {
|
||||
let mut coll = OrderedCollection::default();
|
||||
coll.collection_props.items = serde_json::to_value(self.get_activities(conn))
|
||||
.expect("Blog::outbox: activity serialization error");
|
||||
coll.collection_props.items = serde_json::to_value(self.get_activities(conn)?)?;
|
||||
coll.collection_props
|
||||
.set_total_items_u64(self.get_activities(conn).len() as u64)
|
||||
.expect("Blog::outbox: count serialization error");
|
||||
ActivityStream::new(coll)
|
||||
.set_total_items_u64(self.get_activities(conn)?.len() as u64)?;
|
||||
Ok(ActivityStream::new(coll))
|
||||
}
|
||||
|
||||
fn get_activities(&self, _conn: &Connection) -> Vec<serde_json::Value> {
|
||||
vec![]
|
||||
fn get_activities(&self, _conn: &Connection) -> Result<Vec<serde_json::Value>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
pub fn get_keypair(&self) -> PKey<Private> {
|
||||
pub fn get_keypair(&self) -> Result<PKey<Private>> {
|
||||
PKey::from_rsa(
|
||||
Rsa::private_key_from_pem(
|
||||
self.private_key
|
||||
.clone()
|
||||
.expect("Blog::get_keypair: private key not found error")
|
||||
.clone()?
|
||||
.as_ref(),
|
||||
).expect("Blog::get_keypair: pem parsing error"),
|
||||
).expect("Blog::get_keypair: private key deserialization error")
|
||||
)?,
|
||||
).map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn webfinger(&self, conn: &Connection) -> Webfinger {
|
||||
Webfinger {
|
||||
pub fn webfinger(&self, conn: &Connection) -> Result<Webfinger> {
|
||||
Ok(Webfinger {
|
||||
subject: format!(
|
||||
"acct:{}@{}",
|
||||
self.actor_id,
|
||||
self.get_instance(conn).public_domain
|
||||
self.get_instance(conn)?.public_domain
|
||||
),
|
||||
aliases: vec![self.ap_url.clone()],
|
||||
links: vec![
|
||||
@@ -370,7 +313,7 @@ impl Blog {
|
||||
Link {
|
||||
rel: String::from("http://schemas.google.com/g/2010#updates-from"),
|
||||
mime_type: Some(String::from("application/atom+xml")),
|
||||
href: Some(self.get_instance(conn).compute_box(
|
||||
href: Some(self.get_instance(conn)?.compute_box(
|
||||
BLOG_PREFIX,
|
||||
&self.actor_id,
|
||||
"feed.atom",
|
||||
@@ -384,50 +327,41 @@ impl Blog {
|
||||
template: None,
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_url(conn: &Connection, url: &str) -> Option<Blog> {
|
||||
Blog::find_by_ap_url(conn, url).or_else(|| {
|
||||
pub fn from_url(conn: &Connection, url: &str) -> Result<Blog> {
|
||||
Blog::find_by_ap_url(conn, url).or_else(|_| {
|
||||
// The requested blog was not in the DB
|
||||
// We try to fetch it if it is remote
|
||||
if Url::parse(url)
|
||||
.expect("Blog::from_url: ap_url parsing error")
|
||||
.host_str()
|
||||
.expect("Blog::from_url: host extraction error") != BASE_URL.as_str()
|
||||
{
|
||||
if Url::parse(url)?.host_str()? != BASE_URL.as_str() {
|
||||
Blog::fetch_from_url(conn, url)
|
||||
} else {
|
||||
None
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_fqn(&self, conn: &Connection) -> String {
|
||||
if self.instance_id == Instance::local_id(conn) {
|
||||
if self.instance_id == Instance::get_local(conn).ok().expect("Blog::get_fqn: local instance error").id {
|
||||
self.actor_id.clone()
|
||||
} else {
|
||||
format!(
|
||||
"{}@{}",
|
||||
self.actor_id,
|
||||
self.get_instance(conn).public_domain
|
||||
self.get_instance(conn).ok().expect("Blog::get_fqn: instance error").public_domain
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_json(&self, conn: &Connection) -> serde_json::Value {
|
||||
let mut json = serde_json::to_value(self).expect("Blog::to_json: serialization error");
|
||||
json["fqn"] = json!(self.get_fqn(conn));
|
||||
json
|
||||
}
|
||||
|
||||
pub fn delete(&self, conn: &Connection, searcher: &Searcher) {
|
||||
for post in Post::get_for_blog(conn, &self) {
|
||||
post.delete(&(conn, searcher));
|
||||
pub fn delete(&self, conn: &Connection, searcher: &Searcher) -> Result<()> {
|
||||
for post in Post::get_for_blog(conn, &self)? {
|
||||
post.delete(&(conn, searcher))?;
|
||||
}
|
||||
diesel::delete(self)
|
||||
.execute(conn)
|
||||
.expect("Blog::delete: blog deletion error");
|
||||
.map(|_| ())
|
||||
.map_err(Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,35 +389,33 @@ impl WithInbox for Blog {
|
||||
}
|
||||
|
||||
impl sign::Signer for Blog {
|
||||
type Error = Error;
|
||||
|
||||
fn get_key_id(&self) -> String {
|
||||
format!("{}#main-key", self.ap_url)
|
||||
}
|
||||
|
||||
fn sign(&self, to_sign: &str) -> Vec<u8> {
|
||||
let key = self.get_keypair();
|
||||
fn sign(&self, to_sign: &str) -> Result<Vec<u8>> {
|
||||
let key = self.get_keypair()?;
|
||||
let mut signer =
|
||||
Signer::new(MessageDigest::sha256(), &key).expect("Blog::sign: initialization error");
|
||||
Signer::new(MessageDigest::sha256(), &key)?;
|
||||
signer
|
||||
.update(to_sign.as_bytes())
|
||||
.expect("Blog::sign: content insertion error");
|
||||
.update(to_sign.as_bytes())?;
|
||||
signer
|
||||
.sign_to_vec()
|
||||
.expect("Blog::sign: finalization error")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
fn verify(&self, data: &str, signature: &[u8]) -> bool {
|
||||
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool> {
|
||||
let key = PKey::from_rsa(
|
||||
Rsa::public_key_from_pem(self.public_key.as_ref())
|
||||
.expect("Blog::verify: pem parsing error"),
|
||||
).expect("Blog::verify: deserialization error");
|
||||
let mut verifier = Verifier::new(MessageDigest::sha256(), &key)
|
||||
.expect("Blog::verify: initialization error");
|
||||
Rsa::public_key_from_pem(self.public_key.as_ref())?
|
||||
)?;
|
||||
let mut verifier = Verifier::new(MessageDigest::sha256(), &key)?;
|
||||
verifier
|
||||
.update(data.as_bytes())
|
||||
.expect("Blog::verify: content insertion error");
|
||||
.update(data.as_bytes())?;
|
||||
verifier
|
||||
.verify(&signature)
|
||||
.expect("Blog::verify: finalization error")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,9 +425,9 @@ impl NewBlog {
|
||||
title: String,
|
||||
summary: String,
|
||||
instance_id: i32,
|
||||
) -> NewBlog {
|
||||
) -> Result<NewBlog> {
|
||||
let (pub_key, priv_key) = sign::gen_keypair();
|
||||
NewBlog {
|
||||
Ok(NewBlog {
|
||||
actor_id,
|
||||
title,
|
||||
summary,
|
||||
@@ -503,11 +435,9 @@ impl NewBlog {
|
||||
inbox_url: String::from(""),
|
||||
instance_id,
|
||||
ap_url: String::from(""),
|
||||
public_key: String::from_utf8(pub_key).expect("NewBlog::new_local: public key error"),
|
||||
private_key: Some(
|
||||
String::from_utf8(priv_key).expect("NewBlog::new_local: private key error"),
|
||||
),
|
||||
}
|
||||
public_key: String::from_utf8(pub_key).or(Err(Error::Signature))?,
|
||||
private_key: Some(String::from_utf8(priv_key).or(Err(Error::Signature))?),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,23 +459,23 @@ pub(crate) mod tests {
|
||||
"BlogName".to_owned(),
|
||||
"Blog name".to_owned(),
|
||||
"This is a small blog".to_owned(),
|
||||
Instance::local_id(conn),
|
||||
));
|
||||
blog1.update_boxes(conn);
|
||||
Instance::get_local(conn).unwrap().id
|
||||
).unwrap()).unwrap();
|
||||
blog1.update_boxes(conn).unwrap();
|
||||
let blog2 = Blog::insert(conn, NewBlog::new_local(
|
||||
"MyBlog".to_owned(),
|
||||
"My blog".to_owned(),
|
||||
"Welcome to my blog".to_owned(),
|
||||
Instance::local_id(conn),
|
||||
));
|
||||
blog2.update_boxes(conn);
|
||||
Instance::get_local(conn).unwrap().id
|
||||
).unwrap()).unwrap();
|
||||
blog2.update_boxes(conn).unwrap();
|
||||
let blog3 = Blog::insert(conn, NewBlog::new_local(
|
||||
"WhyILikePlume".to_owned(),
|
||||
"Why I like Plume".to_owned(),
|
||||
"In this blog I will explay you why I like Plume so much".to_owned(),
|
||||
Instance::local_id(conn),
|
||||
));
|
||||
blog3.update_boxes(conn);
|
||||
Instance::get_local(conn).unwrap().id
|
||||
).unwrap()).unwrap();
|
||||
blog3.update_boxes(conn).unwrap();
|
||||
|
||||
BlogAuthor::insert(
|
||||
conn,
|
||||
@@ -554,7 +484,7 @@ pub(crate) mod tests {
|
||||
author_id: users[0].id,
|
||||
is_owner: true,
|
||||
},
|
||||
);
|
||||
).unwrap();
|
||||
|
||||
BlogAuthor::insert(
|
||||
conn,
|
||||
@@ -563,7 +493,7 @@ pub(crate) mod tests {
|
||||
author_id: users[1].id,
|
||||
is_owner: false,
|
||||
},
|
||||
);
|
||||
).unwrap();
|
||||
|
||||
BlogAuthor::insert(
|
||||
conn,
|
||||
@@ -572,7 +502,7 @@ pub(crate) mod tests {
|
||||
author_id: users[1].id,
|
||||
is_owner: true,
|
||||
},
|
||||
);
|
||||
).unwrap();
|
||||
|
||||
BlogAuthor::insert(
|
||||
conn,
|
||||
@@ -581,7 +511,7 @@ pub(crate) mod tests {
|
||||
author_id: users[2].id,
|
||||
is_owner: true,
|
||||
},
|
||||
);
|
||||
).unwrap();
|
||||
(users, vec![ blog1, blog2, blog3 ])
|
||||
}
|
||||
|
||||
@@ -597,11 +527,11 @@ pub(crate) mod tests {
|
||||
"SomeName".to_owned(),
|
||||
"Some name".to_owned(),
|
||||
"This is some blog".to_owned(),
|
||||
Instance::local_id(conn),
|
||||
),
|
||||
);
|
||||
Instance::get_local(conn).unwrap().id
|
||||
).unwrap(),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(blog.get_instance(conn).id, Instance::local_id(conn));
|
||||
assert_eq!(blog.get_instance(conn).unwrap().id, Instance::get_local(conn).unwrap().id);
|
||||
// TODO add tests for remote instance
|
||||
|
||||
Ok(())
|
||||
@@ -620,20 +550,20 @@ pub(crate) mod tests {
|
||||
"SomeName".to_owned(),
|
||||
"Some name".to_owned(),
|
||||
"This is some blog".to_owned(),
|
||||
Instance::local_id(conn),
|
||||
),
|
||||
);
|
||||
b1.update_boxes(conn);
|
||||
Instance::get_local(conn).unwrap().id,
|
||||
).unwrap(),
|
||||
).unwrap();
|
||||
b1.update_boxes(conn).unwrap();
|
||||
let b2 = Blog::insert(
|
||||
conn,
|
||||
NewBlog::new_local(
|
||||
"Blog".to_owned(),
|
||||
"Blog".to_owned(),
|
||||
"I've named my blog Blog".to_owned(),
|
||||
Instance::local_id(conn),
|
||||
),
|
||||
);
|
||||
b2.update_boxes(conn);
|
||||
Instance::get_local(conn).unwrap().id
|
||||
).unwrap(),
|
||||
).unwrap();
|
||||
b2.update_boxes(conn).unwrap();
|
||||
let blog = vec![ b1, b2 ];
|
||||
|
||||
BlogAuthor::insert(
|
||||
@@ -643,7 +573,7 @@ pub(crate) mod tests {
|
||||
author_id: user[0].id,
|
||||
is_owner: true,
|
||||
},
|
||||
);
|
||||
).unwrap();
|
||||
|
||||
BlogAuthor::insert(
|
||||
conn,
|
||||
@@ -652,7 +582,7 @@ pub(crate) mod tests {
|
||||
author_id: user[1].id,
|
||||
is_owner: false,
|
||||
},
|
||||
);
|
||||
).unwrap();
|
||||
|
||||
BlogAuthor::insert(
|
||||
conn,
|
||||
@@ -661,50 +591,50 @@ pub(crate) mod tests {
|
||||
author_id: user[0].id,
|
||||
is_owner: true,
|
||||
},
|
||||
);
|
||||
).unwrap();
|
||||
|
||||
assert!(
|
||||
blog[0]
|
||||
.list_authors(conn)
|
||||
.list_authors(conn).unwrap()
|
||||
.iter()
|
||||
.any(|a| a.id == user[0].id)
|
||||
);
|
||||
assert!(
|
||||
blog[0]
|
||||
.list_authors(conn)
|
||||
.list_authors(conn).unwrap()
|
||||
.iter()
|
||||
.any(|a| a.id == user[1].id)
|
||||
);
|
||||
assert!(
|
||||
blog[1]
|
||||
.list_authors(conn)
|
||||
.list_authors(conn).unwrap()
|
||||
.iter()
|
||||
.any(|a| a.id == user[0].id)
|
||||
);
|
||||
assert!(
|
||||
!blog[1]
|
||||
.list_authors(conn)
|
||||
.list_authors(conn).unwrap()
|
||||
.iter()
|
||||
.any(|a| a.id == user[1].id)
|
||||
);
|
||||
|
||||
assert!(
|
||||
Blog::find_for_author(conn, &user[0])
|
||||
Blog::find_for_author(conn, &user[0]).unwrap()
|
||||
.iter()
|
||||
.any(|b| b.id == blog[0].id)
|
||||
);
|
||||
assert!(
|
||||
Blog::find_for_author(conn, &user[1])
|
||||
Blog::find_for_author(conn, &user[1]).unwrap()
|
||||
.iter()
|
||||
.any(|b| b.id == blog[0].id)
|
||||
);
|
||||
assert!(
|
||||
Blog::find_for_author(conn, &user[0])
|
||||
Blog::find_for_author(conn, &user[0]).unwrap()
|
||||
.iter()
|
||||
.any(|b| b.id == blog[1].id)
|
||||
);
|
||||
assert!(
|
||||
!Blog::find_for_author(conn, &user[1])
|
||||
!Blog::find_for_author(conn, &user[1]).unwrap()
|
||||
.iter()
|
||||
.any(|b| b.id == blog[1].id)
|
||||
);
|
||||
@@ -725,9 +655,9 @@ pub(crate) mod tests {
|
||||
"SomeName".to_owned(),
|
||||
"Some name".to_owned(),
|
||||
"This is some blog".to_owned(),
|
||||
Instance::local_id(conn),
|
||||
),
|
||||
);
|
||||
Instance::get_local(conn).unwrap().id,
|
||||
).unwrap(),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
Blog::find_local(conn, "SomeName").unwrap().id,
|
||||
@@ -750,9 +680,9 @@ pub(crate) mod tests {
|
||||
"SomeName".to_owned(),
|
||||
"Some name".to_owned(),
|
||||
"This is some blog".to_owned(),
|
||||
Instance::local_id(conn),
|
||||
),
|
||||
);
|
||||
Instance::get_local(conn).unwrap().id,
|
||||
).unwrap(),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(blog.get_fqn(conn), "SomeName");
|
||||
|
||||
@@ -766,8 +696,8 @@ pub(crate) mod tests {
|
||||
conn.test_transaction::<_, (), _>(|| {
|
||||
let (_, blogs) = fill_database(conn);
|
||||
|
||||
blogs[0].delete(conn, &get_searcher());
|
||||
assert!(Blog::get(conn, blogs[0].id).is_none());
|
||||
blogs[0].delete(conn, &get_searcher()).unwrap();
|
||||
assert!(Blog::get(conn, blogs[0].id).is_err());
|
||||
|
||||
Ok(())
|
||||
});
|
||||
@@ -786,20 +716,20 @@ pub(crate) mod tests {
|
||||
"SomeName".to_owned(),
|
||||
"Some name".to_owned(),
|
||||
"This is some blog".to_owned(),
|
||||
Instance::local_id(conn),
|
||||
),
|
||||
);
|
||||
b1.update_boxes(conn);
|
||||
Instance::get_local(conn).unwrap().id,
|
||||
).unwrap(),
|
||||
).unwrap();
|
||||
b1.update_boxes(conn).unwrap();
|
||||
let b2 = Blog::insert(
|
||||
conn,
|
||||
NewBlog::new_local(
|
||||
"Blog".to_owned(),
|
||||
"Blog".to_owned(),
|
||||
"I've named my blog Blog".to_owned(),
|
||||
Instance::local_id(conn),
|
||||
),
|
||||
);
|
||||
b2.update_boxes(conn);
|
||||
Instance::get_local(conn).unwrap().id,
|
||||
).unwrap(),
|
||||
).unwrap();
|
||||
b2.update_boxes(conn).unwrap();
|
||||
let blog = vec![ b1, b2 ];
|
||||
|
||||
BlogAuthor::insert(
|
||||
@@ -809,7 +739,7 @@ pub(crate) mod tests {
|
||||
author_id: user[0].id,
|
||||
is_owner: true,
|
||||
},
|
||||
);
|
||||
).unwrap();
|
||||
|
||||
BlogAuthor::insert(
|
||||
conn,
|
||||
@@ -818,7 +748,7 @@ pub(crate) mod tests {
|
||||
author_id: user[1].id,
|
||||
is_owner: false,
|
||||
},
|
||||
);
|
||||
).unwrap();
|
||||
|
||||
BlogAuthor::insert(
|
||||
conn,
|
||||
@@ -827,13 +757,13 @@ pub(crate) mod tests {
|
||||
author_id: user[0].id,
|
||||
is_owner: true,
|
||||
},
|
||||
);
|
||||
).unwrap();
|
||||
|
||||
user[0].delete(conn, &searcher);
|
||||
assert!(Blog::get(conn, blog[0].id).is_some());
|
||||
assert!(Blog::get(conn, blog[1].id).is_none());
|
||||
user[1].delete(conn, &searcher);
|
||||
assert!(Blog::get(conn, blog[0].id).is_none());
|
||||
user[0].delete(conn, &searcher).unwrap();
|
||||
assert!(Blog::get(conn, blog[0].id).is_ok());
|
||||
assert!(Blog::get(conn, blog[1].id).is_err());
|
||||
user[1].delete(conn, &searcher).unwrap();
|
||||
assert!(Blog::get(conn, blog[0].id).is_err());
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||
use comments::Comment;
|
||||
use schema::comment_seers;
|
||||
use users::User;
|
||||
use Connection;
|
||||
use {Connection, Error, Result};
|
||||
|
||||
#[derive(Queryable, Serialize, Clone)]
|
||||
pub struct CommentSeers {
|
||||
@@ -22,11 +22,11 @@ pub struct NewCommentSeers {
|
||||
impl CommentSeers {
|
||||
insert!(comment_seers, NewCommentSeers);
|
||||
|
||||
pub fn can_see(conn: &Connection, c: &Comment, u: &User) -> bool {
|
||||
!comment_seers::table.filter(comment_seers::comment_id.eq(c.id))
|
||||
pub fn can_see(conn: &Connection, c: &Comment, u: &User) -> Result<bool> {
|
||||
comment_seers::table.filter(comment_seers::comment_id.eq(c.id))
|
||||
.filter(comment_seers::user_id.eq(u.id))
|
||||
.load::<CommentSeers>(conn)
|
||||
.expect("Comment::get_responses: loading error")
|
||||
.is_empty()
|
||||
.map_err(Error::from)
|
||||
.map(|r| !r.is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
+102
-134
@@ -18,7 +18,7 @@ use posts::Post;
|
||||
use safe_string::SafeString;
|
||||
use schema::comments;
|
||||
use users::User;
|
||||
use Connection;
|
||||
use {Connection, Error, Result};
|
||||
|
||||
#[derive(Queryable, Identifiable, Serialize, Clone)]
|
||||
pub struct Comment {
|
||||
@@ -53,150 +53,125 @@ impl Comment {
|
||||
list_by!(comments, list_by_post, post_id as i32);
|
||||
find_by!(comments, find_by_ap_url, ap_url as &str);
|
||||
|
||||
pub fn get_author(&self, conn: &Connection) -> User {
|
||||
User::get(conn, self.author_id).expect("Comment::get_author: author error")
|
||||
pub fn get_author(&self, conn: &Connection) -> Result<User> {
|
||||
User::get(conn, self.author_id)
|
||||
}
|
||||
|
||||
pub fn get_post(&self, conn: &Connection) -> Post {
|
||||
Post::get(conn, self.post_id).expect("Comment::get_post: post error")
|
||||
pub fn get_post(&self, conn: &Connection) -> Result<Post> {
|
||||
Post::get(conn, self.post_id)
|
||||
}
|
||||
|
||||
pub fn count_local(conn: &Connection) -> i64 {
|
||||
pub fn count_local(conn: &Connection) -> Result<i64> {
|
||||
use schema::users;
|
||||
let local_authors = users::table
|
||||
.filter(users::instance_id.eq(Instance::local_id(conn)))
|
||||
.filter(users::instance_id.eq(Instance::get_local(conn)?.id))
|
||||
.select(users::id);
|
||||
comments::table
|
||||
.filter(comments::author_id.eq_any(local_authors))
|
||||
.count()
|
||||
.get_result(conn)
|
||||
.expect("Comment::count_local: loading error")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn get_responses(&self, conn: &Connection) -> Vec<Comment> {
|
||||
pub fn get_responses(&self, conn: &Connection) -> Result<Vec<Comment>> {
|
||||
comments::table.filter(comments::in_response_to_id.eq(self.id))
|
||||
.load::<Comment>(conn)
|
||||
.expect("Comment::get_responses: loading error")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn update_ap_url(&self, conn: &Connection) -> Comment {
|
||||
pub fn update_ap_url(&self, conn: &Connection) -> Result<Comment> {
|
||||
if self.ap_url.is_none() {
|
||||
diesel::update(self)
|
||||
.set(comments::ap_url.eq(self.compute_id(conn)))
|
||||
.execute(conn)
|
||||
.expect("Comment::update_ap_url: update error");
|
||||
Comment::get(conn, self.id).expect("Comment::update_ap_url: get error")
|
||||
.set(comments::ap_url.eq(self.compute_id(conn)?))
|
||||
.execute(conn)?;
|
||||
Comment::get(conn, self.id)
|
||||
} else {
|
||||
self.clone()
|
||||
Ok(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute_id(&self, conn: &Connection) -> String {
|
||||
format!("{}comment/{}", self.get_post(conn).ap_url, self.id)
|
||||
pub fn compute_id(&self, conn: &Connection) -> Result<String> {
|
||||
Ok(format!("{}comment/{}", self.get_post(conn)?.ap_url, self.id))
|
||||
}
|
||||
|
||||
pub fn can_see(&self, conn: &Connection, user: Option<&User>) -> bool {
|
||||
self.public_visibility ||
|
||||
user.as_ref().map(|u| CommentSeers::can_see(conn, self, u)).unwrap_or(false)
|
||||
user.as_ref().map(|u| CommentSeers::can_see(conn, self, u).unwrap_or(false))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn to_activity(&self, conn: &Connection) -> Note {
|
||||
pub fn to_activity(&self, conn: &Connection) -> Result<Note> {
|
||||
let (html, mentions, _hashtags) = utils::md_to_html(self.content.get().as_ref(),
|
||||
&Instance::get_local(conn)
|
||||
.expect("Comment::to_activity: instance error")
|
||||
.public_domain);
|
||||
&Instance::get_local(conn)?.public_domain);
|
||||
|
||||
let author = User::get(conn, self.author_id).expect("Comment::to_activity: author error");
|
||||
let author = User::get(conn, self.author_id)?;
|
||||
let mut note = Note::default();
|
||||
let to = vec![Id::new(PUBLIC_VISIBILTY.to_string())];
|
||||
|
||||
note.object_props
|
||||
.set_id_string(self.ap_url.clone().unwrap_or_default())
|
||||
.expect("Comment::to_activity: id error");
|
||||
.set_id_string(self.ap_url.clone().unwrap_or_default())?;
|
||||
note.object_props
|
||||
.set_summary_string(self.spoiler_text.clone())
|
||||
.expect("Comment::to_activity: summary error");
|
||||
.set_summary_string(self.spoiler_text.clone())?;
|
||||
note.object_props
|
||||
.set_content_string(html)
|
||||
.expect("Comment::to_activity: content error");
|
||||
.set_content_string(html)?;
|
||||
note.object_props
|
||||
.set_in_reply_to_link(Id::new(self.in_response_to_id.map_or_else(
|
||||
|| {
|
||||
Post::get(conn, self.post_id)
|
||||
.expect("Comment::to_activity: post error")
|
||||
.ap_url
|
||||
},
|
||||
|id| {
|
||||
let comm =
|
||||
Comment::get(conn, id).expect("Comment::to_activity: comment error");
|
||||
comm.ap_url.clone().unwrap_or_else(|| comm.compute_id(conn))
|
||||
},
|
||||
)))
|
||||
.expect("Comment::to_activity: in_reply_to error");
|
||||
|| Ok(Post::get(conn, self.post_id)?.ap_url),
|
||||
|id| Ok(Comment::get(conn, id)?.compute_id(conn)?) as Result<String>,
|
||||
)?))?;
|
||||
note.object_props
|
||||
.set_published_string(chrono::Utc::now().to_rfc3339())
|
||||
.expect("Comment::to_activity: published error");
|
||||
.set_published_string(chrono::Utc::now().to_rfc3339())?;
|
||||
note.object_props
|
||||
.set_attributed_to_link(author.clone().into_id())
|
||||
.expect("Comment::to_activity: attributed_to error");
|
||||
.set_attributed_to_link(author.clone().into_id())?;
|
||||
note.object_props
|
||||
.set_to_link_vec(to.clone())
|
||||
.expect("Comment::to_activity: to error");
|
||||
.set_to_link_vec(to.clone())?;
|
||||
note.object_props
|
||||
.set_tag_link_vec(
|
||||
mentions
|
||||
.into_iter()
|
||||
.map(|m| Mention::build_activity(conn, &m))
|
||||
.filter_map(|m| Mention::build_activity(conn, &m).ok())
|
||||
.collect::<Vec<link::Mention>>(),
|
||||
)
|
||||
.expect("Comment::to_activity: tag error");
|
||||
note
|
||||
)?;
|
||||
Ok(note)
|
||||
}
|
||||
|
||||
pub fn create_activity(&self, conn: &Connection) -> Create {
|
||||
pub fn create_activity(&self, conn: &Connection) -> Result<Create> {
|
||||
let author =
|
||||
User::get(conn, self.author_id).expect("Comment::create_activity: author error");
|
||||
User::get(conn, self.author_id)?;
|
||||
|
||||
let note = self.to_activity(conn);
|
||||
let note = self.to_activity(conn)?;
|
||||
let mut act = Create::default();
|
||||
act.create_props
|
||||
.set_actor_link(author.into_id())
|
||||
.expect("Comment::create_activity: actor error");
|
||||
.set_actor_link(author.into_id())?;
|
||||
act.create_props
|
||||
.set_object_object(note.clone())
|
||||
.expect("Comment::create_activity: object error");
|
||||
.set_object_object(note.clone())?;
|
||||
act.object_props
|
||||
.set_id_string(format!(
|
||||
"{}/activity",
|
||||
self.ap_url
|
||||
.clone()
|
||||
.expect("Comment::create_activity: ap_url error")
|
||||
))
|
||||
.expect("Comment::create_activity: id error");
|
||||
.clone()?,
|
||||
))?;
|
||||
act.object_props
|
||||
.set_to_link_vec(
|
||||
note.object_props
|
||||
.to_link_vec::<Id>()
|
||||
.expect("Comment::create_activity: id error"),
|
||||
)
|
||||
.expect("Comment::create_activity: to error");
|
||||
.to_link_vec::<Id>()?,
|
||||
)?;
|
||||
act.object_props
|
||||
.set_cc_link_vec::<Id>(vec![])
|
||||
.expect("Comment::create_activity: cc error");
|
||||
act
|
||||
.set_cc_link_vec::<Id>(vec![])?;
|
||||
Ok(act)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromActivity<Note, Connection> for Comment {
|
||||
fn from_activity(conn: &Connection, note: Note, actor: Id) -> Comment {
|
||||
type Error = Error;
|
||||
|
||||
fn from_activity(conn: &Connection, note: Note, actor: Id) -> Result<Comment> {
|
||||
let comm = {
|
||||
let previous_url = note
|
||||
.object_props
|
||||
.in_reply_to
|
||||
.as_ref()
|
||||
.expect("Comment::from_activity: not an answer error")
|
||||
.as_str()
|
||||
.expect("Comment::from_activity: in_reply_to parsing error");
|
||||
.as_ref()?
|
||||
.as_str()?;
|
||||
let previous_comment = Comment::find_by_ap_url(conn, previous_url);
|
||||
|
||||
let is_public = |v: &Option<serde_json::Value>| match v.as_ref().unwrap_or(&serde_json::Value::Null) {
|
||||
@@ -216,42 +191,35 @@ impl FromActivity<Note, Connection> for Comment {
|
||||
content: SafeString::new(
|
||||
¬e
|
||||
.object_props
|
||||
.content_string()
|
||||
.expect("Comment::from_activity: content deserialization error"),
|
||||
.content_string()?
|
||||
),
|
||||
spoiler_text: note
|
||||
.object_props
|
||||
.summary_string()
|
||||
.unwrap_or_default(),
|
||||
ap_url: note.object_props.id_string().ok(),
|
||||
in_response_to_id: previous_comment.clone().map(|c| c.id),
|
||||
post_id: previous_comment.map(|c| c.post_id).unwrap_or_else(|| {
|
||||
Post::find_by_ap_url(conn, previous_url)
|
||||
.expect("Comment::from_activity: post error")
|
||||
.id
|
||||
}),
|
||||
author_id: User::from_url(conn, actor.as_ref())
|
||||
.expect("Comment::from_activity: author error")
|
||||
.id,
|
||||
in_response_to_id: previous_comment.iter().map(|c| c.id).next(),
|
||||
post_id: previous_comment.map(|c| c.post_id)
|
||||
.or_else(|_| Ok(Post::find_by_ap_url(conn, previous_url)?.id) as Result<i32>)?,
|
||||
author_id: User::from_url(conn, actor.as_ref())?.id,
|
||||
sensitive: false, // "sensitive" is not a standard property, we need to think about how to support it with the activitypub crate
|
||||
public_visibility
|
||||
},
|
||||
);
|
||||
)?;
|
||||
|
||||
// save mentions
|
||||
if let Some(serde_json::Value::Array(tags)) = note.object_props.tag.clone() {
|
||||
for tag in tags {
|
||||
serde_json::from_value::<link::Mention>(tag)
|
||||
.map(|m| {
|
||||
let author = &Post::get(conn, comm.post_id)
|
||||
.expect("Comment::from_activity: error")
|
||||
.get_authors(conn)[0];
|
||||
.map_err(Error::from)
|
||||
.and_then(|m| {
|
||||
let author = &Post::get(conn, comm.post_id)?
|
||||
.get_authors(conn)?[0];
|
||||
let not_author = m
|
||||
.link_props
|
||||
.href_string()
|
||||
.expect("Comment::from_activity: no href error")
|
||||
.href_string()?
|
||||
!= author.ap_url.clone();
|
||||
Mention::from_activity(conn, &m, comm.id, false, not_author)
|
||||
Ok(Mention::from_activity(conn, &m, comm.id, false, not_author)?)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -279,13 +247,13 @@ impl FromActivity<Note, Connection> for Comment {
|
||||
let receivers_ap_url = to.chain(cc).chain(bto).chain(bcc)
|
||||
.collect::<HashSet<_>>()//remove duplicates (don't do a query more than once)
|
||||
.into_iter()
|
||||
.map(|v| if let Some(user) = User::from_url(conn,&v) {
|
||||
.map(|v| if let Ok(user) = User::from_url(conn,&v) {
|
||||
vec![user]
|
||||
} else {
|
||||
vec![]// TODO try to fetch collection
|
||||
})
|
||||
.flatten()
|
||||
.filter(|u| u.get_instance(conn).local)
|
||||
.filter(|u| u.get_instance(conn).map(|i| i.local).unwrap_or(false))
|
||||
.collect::<HashSet<User>>();//remove duplicates (prevent db error)
|
||||
|
||||
for user in &receivers_ap_url {
|
||||
@@ -295,18 +263,20 @@ impl FromActivity<Note, Connection> for Comment {
|
||||
comment_id: comm.id,
|
||||
user_id: user.id
|
||||
}
|
||||
);
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
comm.notify(conn);
|
||||
comm
|
||||
comm.notify(conn)?;
|
||||
Ok(comm)
|
||||
}
|
||||
}
|
||||
|
||||
impl Notify<Connection> for Comment {
|
||||
fn notify(&self, conn: &Connection) {
|
||||
for author in self.get_post(conn).get_authors(conn) {
|
||||
type Error = Error;
|
||||
|
||||
fn notify(&self, conn: &Connection) -> Result<()> {
|
||||
for author in self.get_post(conn)?.get_authors(conn)? {
|
||||
Notification::insert(
|
||||
conn,
|
||||
NewNotification {
|
||||
@@ -314,8 +284,9 @@ impl Notify<Connection> for Comment {
|
||||
object_id: self.id,
|
||||
user_id: author.id,
|
||||
},
|
||||
);
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,67 +296,64 @@ pub struct CommentTree {
|
||||
}
|
||||
|
||||
impl CommentTree {
|
||||
pub fn from_post(conn: &Connection, p: &Post, user: Option<&User>) -> Vec<Self> {
|
||||
Comment::list_by_post(conn, p.id).into_iter()
|
||||
pub fn from_post(conn: &Connection, p: &Post, user: Option<&User>) -> Result<Vec<Self>> {
|
||||
Ok(Comment::list_by_post(conn, p.id)?.into_iter()
|
||||
.filter(|c| c.in_response_to_id.is_none())
|
||||
.filter(|c| c.can_see(conn, user))
|
||||
.map(|c| Self::from_comment(conn, c, user))
|
||||
.collect()
|
||||
.filter_map(|c| Self::from_comment(conn, c, user).ok())
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn from_comment(conn: &Connection, comment: Comment, user: Option<&User>) -> Self {
|
||||
let responses = comment.get_responses(conn).into_iter()
|
||||
pub fn from_comment(conn: &Connection, comment: Comment, user: Option<&User>) -> Result<Self> {
|
||||
let responses = comment.get_responses(conn)?.into_iter()
|
||||
.filter(|c| c.can_see(conn, user))
|
||||
.map(|c| Self::from_comment(conn, c, user))
|
||||
.filter_map(|c| Self::from_comment(conn, c, user).ok())
|
||||
.collect();
|
||||
CommentTree {
|
||||
Ok(CommentTree {
|
||||
comment,
|
||||
responses,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deletable<Connection, Delete> for Comment {
|
||||
fn delete(&self, conn: &Connection) -> Delete {
|
||||
type Error = Error;
|
||||
|
||||
fn delete(&self, conn: &Connection) -> Result<Delete> {
|
||||
let mut act = Delete::default();
|
||||
act.delete_props
|
||||
.set_actor_link(self.get_author(conn).into_id())
|
||||
.expect("Comment::delete: actor error");
|
||||
.set_actor_link(self.get_author(conn)?.into_id())?;
|
||||
|
||||
let mut tombstone = Tombstone::default();
|
||||
tombstone
|
||||
.object_props
|
||||
.set_id_string(self.ap_url.clone().expect("Comment::delete: no ap_url"))
|
||||
.expect("Comment::delete: object.id error");
|
||||
.set_id_string(self.ap_url.clone()?)?;
|
||||
act.delete_props
|
||||
.set_object_object(tombstone)
|
||||
.expect("Comment::delete: object error");
|
||||
.set_object_object(tombstone)?;
|
||||
|
||||
act.object_props
|
||||
.set_id_string(format!("{}#delete", self.ap_url.clone().unwrap()))
|
||||
.expect("Comment::delete: id error");
|
||||
.set_id_string(format!("{}#delete", self.ap_url.clone().unwrap()))?;
|
||||
act.object_props
|
||||
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILTY)])
|
||||
.expect("Comment::delete: to error");
|
||||
.set_to_link_vec(vec![Id::new(PUBLIC_VISIBILTY)])?;
|
||||
|
||||
for m in Mention::list_for_comment(&conn, self.id) {
|
||||
m.delete(conn);
|
||||
for m in Mention::list_for_comment(&conn, self.id)? {
|
||||
m.delete(conn)?;
|
||||
}
|
||||
diesel::update(comments::table).filter(comments::in_response_to_id.eq(self.id))
|
||||
.set(comments::in_response_to_id.eq(self.in_response_to_id))
|
||||
.execute(conn)
|
||||
.expect("Comment::delete: DB error could not update other comments");
|
||||
.execute(conn)?;
|
||||
diesel::delete(self)
|
||||
.execute(conn)
|
||||
.expect("Comment::delete: DB error");
|
||||
act
|
||||
.execute(conn)?;
|
||||
Ok(act)
|
||||
}
|
||||
|
||||
fn delete_id(id: &str, actor_id: &str, conn: &Connection) {
|
||||
let actor = User::find_by_ap_url(conn, actor_id);
|
||||
let comment = Comment::find_by_ap_url(conn, id);
|
||||
if let Some(comment) = comment.filter(|c| c.author_id == actor.unwrap().id) {
|
||||
comment.delete(conn);
|
||||
fn delete_id(id: &str, actor_id: &str, conn: &Connection) -> Result<Delete> {
|
||||
let actor = User::find_by_ap_url(conn, actor_id)?;
|
||||
let comment = Comment::find_by_ap_url(conn, id)?;
|
||||
if comment.author_id == actor.id {
|
||||
comment.delete(conn)
|
||||
} else {
|
||||
Err(Error::Unauthorized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use diesel::{dsl::sql_query, r2d2::{ConnectionManager, CustomizeConnection, Error as ConnError, Pool, PooledConnection}, ConnectionError, RunQueryDsl};
|
||||
use diesel::{r2d2::{ConnectionManager, CustomizeConnection, Error as ConnError, Pool, PooledConnection}};
|
||||
#[cfg(feature = "sqlite")]
|
||||
use diesel::{dsl::sql_query, ConnectionError, RunQueryDsl};
|
||||
use rocket::{
|
||||
http::Status,
|
||||
request::{self, FromRequest},
|
||||
|
||||
+57
-75
@@ -15,7 +15,7 @@ use plume_common::activity_pub::{
|
||||
};
|
||||
use schema::follows;
|
||||
use users::User;
|
||||
use {ap_url, Connection, BASE_URL};
|
||||
use {ap_url, Connection, BASE_URL, Error, Result};
|
||||
|
||||
#[derive(Clone, Queryable, Identifiable, Associations)]
|
||||
#[belongs_to(User, foreign_key = "following_id")]
|
||||
@@ -39,37 +39,30 @@ impl Follow {
|
||||
get!(follows);
|
||||
find_by!(follows, find_by_ap_url, ap_url as &str);
|
||||
|
||||
pub fn find(conn: &Connection, from: i32, to: i32) -> Option<Follow> {
|
||||
pub fn find(conn: &Connection, from: i32, to: i32) -> Result<Follow> {
|
||||
follows::table
|
||||
.filter(follows::follower_id.eq(from))
|
||||
.filter(follows::following_id.eq(to))
|
||||
.get_result(conn)
|
||||
.ok()
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn to_activity(&self, conn: &Connection) -> FollowAct {
|
||||
let user = User::get(conn, self.follower_id)
|
||||
.expect("Follow::to_activity: actor not found error");
|
||||
let target = User::get(conn, self.following_id)
|
||||
.expect("Follow::to_activity: target not found error");
|
||||
pub fn to_activity(&self, conn: &Connection) -> Result<FollowAct> {
|
||||
let user = User::get(conn, self.follower_id)?;
|
||||
let target = User::get(conn, self.following_id)?;
|
||||
|
||||
let mut act = FollowAct::default();
|
||||
act.follow_props
|
||||
.set_actor_link::<Id>(user.clone().into_id())
|
||||
.expect("Follow::to_activity: actor error");
|
||||
.set_actor_link::<Id>(user.clone().into_id())?;
|
||||
act.follow_props
|
||||
.set_object_link::<Id>(target.clone().into_id())
|
||||
.expect("Follow::to_activity: object error");
|
||||
.set_object_link::<Id>(target.clone().into_id())?;
|
||||
act.object_props
|
||||
.set_id_string(self.ap_url.clone())
|
||||
.expect("Follow::to_activity: id error");
|
||||
.set_id_string(self.ap_url.clone())?;
|
||||
act.object_props
|
||||
.set_to_link(target.into_id())
|
||||
.expect("Follow::to_activity: target error");
|
||||
.set_to_link(target.into_id())?;
|
||||
act.object_props
|
||||
.set_cc_link_vec::<Id>(vec![])
|
||||
.expect("Follow::to_activity: cc error");
|
||||
act
|
||||
.set_cc_link_vec::<Id>(vec![])?;
|
||||
Ok(act)
|
||||
}
|
||||
|
||||
/// from -> The one sending the follow request
|
||||
@@ -81,78 +74,69 @@ impl Follow {
|
||||
follow: FollowAct,
|
||||
from_id: i32,
|
||||
target_id: i32,
|
||||
) -> Follow {
|
||||
) -> Result<Follow> {
|
||||
let res = Follow::insert(
|
||||
conn,
|
||||
NewFollow {
|
||||
follower_id: from_id,
|
||||
following_id: target_id,
|
||||
ap_url: follow.object_props.id_string().expect("Follow::accept_follow: get id error"),
|
||||
ap_url: follow.object_props.id_string()?,
|
||||
},
|
||||
);
|
||||
)?;
|
||||
|
||||
let mut accept = Accept::default();
|
||||
let accept_id = ap_url(&format!("{}/follow/{}/accept", BASE_URL.as_str(), &res.id));
|
||||
accept
|
||||
.object_props
|
||||
.set_id_string(accept_id)
|
||||
.expect("Follow::accept_follow: set id error");
|
||||
.set_id_string(accept_id)?;
|
||||
accept
|
||||
.object_props
|
||||
.set_to_link(from.clone().into_id())
|
||||
.expect("Follow::accept_follow: to error");
|
||||
.set_to_link(from.clone().into_id())?;
|
||||
accept
|
||||
.object_props
|
||||
.set_cc_link_vec::<Id>(vec![])
|
||||
.expect("Follow::accept_follow: cc error");
|
||||
.set_cc_link_vec::<Id>(vec![])?;
|
||||
accept
|
||||
.accept_props
|
||||
.set_actor_link::<Id>(target.clone().into_id())
|
||||
.expect("Follow::accept_follow: actor error");
|
||||
.set_actor_link::<Id>(target.clone().into_id())?;
|
||||
accept
|
||||
.accept_props
|
||||
.set_object_object(follow)
|
||||
.expect("Follow::accept_follow: object error");
|
||||
.set_object_object(follow)?;
|
||||
broadcast(&*target, accept, vec![from.clone()]);
|
||||
res
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromActivity<FollowAct, Connection> for Follow {
|
||||
fn from_activity(conn: &Connection, follow: FollowAct, _actor: Id) -> Follow {
|
||||
type Error = Error;
|
||||
|
||||
fn from_activity(conn: &Connection, follow: FollowAct, _actor: Id) -> Result<Follow> {
|
||||
let from_id = follow
|
||||
.follow_props
|
||||
.actor_link::<Id>()
|
||||
.map(|l| l.into())
|
||||
.unwrap_or_else(|_| {
|
||||
follow
|
||||
.follow_props
|
||||
.actor_object::<Person>()
|
||||
.expect("Follow::from_activity: actor not found error")
|
||||
.object_props
|
||||
.id_string()
|
||||
.expect("Follow::from_activity: actor not found error")
|
||||
});
|
||||
.or_else(|_| Ok(follow
|
||||
.follow_props
|
||||
.actor_object::<Person>()?
|
||||
.object_props
|
||||
.id_string()?) as Result<String>)?;
|
||||
let from =
|
||||
User::from_url(conn, &from_id).expect("Follow::from_activity: actor not found error");
|
||||
User::from_url(conn, &from_id)?;
|
||||
match User::from_url(
|
||||
conn,
|
||||
follow
|
||||
.follow_props
|
||||
.object
|
||||
.as_str()
|
||||
.expect("Follow::from_activity: target url parsing error"),
|
||||
.as_str()?,
|
||||
) {
|
||||
Some(user) => Follow::accept_follow(conn, &from, &user, follow, from.id, user.id),
|
||||
None => {
|
||||
Ok(user) => Follow::accept_follow(conn, &from, &user, follow, from.id, user.id),
|
||||
Err(_) => {
|
||||
let blog = Blog::from_url(
|
||||
conn,
|
||||
follow
|
||||
.follow_props
|
||||
.object
|
||||
.as_str()
|
||||
.expect("Follow::from_activity: target url parsing error"),
|
||||
).expect("Follow::from_activity: target not found error");
|
||||
.as_str()?,
|
||||
)?;
|
||||
Follow::accept_follow(conn, &from, &blog, follow, from.id, blog.id)
|
||||
}
|
||||
}
|
||||
@@ -160,7 +144,9 @@ impl FromActivity<FollowAct, Connection> for Follow {
|
||||
}
|
||||
|
||||
impl Notify<Connection> for Follow {
|
||||
fn notify(&self, conn: &Connection) {
|
||||
type Error = Error;
|
||||
|
||||
fn notify(&self, conn: &Connection) -> Result<()> {
|
||||
Notification::insert(
|
||||
conn,
|
||||
NewNotification {
|
||||
@@ -168,47 +154,43 @@ impl Notify<Connection> for Follow {
|
||||
object_id: self.id,
|
||||
user_id: self.following_id,
|
||||
},
|
||||
);
|
||||
).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deletable<Connection, Undo> for Follow {
|
||||
fn delete(&self, conn: &Connection) -> Undo {
|
||||
type Error = Error;
|
||||
|
||||
fn delete(&self, conn: &Connection) -> Result<Undo> {
|
||||
diesel::delete(self)
|
||||
.execute(conn)
|
||||
.expect("Follow::delete: follow deletion error");
|
||||
.execute(conn)?;
|
||||
|
||||
// delete associated notification if any
|
||||
if let Some(notif) = Notification::find(conn, notification_kind::FOLLOW, self.id) {
|
||||
if let Ok(notif) = Notification::find(conn, notification_kind::FOLLOW, self.id) {
|
||||
diesel::delete(¬if)
|
||||
.execute(conn)
|
||||
.expect("Follow::delete: notification deletion error");
|
||||
.execute(conn)?;
|
||||
}
|
||||
|
||||
let mut undo = Undo::default();
|
||||
undo.undo_props
|
||||
.set_actor_link(
|
||||
User::get(conn, self.follower_id)
|
||||
.expect("Follow::delete: actor error")
|
||||
User::get(conn, self.follower_id)?
|
||||
.into_id(),
|
||||
)
|
||||
.expect("Follow::delete: actor error");
|
||||
)?;
|
||||
undo.object_props
|
||||
.set_id_string(format!("{}/undo", self.ap_url))
|
||||
.expect("Follow::delete: id error");
|
||||
.set_id_string(format!("{}/undo", self.ap_url))?;
|
||||
undo.undo_props
|
||||
.set_object_link::<Id>(self.clone().into_id())
|
||||
.expect("Follow::delete: object error");
|
||||
undo
|
||||
.set_object_link::<Id>(self.clone().into_id())?;
|
||||
Ok(undo)
|
||||
}
|
||||
|
||||
fn delete_id(id: &str, actor_id: &str, conn: &Connection) {
|
||||
if let Some(follow) = Follow::find_by_ap_url(conn, id) {
|
||||
if let Some(user) = User::find_by_ap_url(conn, actor_id) {
|
||||
if user.id == follow.follower_id {
|
||||
follow.delete(conn);
|
||||
}
|
||||
}
|
||||
fn delete_id(id: &str, actor_id: &str, conn: &Connection) -> Result<Undo> {
|
||||
let follow = Follow::find_by_ap_url(conn, id)?;
|
||||
let user = User::find_by_ap_url(conn, actor_id)?;
|
||||
if user.id == follow.follower_id {
|
||||
follow.delete(conn)
|
||||
} else {
|
||||
Err(Error::Unauthorized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use plume_common::utils::md_to_html;
|
||||
use safe_string::SafeString;
|
||||
use schema::{instances, users};
|
||||
use users::User;
|
||||
use Connection;
|
||||
use {Connection, Error, Result};
|
||||
|
||||
#[derive(Clone, Identifiable, Queryable, Serialize)]
|
||||
pub struct Instance {
|
||||
@@ -40,80 +40,73 @@ pub struct NewInstance {
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
pub fn get_local(conn: &Connection) -> Option<Instance> {
|
||||
pub fn get_local(conn: &Connection) -> Result<Instance> {
|
||||
instances::table
|
||||
.filter(instances::local.eq(true))
|
||||
.limit(1)
|
||||
.load::<Instance>(conn)
|
||||
.expect("Instance::get_local: loading error")
|
||||
.load::<Instance>(conn)?
|
||||
.into_iter()
|
||||
.nth(0)
|
||||
.nth(0).ok_or(Error::NotFound)
|
||||
}
|
||||
|
||||
pub fn get_remotes(conn: &Connection) -> Vec<Instance> {
|
||||
pub fn get_remotes(conn: &Connection) -> Result<Vec<Instance>> {
|
||||
instances::table
|
||||
.filter(instances::local.eq(false))
|
||||
.load::<Instance>(conn)
|
||||
.expect("Instance::get_remotes: loading error")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn page(conn: &Connection, (min, max): (i32, i32)) -> Vec<Instance> {
|
||||
pub fn page(conn: &Connection, (min, max): (i32, i32)) -> Result<Vec<Instance>> {
|
||||
instances::table
|
||||
.order(instances::public_domain.asc())
|
||||
.offset(min.into())
|
||||
.limit((max - min).into())
|
||||
.load::<Instance>(conn)
|
||||
.expect("Instance::page: loading error")
|
||||
}
|
||||
|
||||
pub fn local_id(conn: &Connection) -> i32 {
|
||||
Instance::get_local(conn)
|
||||
.expect("Instance::local_id: local instance not found error")
|
||||
.id
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
insert!(instances, NewInstance);
|
||||
get!(instances);
|
||||
find_by!(instances, find_by_domain, public_domain as &str);
|
||||
|
||||
pub fn toggle_block(&self, conn: &Connection) {
|
||||
pub fn toggle_block(&self, conn: &Connection) -> Result<()> {
|
||||
diesel::update(self)
|
||||
.set(instances::blocked.eq(!self.blocked))
|
||||
.execute(conn)
|
||||
.expect("Instance::toggle_block: update error");
|
||||
.map(|_| ())
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
/// id: AP object id
|
||||
pub fn is_blocked(conn: &Connection, id: &str) -> bool {
|
||||
pub fn is_blocked(conn: &Connection, id: &str) -> Result<bool> {
|
||||
for block in instances::table
|
||||
.filter(instances::blocked.eq(true))
|
||||
.get_results::<Instance>(conn)
|
||||
.expect("Instance::is_blocked: loading error")
|
||||
.get_results::<Instance>(conn)?
|
||||
{
|
||||
if id.starts_with(&format!("https://{}/", block.public_domain)) {
|
||||
return true;
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub fn has_admin(&self, conn: &Connection) -> bool {
|
||||
!users::table
|
||||
pub fn has_admin(&self, conn: &Connection) -> Result<bool> {
|
||||
users::table
|
||||
.filter(users::instance_id.eq(self.id))
|
||||
.filter(users::is_admin.eq(true))
|
||||
.load::<User>(conn)
|
||||
.expect("Instance::has_admin: loading error")
|
||||
.is_empty()
|
||||
.map_err(Error::from)
|
||||
.map(|r| !r.is_empty())
|
||||
}
|
||||
|
||||
pub fn main_admin(&self, conn: &Connection) -> User {
|
||||
pub fn main_admin(&self, conn: &Connection) -> Result<User> {
|
||||
users::table
|
||||
.filter(users::instance_id.eq(self.id))
|
||||
.filter(users::is_admin.eq(true))
|
||||
.limit(1)
|
||||
.get_result::<User>(conn)
|
||||
.expect("Instance::main_admin: loading error")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn compute_box(
|
||||
@@ -138,7 +131,7 @@ impl Instance {
|
||||
open_registrations: bool,
|
||||
short_description: SafeString,
|
||||
long_description: SafeString,
|
||||
) {
|
||||
) -> Result<()> {
|
||||
let (sd, _, _) = md_to_html(short_description.as_ref(), &self.public_domain);
|
||||
let (ld, _, _) = md_to_html(long_description.as_ref(), &self.public_domain);
|
||||
diesel::update(self)
|
||||
@@ -151,14 +144,15 @@ impl Instance {
|
||||
instances::long_description_html.eq(ld),
|
||||
))
|
||||
.execute(conn)
|
||||
.expect("Instance::update: update error");
|
||||
.map(|_| ())
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn count(conn: &Connection) -> i64 {
|
||||
pub fn count(conn: &Connection) -> Result<i64> {
|
||||
instances::table
|
||||
.count()
|
||||
.get_result(conn)
|
||||
.expect("Instance::count: counting error")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +214,7 @@ pub(crate) mod tests {
|
||||
(
|
||||
inst.clone(),
|
||||
Instance::find_by_domain(conn, &inst.public_domain)
|
||||
.unwrap_or_else(|| Instance::insert(conn, inst)),
|
||||
.unwrap_or_else(|_| Instance::insert(conn, inst).unwrap()),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
@@ -253,7 +247,6 @@ pub(crate) mod tests {
|
||||
assert_eq!(res.long_description_html.get(), &inserted.long_description_html);
|
||||
assert_eq!(res.short_description_html.get(), &inserted.short_description_html);
|
||||
|
||||
assert_eq!(Instance::local_id(conn), res.id);
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
@@ -263,9 +256,9 @@ pub(crate) mod tests {
|
||||
let conn = &db();
|
||||
conn.test_transaction::<_, (), _>(|| {
|
||||
let inserted = fill_database(conn);
|
||||
assert_eq!(Instance::count(conn), inserted.len() as i64);
|
||||
assert_eq!(Instance::count(conn).unwrap(), inserted.len() as i64);
|
||||
|
||||
let res = Instance::get_remotes(conn);
|
||||
let res = Instance::get_remotes(conn).unwrap();
|
||||
assert_eq!(
|
||||
res.len(),
|
||||
inserted.iter().filter(|(inst, _)| !inst.local).count()
|
||||
@@ -293,15 +286,15 @@ pub(crate) mod tests {
|
||||
assert_eq!(&newinst.short_description_html, inst.short_description_html.get());
|
||||
});
|
||||
|
||||
let page = Instance::page(conn, (0, 2));
|
||||
let page = Instance::page(conn, (0, 2)).unwrap();
|
||||
assert_eq!(page.len(), 2);
|
||||
let page1 = &page[0];
|
||||
let page2 = &page[1];
|
||||
assert!(page1.public_domain <= page2.public_domain);
|
||||
|
||||
let mut last_domaine: String = Instance::page(conn, (0, 1))[0].public_domain.clone();
|
||||
let mut last_domaine: String = Instance::page(conn, (0, 1)).unwrap()[0].public_domain.clone();
|
||||
for i in 1..inserted.len() as i32 {
|
||||
let page = Instance::page(conn, (i, i + 1));
|
||||
let page = Instance::page(conn, (i, i + 1)).unwrap();
|
||||
assert_eq!(page.len(), 1);
|
||||
assert!(last_domaine <= page[0].public_domain);
|
||||
last_domaine = page[0].public_domain.clone();
|
||||
@@ -320,7 +313,7 @@ pub(crate) mod tests {
|
||||
let inst_list = &inst_list[1..];
|
||||
|
||||
let blocked = inst.blocked;
|
||||
inst.toggle_block(conn);
|
||||
inst.toggle_block(conn).unwrap();
|
||||
let inst = Instance::get(conn, inst.id).unwrap();
|
||||
assert_eq!(inst.blocked, !blocked);
|
||||
assert_eq!(
|
||||
@@ -333,25 +326,25 @@ pub(crate) mod tests {
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
Instance::is_blocked(conn, &format!("https://{}/something", inst.public_domain)),
|
||||
Instance::is_blocked(conn, &format!("https://{}/something", inst.public_domain)).unwrap(),
|
||||
inst.blocked
|
||||
);
|
||||
assert_eq!(
|
||||
Instance::is_blocked(conn, &format!("https://{}a/something", inst.public_domain)),
|
||||
Instance::is_blocked(conn, &format!("https://{}a/something", inst.public_domain)).unwrap(),
|
||||
Instance::find_by_domain(conn, &format!("{}a", inst.public_domain))
|
||||
.map(|inst| inst.blocked)
|
||||
.unwrap_or(false)
|
||||
);
|
||||
|
||||
inst.toggle_block(conn);
|
||||
inst.toggle_block(conn).unwrap();
|
||||
let inst = Instance::get(conn, inst.id).unwrap();
|
||||
assert_eq!(inst.blocked, blocked);
|
||||
assert_eq!(
|
||||
Instance::is_blocked(conn, &format!("https://{}/something", inst.public_domain)),
|
||||
Instance::is_blocked(conn, &format!("https://{}/something", inst.public_domain)).unwrap(),
|
||||
inst.blocked
|
||||
);
|
||||
assert_eq!(
|
||||
Instance::is_blocked(conn, &format!("https://{}a/something", inst.public_domain)),
|
||||
Instance::is_blocked(conn, &format!("https://{}a/something", inst.public_domain)).unwrap(),
|
||||
Instance::find_by_domain(conn, &format!("{}a", inst.public_domain))
|
||||
.map(|inst| inst.blocked)
|
||||
.unwrap_or(false)
|
||||
@@ -382,7 +375,7 @@ pub(crate) mod tests {
|
||||
false,
|
||||
SafeString::new("[short](#link)"),
|
||||
SafeString::new("[long_description](/with_link)"),
|
||||
);
|
||||
).unwrap();
|
||||
let inst = Instance::get(conn, inst.id).unwrap();
|
||||
assert_eq!(inst.name, "NewName".to_owned());
|
||||
assert_eq!(inst.open_registrations, false);
|
||||
|
||||
+114
-22
@@ -1,4 +1,5 @@
|
||||
#![allow(proc_macro_derive_resolution_fallback)] // This can be removed after diesel-1.4
|
||||
#![feature(try_trait)]
|
||||
|
||||
extern crate activitypub;
|
||||
extern crate ammonia;
|
||||
@@ -47,6 +48,102 @@ pub type Connection = diesel::SqliteConnection;
|
||||
#[cfg(all(not(feature = "sqlite"), feature = "postgres"))]
|
||||
pub type Connection = diesel::PgConnection;
|
||||
|
||||
/// All the possible errors that can be encoutered in this crate
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Db(diesel::result::Error),
|
||||
InvalidValue,
|
||||
Io(std::io::Error),
|
||||
MissingApProperty,
|
||||
NotFound,
|
||||
Request,
|
||||
SerDe,
|
||||
Search(search::SearcherError),
|
||||
Signature,
|
||||
Unauthorized,
|
||||
Url,
|
||||
Webfinger,
|
||||
}
|
||||
|
||||
impl From<bcrypt::BcryptError> for Error {
|
||||
fn from(_: bcrypt::BcryptError) -> Self {
|
||||
Error::Signature
|
||||
}
|
||||
}
|
||||
|
||||
impl From<openssl::error::ErrorStack> for Error {
|
||||
fn from(_: openssl::error::ErrorStack) -> Self {
|
||||
Error::Signature
|
||||
}
|
||||
}
|
||||
|
||||
impl From<diesel::result::Error> for Error {
|
||||
fn from(err: diesel::result::Error) -> Self {
|
||||
Error::Db(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::option::NoneError> for Error {
|
||||
fn from(_: std::option::NoneError) -> Self {
|
||||
Error::NotFound
|
||||
}
|
||||
}
|
||||
|
||||
impl From<url::ParseError> for Error {
|
||||
fn from(_: url::ParseError) -> Self {
|
||||
Error::Url
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for Error {
|
||||
fn from(_: serde_json::Error) -> Self {
|
||||
Error::SerDe
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for Error {
|
||||
fn from(_: reqwest::Error) -> Self {
|
||||
Error::Request
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reqwest::header::InvalidHeaderValue> for Error {
|
||||
fn from(_: reqwest::header::InvalidHeaderValue) -> Self {
|
||||
Error::Request
|
||||
}
|
||||
}
|
||||
|
||||
impl From<activitypub::Error> for Error {
|
||||
fn from(err: activitypub::Error) -> Self {
|
||||
match err {
|
||||
activitypub::Error::NotFound => Error::MissingApProperty,
|
||||
_ => Error::SerDe,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<webfinger::WebfingerError> for Error {
|
||||
fn from(_: webfinger::WebfingerError) -> Self {
|
||||
Error::Webfinger
|
||||
}
|
||||
}
|
||||
|
||||
impl From<search::SearcherError> for Error {
|
||||
fn from(err: search::SearcherError) -> Self {
|
||||
Error::Search(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
Error::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
pub type ApiResult<T> = std::result::Result<T, canapi::Error>;
|
||||
|
||||
/// Adds a function to a model, that returns the first
|
||||
/// matching row for a given list of fields.
|
||||
///
|
||||
@@ -63,13 +160,14 @@ pub type Connection = diesel::PgConnection;
|
||||
macro_rules! find_by {
|
||||
($table:ident, $fn:ident, $($col:ident as $type:ty),+) => {
|
||||
/// Try to find a $table with a given $col
|
||||
pub fn $fn(conn: &crate::Connection, $($col: $type),+) -> Option<Self> {
|
||||
pub fn $fn(conn: &crate::Connection, $($col: $type),+) -> Result<Self> {
|
||||
$table::table
|
||||
$(.filter($table::$col.eq($col)))+
|
||||
.limit(1)
|
||||
.load::<Self>(conn)
|
||||
.expect("macro::find_by: Error loading $table by $col")
|
||||
.into_iter().nth(0)
|
||||
.load::<Self>(conn)?
|
||||
.into_iter()
|
||||
.next()
|
||||
.ok_or(Error::NotFound)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -89,11 +187,11 @@ macro_rules! find_by {
|
||||
macro_rules! list_by {
|
||||
($table:ident, $fn:ident, $($col:ident as $type:ty),+) => {
|
||||
/// Try to find a $table with a given $col
|
||||
pub fn $fn(conn: &crate::Connection, $($col: $type),+) -> Vec<Self> {
|
||||
pub fn $fn(conn: &crate::Connection, $($col: $type),+) -> Result<Vec<Self>> {
|
||||
$table::table
|
||||
$(.filter($table::$col.eq($col)))+
|
||||
.load::<Self>(conn)
|
||||
.expect("macro::list_by: Error loading $table by $col")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -112,14 +210,14 @@ macro_rules! list_by {
|
||||
/// ```
|
||||
macro_rules! get {
|
||||
($table:ident) => {
|
||||
pub fn get(conn: &crate::Connection, id: i32) -> Option<Self> {
|
||||
pub fn get(conn: &crate::Connection, id: i32) -> Result<Self> {
|
||||
$table::table
|
||||
.filter($table::id.eq(id))
|
||||
.limit(1)
|
||||
.load::<Self>(conn)
|
||||
.expect("macro::get: Error loading $table by id")
|
||||
.load::<Self>(conn)?
|
||||
.into_iter()
|
||||
.nth(0)
|
||||
.next()
|
||||
.ok_or(Error::NotFound)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -140,11 +238,10 @@ macro_rules! insert {
|
||||
($table:ident, $from:ident) => {
|
||||
last!($table);
|
||||
|
||||
pub fn insert(conn: &crate::Connection, new: $from) -> Self {
|
||||
pub fn insert(conn: &crate::Connection, new: $from) -> Result<Self> {
|
||||
diesel::insert_into($table::table)
|
||||
.values(new)
|
||||
.execute(conn)
|
||||
.expect("macro::insert: Error saving new $table");
|
||||
.execute(conn)?;
|
||||
Self::last(conn)
|
||||
}
|
||||
};
|
||||
@@ -164,19 +261,14 @@ macro_rules! insert {
|
||||
/// ```
|
||||
macro_rules! last {
|
||||
($table:ident) => {
|
||||
pub fn last(conn: &crate::Connection) -> Self {
|
||||
pub fn last(conn: &crate::Connection) -> Result<Self> {
|
||||
$table::table
|
||||
.order_by($table::id.desc())
|
||||
.limit(1)
|
||||
.load::<Self>(conn)
|
||||
.expect(concat!(
|
||||
"macro::last: Error getting last ",
|
||||
stringify!($table)
|
||||
))
|
||||
.iter()
|
||||
.load::<Self>(conn)?
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect(concat!("macro::last: No last ", stringify!($table)))
|
||||
.clone()
|
||||
.ok_or(Error::NotFound)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+49
-62
@@ -10,7 +10,7 @@ use plume_common::activity_pub::{
|
||||
use posts::Post;
|
||||
use schema::likes;
|
||||
use users::User;
|
||||
use Connection;
|
||||
use {Connection, Error, Result};
|
||||
|
||||
#[derive(Clone, Queryable, Identifiable)]
|
||||
pub struct Like {
|
||||
@@ -35,69 +35,64 @@ impl Like {
|
||||
find_by!(likes, find_by_ap_url, ap_url as &str);
|
||||
find_by!(likes, find_by_user_on_post, user_id as i32, post_id as i32);
|
||||
|
||||
pub fn to_activity(&self, conn: &Connection) -> activity::Like {
|
||||
pub fn to_activity(&self, conn: &Connection) -> Result<activity::Like> {
|
||||
let mut act = activity::Like::default();
|
||||
act.like_props
|
||||
.set_actor_link(
|
||||
User::get(conn, self.user_id)
|
||||
.expect("Like::to_activity: user error")
|
||||
User::get(conn, self.user_id)?
|
||||
.into_id(),
|
||||
)
|
||||
.expect("Like::to_activity: actor error");
|
||||
)?;
|
||||
act.like_props
|
||||
.set_object_link(
|
||||
Post::get(conn, self.post_id)
|
||||
.expect("Like::to_activity: post error")
|
||||
Post::get(conn, self.post_id)?
|
||||
.into_id(),
|
||||
)
|
||||
.expect("Like::to_activity: object error");
|
||||
)?;
|
||||
act.object_props
|
||||
.set_to_link(Id::new(PUBLIC_VISIBILTY.to_string()))
|
||||
.expect("Like::to_activity: to error");
|
||||
.set_to_link(Id::new(PUBLIC_VISIBILTY.to_string()))?;
|
||||
act.object_props
|
||||
.set_cc_link_vec::<Id>(vec![])
|
||||
.expect("Like::to_activity: cc error");
|
||||
.set_cc_link_vec::<Id>(vec![])?;
|
||||
act.object_props
|
||||
.set_id_string(self.ap_url.clone())
|
||||
.expect("Like::to_activity: id error");
|
||||
.set_id_string(self.ap_url.clone())?;
|
||||
|
||||
act
|
||||
Ok(act)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromActivity<activity::Like, Connection> for Like {
|
||||
fn from_activity(conn: &Connection, like: activity::Like, _actor: Id) -> Like {
|
||||
type Error = Error;
|
||||
|
||||
fn from_activity(conn: &Connection, like: activity::Like, _actor: Id) -> Result<Like> {
|
||||
let liker = User::from_url(
|
||||
conn,
|
||||
like.like_props
|
||||
.actor
|
||||
.as_str()
|
||||
.expect("Like::from_activity: actor error"),
|
||||
);
|
||||
.as_str()?,
|
||||
)?;
|
||||
let post = Post::find_by_ap_url(
|
||||
conn,
|
||||
like.like_props
|
||||
.object
|
||||
.as_str()
|
||||
.expect("Like::from_activity: object error"),
|
||||
);
|
||||
.as_str()?,
|
||||
)?;
|
||||
let res = Like::insert(
|
||||
conn,
|
||||
NewLike {
|
||||
post_id: post.expect("Like::from_activity: post error").id,
|
||||
user_id: liker.expect("Like::from_activity: user error").id,
|
||||
ap_url: like.object_props.id_string().unwrap_or_default(),
|
||||
post_id: post.id,
|
||||
user_id: liker.id,
|
||||
ap_url: like.object_props.id_string()?,
|
||||
},
|
||||
);
|
||||
res.notify(conn);
|
||||
res
|
||||
)?;
|
||||
res.notify(conn)?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
impl Notify<Connection> for Like {
|
||||
fn notify(&self, conn: &Connection) {
|
||||
let post = Post::get(conn, self.post_id).expect("Like::notify: post error");
|
||||
for author in post.get_authors(conn) {
|
||||
type Error = Error;
|
||||
|
||||
fn notify(&self, conn: &Connection) -> Result<()> {
|
||||
let post = Post::get(conn, self.post_id)?;
|
||||
for author in post.get_authors(conn)? {
|
||||
Notification::insert(
|
||||
conn,
|
||||
NewNotification {
|
||||
@@ -105,55 +100,47 @@ impl Notify<Connection> for Like {
|
||||
object_id: self.id,
|
||||
user_id: author.id,
|
||||
},
|
||||
);
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deletable<Connection, activity::Undo> for Like {
|
||||
fn delete(&self, conn: &Connection) -> activity::Undo {
|
||||
type Error = Error;
|
||||
|
||||
fn delete(&self, conn: &Connection) -> Result<activity::Undo> {
|
||||
diesel::delete(self)
|
||||
.execute(conn)
|
||||
.expect("Like::delete: delete error");
|
||||
.execute(conn)?;
|
||||
|
||||
// delete associated notification if any
|
||||
if let Some(notif) = Notification::find(conn, notification_kind::LIKE, self.id) {
|
||||
if let Ok(notif) = Notification::find(conn, notification_kind::LIKE, self.id) {
|
||||
diesel::delete(¬if)
|
||||
.execute(conn)
|
||||
.expect("Like::delete: notification error");
|
||||
.execute(conn)?;
|
||||
}
|
||||
|
||||
let mut act = activity::Undo::default();
|
||||
act.undo_props
|
||||
.set_actor_link(
|
||||
User::get(conn, self.user_id)
|
||||
.expect("Like::delete: user error")
|
||||
.into_id(),
|
||||
)
|
||||
.expect("Like::delete: actor error");
|
||||
.set_actor_link(User::get(conn, self.user_id)?.into_id(),)?;
|
||||
act.undo_props
|
||||
.set_object_object(self.to_activity(conn))
|
||||
.expect("Like::delete: object error");
|
||||
.set_object_object(self.to_activity(conn)?)?;
|
||||
act.object_props
|
||||
.set_id_string(format!("{}#delete", self.ap_url))
|
||||
.expect("Like::delete: id error");
|
||||
.set_id_string(format!("{}#delete", self.ap_url))?;
|
||||
act.object_props
|
||||
.set_to_link(Id::new(PUBLIC_VISIBILTY.to_string()))
|
||||
.expect("Like::delete: to error");
|
||||
.set_to_link(Id::new(PUBLIC_VISIBILTY.to_string()))?;
|
||||
act.object_props
|
||||
.set_cc_link_vec::<Id>(vec![])
|
||||
.expect("Like::delete: cc error");
|
||||
.set_cc_link_vec::<Id>(vec![])?;
|
||||
|
||||
act
|
||||
Ok(act)
|
||||
}
|
||||
|
||||
fn delete_id(id: &str, actor_id: &str, conn: &Connection) {
|
||||
if let Some(like) = Like::find_by_ap_url(conn, id) {
|
||||
if let Some(user) = User::find_by_ap_url(conn, actor_id) {
|
||||
if user.id == like.user_id {
|
||||
like.delete(conn);
|
||||
}
|
||||
}
|
||||
fn delete_id(id: &str, actor_id: &str, conn: &Connection) -> Result<activity::Undo> {
|
||||
let like = Like::find_by_ap_url(conn, id)?;
|
||||
let user = User::find_by_ap_url(conn, actor_id)?;
|
||||
if user.id == like.user_id {
|
||||
like.delete(conn)
|
||||
} else {
|
||||
Err(Error::Unauthorized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+46
-46
@@ -11,7 +11,7 @@ use instance::Instance;
|
||||
use safe_string::SafeString;
|
||||
use schema::medias;
|
||||
use users::User;
|
||||
use {ap_url, Connection};
|
||||
use {ap_url, Connection, Error, Result};
|
||||
|
||||
#[derive(Clone, Identifiable, Queryable, Serialize)]
|
||||
pub struct Media {
|
||||
@@ -50,10 +50,10 @@ impl Media {
|
||||
get!(medias);
|
||||
list_by!(medias, for_user, owner_id as i32);
|
||||
|
||||
pub fn list_all_medias(conn: &Connection) -> Vec<Media> {
|
||||
pub fn list_all_medias(conn: &Connection) -> Result<Vec<Media>> {
|
||||
medias::table
|
||||
.load::<Media>(conn)
|
||||
.expect("Media::list_all_medias: loading error")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn category(&self) -> MediaCategory {
|
||||
@@ -70,9 +70,9 @@ impl Media {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn preview_html(&self, conn: &Connection) -> SafeString {
|
||||
let url = self.url(conn);
|
||||
match self.category() {
|
||||
pub fn preview_html(&self, conn: &Connection) -> Result<SafeString> {
|
||||
let url = self.url(conn)?;
|
||||
Ok(match self.category() {
|
||||
MediaCategory::Image => SafeString::new(&format!(
|
||||
r#"<img src="{}" alt="{}" title="{}" class=\"preview\">"#,
|
||||
url, escape(&self.alt_text), escape(&self.alt_text)
|
||||
@@ -86,12 +86,12 @@ impl Media {
|
||||
url, escape(&self.alt_text)
|
||||
)),
|
||||
MediaCategory::Unknown => SafeString::new(""),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn html(&self, conn: &Connection) -> SafeString {
|
||||
let url = self.url(conn);
|
||||
match self.category() {
|
||||
pub fn html(&self, conn: &Connection) -> Result<SafeString> {
|
||||
let url = self.url(conn)?;
|
||||
Ok(match self.category() {
|
||||
MediaCategory::Image => SafeString::new(&format!(
|
||||
r#"<img src="{}" alt="{}" title="{}">"#,
|
||||
url, escape(&self.alt_text), escape(&self.alt_text)
|
||||
@@ -105,46 +105,45 @@ impl Media {
|
||||
url, escape(&self.alt_text)
|
||||
)),
|
||||
MediaCategory::Unknown => SafeString::new(""),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn markdown(&self, conn: &Connection) -> SafeString {
|
||||
let url = self.url(conn);
|
||||
match self.category() {
|
||||
pub fn markdown(&self, conn: &Connection) -> Result<SafeString> {
|
||||
let url = self.url(conn)?;
|
||||
Ok(match self.category() {
|
||||
MediaCategory::Image => SafeString::new(&format!("", escape(&self.alt_text), url)),
|
||||
MediaCategory::Audio | MediaCategory::Video => self.html(conn),
|
||||
MediaCategory::Audio | MediaCategory::Video => self.html(conn)?,
|
||||
MediaCategory::Unknown => SafeString::new(""),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn url(&self, conn: &Connection) -> String {
|
||||
pub fn url(&self, conn: &Connection) -> Result<String> {
|
||||
if self.is_remote {
|
||||
self.remote_url.clone().unwrap_or_default()
|
||||
Ok(self.remote_url.clone().unwrap_or_default())
|
||||
} else {
|
||||
ap_url(&format!(
|
||||
Ok(ap_url(&format!(
|
||||
"{}/{}",
|
||||
Instance::get_local(conn)
|
||||
.expect("Media::url: local instance not found error")
|
||||
.public_domain,
|
||||
Instance::get_local(conn)?.public_domain,
|
||||
self.file_path
|
||||
))
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(&self, conn: &Connection) {
|
||||
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
||||
if !self.is_remote {
|
||||
fs::remove_file(self.file_path.as_str()).expect("Media::delete: file deletion error");
|
||||
fs::remove_file(self.file_path.as_str())?;
|
||||
}
|
||||
diesel::delete(self)
|
||||
.execute(conn)
|
||||
.expect("Media::delete: database entry deletion error");
|
||||
.map(|_| ())
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn save_remote(conn: &Connection, url: String, user: &User) -> Result<Media, ()> {
|
||||
pub fn save_remote(conn: &Connection, url: String, user: &User) -> Result<Media> {
|
||||
if url.contains(&['<', '>', '"'][..]) {
|
||||
Err(())
|
||||
Err(Error::Url)
|
||||
} else {
|
||||
Ok(Media::insert(
|
||||
Media::insert(
|
||||
conn,
|
||||
NewMedia {
|
||||
file_path: String::new(),
|
||||
@@ -155,19 +154,20 @@ impl Media {
|
||||
content_warning: None,
|
||||
owner_id: user.id,
|
||||
},
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_owner(&self, conn: &Connection, user: &User) {
|
||||
pub fn set_owner(&self, conn: &Connection, user: &User) -> Result<()> {
|
||||
diesel::update(self)
|
||||
.set(medias::owner_id.eq(user.id))
|
||||
.execute(conn)
|
||||
.expect("Media::set_owner: owner update error");
|
||||
.map(|_| ())
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
// TODO: merge with save_remote?
|
||||
pub fn from_activity(conn: &Connection, image: &Image) -> Option<Media> {
|
||||
pub fn from_activity(conn: &Connection, image: &Image) -> Result<Media> {
|
||||
let remote_url = image.object_props.url_string().ok()?;
|
||||
let ext = remote_url
|
||||
.rsplit('.')
|
||||
@@ -185,7 +185,7 @@ impl Media {
|
||||
.copy_to(&mut dest)
|
||||
.ok()?;
|
||||
|
||||
Some(Media::insert(
|
||||
Media::insert(
|
||||
conn,
|
||||
NewMedia {
|
||||
file_path: path.to_str()?.to_string(),
|
||||
@@ -205,7 +205,7 @@ impl Media {
|
||||
.as_ref(),
|
||||
)?.id,
|
||||
},
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,14 +265,14 @@ pub(crate) mod tests {
|
||||
owner_id: user_two,
|
||||
},
|
||||
].into_iter()
|
||||
.map(|nm| Media::insert(conn, nm))
|
||||
.map(|nm| Media::insert(conn, nm).unwrap())
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub(crate) fn clean(conn: &Conn) {
|
||||
//used to remove files generated by tests
|
||||
for media in Media::list_all_medias(conn) {
|
||||
media.delete(conn);
|
||||
for media in Media::list_all_medias(conn).unwrap() {
|
||||
media.delete(conn).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,10 +298,10 @@ pub(crate) mod tests {
|
||||
content_warning: None,
|
||||
owner_id: user,
|
||||
},
|
||||
);
|
||||
).unwrap();
|
||||
|
||||
assert!(Path::new(&path).exists());
|
||||
media.delete(conn);
|
||||
media.delete(conn).unwrap();
|
||||
assert!(!Path::new(&path).exists());
|
||||
|
||||
clean(conn);
|
||||
@@ -333,26 +333,26 @@ pub(crate) mod tests {
|
||||
content_warning: None,
|
||||
owner_id: u1.id,
|
||||
},
|
||||
);
|
||||
).unwrap();
|
||||
|
||||
assert!(
|
||||
Media::for_user(conn, u1.id)
|
||||
Media::for_user(conn, u1.id).unwrap()
|
||||
.iter()
|
||||
.any(|m| m.id == media.id)
|
||||
);
|
||||
assert!(
|
||||
!Media::for_user(conn, u2.id)
|
||||
!Media::for_user(conn, u2.id).unwrap()
|
||||
.iter()
|
||||
.any(|m| m.id == media.id)
|
||||
);
|
||||
media.set_owner(conn, u2);
|
||||
media.set_owner(conn, u2).unwrap();
|
||||
assert!(
|
||||
!Media::for_user(conn, u1.id)
|
||||
!Media::for_user(conn, u1.id).unwrap()
|
||||
.iter()
|
||||
.any(|m| m.id == media.id)
|
||||
);
|
||||
assert!(
|
||||
Media::for_user(conn, u2.id)
|
||||
Media::for_user(conn, u2.id).unwrap()
|
||||
.iter()
|
||||
.any(|m| m.id == media.id)
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ use plume_common::activity_pub::inbox::Notify;
|
||||
use posts::Post;
|
||||
use schema::mentions;
|
||||
use users::User;
|
||||
use Connection;
|
||||
use {Connection, Error, Result};
|
||||
|
||||
#[derive(Clone, Queryable, Identifiable, Serialize, Deserialize)]
|
||||
pub struct Mention {
|
||||
@@ -32,54 +32,47 @@ impl Mention {
|
||||
list_by!(mentions, list_for_post, post_id as i32);
|
||||
list_by!(mentions, list_for_comment, comment_id as i32);
|
||||
|
||||
pub fn get_mentioned(&self, conn: &Connection) -> Option<User> {
|
||||
pub fn get_mentioned(&self, conn: &Connection) -> Result<User> {
|
||||
User::get(conn, self.mentioned_id)
|
||||
}
|
||||
|
||||
pub fn get_post(&self, conn: &Connection) -> Option<Post> {
|
||||
self.post_id.and_then(|id| Post::get(conn, id))
|
||||
pub fn get_post(&self, conn: &Connection) -> Result<Post> {
|
||||
self.post_id.ok_or(Error::NotFound).and_then(|id| Post::get(conn, id))
|
||||
}
|
||||
|
||||
pub fn get_comment(&self, conn: &Connection) -> Option<Comment> {
|
||||
self.comment_id.and_then(|id| Comment::get(conn, id))
|
||||
pub fn get_comment(&self, conn: &Connection) -> Result<Comment> {
|
||||
self.comment_id.ok_or(Error::NotFound).and_then(|id| Comment::get(conn, id))
|
||||
}
|
||||
|
||||
pub fn get_user(&self, conn: &Connection) -> Option<User> {
|
||||
pub fn get_user(&self, conn: &Connection) -> Result<User> {
|
||||
match self.get_post(conn) {
|
||||
Some(p) => p.get_authors(conn).into_iter().next(),
|
||||
None => self.get_comment(conn).map(|c| c.get_author(conn)),
|
||||
Ok(p) => Ok(p.get_authors(conn)?.into_iter().next()?),
|
||||
Err(_) => self.get_comment(conn).and_then(|c| c.get_author(conn)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_activity(conn: &Connection, ment: &str) -> link::Mention {
|
||||
let user = User::find_by_fqn(conn, ment);
|
||||
pub fn build_activity(conn: &Connection, ment: &str) -> Result<link::Mention> {
|
||||
let user = User::find_by_fqn(conn, ment)?;
|
||||
let mut mention = link::Mention::default();
|
||||
mention
|
||||
.link_props
|
||||
.set_href_string(user.clone().map(|u| u.ap_url).unwrap_or_default())
|
||||
.expect("Mention::build_activity: href error");
|
||||
.set_href_string(user.ap_url)?;
|
||||
mention
|
||||
.link_props
|
||||
.set_name_string(format!("@{}", ment))
|
||||
.expect("Mention::build_activity: name error:");
|
||||
mention
|
||||
.set_name_string(format!("@{}", ment))?;
|
||||
Ok(mention)
|
||||
}
|
||||
|
||||
pub fn to_activity(&self, conn: &Connection) -> link::Mention {
|
||||
let user = self.get_mentioned(conn);
|
||||
pub fn to_activity(&self, conn: &Connection) -> Result<link::Mention> {
|
||||
let user = self.get_mentioned(conn)?;
|
||||
let mut mention = link::Mention::default();
|
||||
mention
|
||||
.link_props
|
||||
.set_href_string(user.clone().map(|u| u.ap_url).unwrap_or_default())
|
||||
.expect("Mention::to_activity: href error");
|
||||
.set_href_string(user.ap_url.clone())?;
|
||||
mention
|
||||
.link_props
|
||||
.set_name_string(
|
||||
user.map(|u| format!("@{}", u.get_fqn(conn)))
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.expect("Mention::to_activity: mention error");
|
||||
mention
|
||||
.set_name_string(format!("@{}", user.get_fqn(conn)))?;
|
||||
Ok(mention)
|
||||
}
|
||||
|
||||
pub fn from_activity(
|
||||
@@ -88,12 +81,12 @@ impl Mention {
|
||||
inside: i32,
|
||||
in_post: bool,
|
||||
notify: bool,
|
||||
) -> Option<Self> {
|
||||
) -> Result<Self> {
|
||||
let ap_url = ment.link_props.href_string().ok()?;
|
||||
let mentioned = User::find_by_ap_url(conn, &ap_url)?;
|
||||
|
||||
if in_post {
|
||||
Post::get(conn, inside).map(|post| {
|
||||
Post::get(conn, inside).and_then(|post| {
|
||||
let res = Mention::insert(
|
||||
conn,
|
||||
NewMention {
|
||||
@@ -101,14 +94,14 @@ impl Mention {
|
||||
post_id: Some(post.id),
|
||||
comment_id: None,
|
||||
},
|
||||
);
|
||||
)?;
|
||||
if notify {
|
||||
res.notify(conn);
|
||||
res.notify(conn)?;
|
||||
}
|
||||
res
|
||||
Ok(res)
|
||||
})
|
||||
} else {
|
||||
Comment::get(conn, inside).map(|comment| {
|
||||
Comment::get(conn, inside).and_then(|comment| {
|
||||
let res = Mention::insert(
|
||||
conn,
|
||||
NewMention {
|
||||
@@ -116,37 +109,38 @@ impl Mention {
|
||||
post_id: None,
|
||||
comment_id: Some(comment.id),
|
||||
},
|
||||
);
|
||||
)?;
|
||||
if notify {
|
||||
res.notify(conn);
|
||||
res.notify(conn)?;
|
||||
}
|
||||
res
|
||||
Ok(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(&self, conn: &Connection) {
|
||||
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
||||
//find related notifications and delete them
|
||||
if let Some(n) = Notification::find(conn, notification_kind::MENTION, self.id) {
|
||||
n.delete(conn)
|
||||
if let Ok(n) = Notification::find(conn, notification_kind::MENTION, self.id) {
|
||||
n.delete(conn)?;
|
||||
}
|
||||
diesel::delete(self)
|
||||
.execute(conn)
|
||||
.expect("Mention::delete: mention deletion error");
|
||||
.map(|_| ())
|
||||
.map_err(Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
impl Notify<Connection> for Mention {
|
||||
fn notify(&self, conn: &Connection) {
|
||||
if let Some(m) = self.get_mentioned(conn) {
|
||||
Notification::insert(
|
||||
conn,
|
||||
NewNotification {
|
||||
kind: notification_kind::MENTION.to_string(),
|
||||
object_id: self.id,
|
||||
user_id: m.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
type Error = Error;
|
||||
fn notify(&self, conn: &Connection) -> Result<()> {
|
||||
let m = self.get_mentioned(conn)?;
|
||||
Notification::insert(
|
||||
conn,
|
||||
NewNotification {
|
||||
kind: notification_kind::MENTION.to_string(),
|
||||
object_id: self.id,
|
||||
user_id: m.id,
|
||||
},
|
||||
).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use posts::Post;
|
||||
use reshares::Reshare;
|
||||
use schema::notifications;
|
||||
use users::User;
|
||||
use Connection;
|
||||
use {Connection, Error, Result};
|
||||
|
||||
pub mod notification_kind {
|
||||
pub const COMMENT: &str = "COMMENT";
|
||||
@@ -40,42 +40,42 @@ impl Notification {
|
||||
insert!(notifications, NewNotification);
|
||||
get!(notifications);
|
||||
|
||||
pub fn find_for_user(conn: &Connection, user: &User) -> Vec<Notification> {
|
||||
pub fn find_for_user(conn: &Connection, user: &User) -> Result<Vec<Notification>> {
|
||||
notifications::table
|
||||
.filter(notifications::user_id.eq(user.id))
|
||||
.order_by(notifications::creation_date.desc())
|
||||
.load::<Notification>(conn)
|
||||
.expect("Notification::find_for_user: notification loading error")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn count_for_user(conn: &Connection, user: &User) -> i64 {
|
||||
pub fn count_for_user(conn: &Connection, user: &User) -> Result<i64> {
|
||||
notifications::table
|
||||
.filter(notifications::user_id.eq(user.id))
|
||||
.count()
|
||||
.get_result(conn)
|
||||
.expect("Notification::count_for_user: count loading error")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn page_for_user(
|
||||
conn: &Connection,
|
||||
user: &User,
|
||||
(min, max): (i32, i32),
|
||||
) -> Vec<Notification> {
|
||||
) -> Result<Vec<Notification>> {
|
||||
notifications::table
|
||||
.filter(notifications::user_id.eq(user.id))
|
||||
.order_by(notifications::creation_date.desc())
|
||||
.offset(min.into())
|
||||
.limit((max - min).into())
|
||||
.load::<Notification>(conn)
|
||||
.expect("Notification::page_for_user: notification loading error")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn find<S: Into<String>>(conn: &Connection, kind: S, obj: i32) -> Option<Notification> {
|
||||
pub fn find<S: Into<String>>(conn: &Connection, kind: S, obj: i32) -> Result<Notification> {
|
||||
notifications::table
|
||||
.filter(notifications::kind.eq(kind.into()))
|
||||
.filter(notifications::object_id.eq(obj))
|
||||
.get_result::<Notification>(conn)
|
||||
.ok()
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn get_message(&self) -> &'static str {
|
||||
@@ -91,41 +91,37 @@ impl Notification {
|
||||
|
||||
pub fn get_url(&self, conn: &Connection) -> Option<String> {
|
||||
match self.kind.as_ref() {
|
||||
notification_kind::COMMENT => self.get_post(conn).map(|p| format!("{}#comment-{}", p.url(conn), self.object_id)),
|
||||
notification_kind::FOLLOW => Some(format!("/@/{}/", self.get_actor(conn).get_fqn(conn))),
|
||||
notification_kind::MENTION => Mention::get(conn, self.object_id).map(|mention|
|
||||
mention.get_post(conn).map(|p| p.url(conn))
|
||||
.unwrap_or_else(|| {
|
||||
let comment = mention.get_comment(conn).expect("Notification::get_url: comment not found error");
|
||||
format!("{}#comment-{}", comment.get_post(conn).url(conn), comment.id)
|
||||
notification_kind::COMMENT => self.get_post(conn).and_then(|p| Some(format!("{}#comment-{}", p.url(conn).ok()?, self.object_id))),
|
||||
notification_kind::FOLLOW => Some(format!("/@/{}/", self.get_actor(conn).ok()?.get_fqn(conn))),
|
||||
notification_kind::MENTION => Mention::get(conn, self.object_id).and_then(|mention|
|
||||
mention.get_post(conn).and_then(|p| p.url(conn))
|
||||
.or_else(|_| {
|
||||
let comment = mention.get_comment(conn)?;
|
||||
Ok(format!("{}#comment-{}", comment.get_post(conn)?.url(conn)?, comment.id))
|
||||
})
|
||||
),
|
||||
).ok(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_post(&self, conn: &Connection) -> Option<Post> {
|
||||
match self.kind.as_ref() {
|
||||
notification_kind::COMMENT => Comment::get(conn, self.object_id).map(|comment| comment.get_post(conn)),
|
||||
notification_kind::LIKE => Like::get(conn, self.object_id).and_then(|like| Post::get(conn, like.post_id)),
|
||||
notification_kind::RESHARE => Reshare::get(conn, self.object_id).and_then(|reshare| reshare.get_post(conn)),
|
||||
notification_kind::COMMENT => Comment::get(conn, self.object_id).and_then(|comment| comment.get_post(conn)).ok(),
|
||||
notification_kind::LIKE => Like::get(conn, self.object_id).and_then(|like| Post::get(conn, like.post_id)).ok(),
|
||||
notification_kind::RESHARE => Reshare::get(conn, self.object_id).and_then(|reshare| reshare.get_post(conn)).ok(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_actor(&self, conn: &Connection) -> User {
|
||||
match self.kind.as_ref() {
|
||||
notification_kind::COMMENT => Comment::get(conn, self.object_id).expect("Notification::get_actor: comment error").get_author(conn),
|
||||
notification_kind::FOLLOW => User::get(conn, Follow::get(conn, self.object_id).expect("Notification::get_actor: follow error").follower_id)
|
||||
.expect("Notification::get_actor: follower error"),
|
||||
notification_kind::LIKE => User::get(conn, Like::get(conn, self.object_id).expect("Notification::get_actor: like error").user_id)
|
||||
.expect("Notification::get_actor: liker error"),
|
||||
notification_kind::MENTION => Mention::get(conn, self.object_id).expect("Notification::get_actor: mention error").get_user(conn)
|
||||
.expect("Notification::get_actor: mentioner error"),
|
||||
notification_kind::RESHARE => Reshare::get(conn, self.object_id).expect("Notification::get_actor: reshare error").get_user(conn)
|
||||
.expect("Notification::get_actor: resharer error"),
|
||||
pub fn get_actor(&self, conn: &Connection) -> Result<User> {
|
||||
Ok(match self.kind.as_ref() {
|
||||
notification_kind::COMMENT => Comment::get(conn, self.object_id)?.get_author(conn)?,
|
||||
notification_kind::FOLLOW => User::get(conn, Follow::get(conn, self.object_id)?.follower_id)?,
|
||||
notification_kind::LIKE => User::get(conn, Like::get(conn, self.object_id)?.user_id)?,
|
||||
notification_kind::MENTION => Mention::get(conn, self.object_id)?.get_user(conn)?,
|
||||
notification_kind::RESHARE => Reshare::get(conn, self.object_id)?.get_user(conn)?,
|
||||
_ => unreachable!("Notification::get_actor: Unknow type"),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn icon_class(&self) -> &'static str {
|
||||
@@ -139,9 +135,10 @@ impl Notification {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete(&self, conn: &Connection) {
|
||||
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
||||
diesel::delete(self)
|
||||
.execute(conn)
|
||||
.expect("Notification::delete: notification deletion error");
|
||||
.map(|_| ())
|
||||
.map_err(Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||
use posts::Post;
|
||||
use schema::post_authors;
|
||||
use users::User;
|
||||
use {Error, Result};
|
||||
|
||||
#[derive(Clone, Queryable, Identifiable, Associations)]
|
||||
#[belongs_to(Post)]
|
||||
|
||||
+239
-274
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@ use plume_common::activity_pub::{
|
||||
use posts::Post;
|
||||
use schema::reshares;
|
||||
use users::User;
|
||||
use Connection;
|
||||
use {Connection, Error, Result};
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Queryable, Identifiable)]
|
||||
pub struct Reshare {
|
||||
@@ -40,91 +40,80 @@ impl Reshare {
|
||||
post_id as i32
|
||||
);
|
||||
|
||||
pub fn get_recents_for_author(conn: &Connection, user: &User, limit: i64) -> Vec<Reshare> {
|
||||
pub fn get_recents_for_author(conn: &Connection, user: &User, limit: i64) -> Result<Vec<Reshare>> {
|
||||
reshares::table
|
||||
.filter(reshares::user_id.eq(user.id))
|
||||
.order(reshares::creation_date.desc())
|
||||
.limit(limit)
|
||||
.load::<Reshare>(conn)
|
||||
.expect("Reshare::get_recents_for_author: loading error")
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
||||
pub fn get_post(&self, conn: &Connection) -> Option<Post> {
|
||||
pub fn get_post(&self, conn: &Connection) -> Result<Post> {
|
||||
Post::get(conn, self.post_id)
|
||||
}
|
||||
|
||||
pub fn get_user(&self, conn: &Connection) -> Option<User> {
|
||||
pub fn get_user(&self, conn: &Connection) -> Result<User> {
|
||||
User::get(conn, self.user_id)
|
||||
}
|
||||
|
||||
pub fn to_activity(&self, conn: &Connection) -> Announce {
|
||||
pub fn to_activity(&self, conn: &Connection) -> Result<Announce> {
|
||||
let mut act = Announce::default();
|
||||
act.announce_props
|
||||
.set_actor_link(
|
||||
User::get(conn, self.user_id)
|
||||
.expect("Reshare::to_activity: user error")
|
||||
.into_id(),
|
||||
)
|
||||
.expect("Reshare::to_activity: actor error");
|
||||
.set_actor_link(User::get(conn, self.user_id)?.into_id())?;
|
||||
act.announce_props
|
||||
.set_object_link(
|
||||
Post::get(conn, self.post_id)
|
||||
.expect("Reshare::to_activity: post error")
|
||||
.into_id(),
|
||||
)
|
||||
.expect("Reshare::to_activity: object error");
|
||||
.set_object_link(Post::get(conn, self.post_id)?.into_id())?;
|
||||
act.object_props
|
||||
.set_id_string(self.ap_url.clone())
|
||||
.expect("Reshare::to_activity: id error");
|
||||
.set_id_string(self.ap_url.clone())?;
|
||||
act.object_props
|
||||
.set_to_link(Id::new(PUBLIC_VISIBILTY.to_string()))
|
||||
.expect("Reshare::to_activity: to error");
|
||||
.set_to_link(Id::new(PUBLIC_VISIBILTY.to_string()))?;
|
||||
act.object_props
|
||||
.set_cc_link_vec::<Id>(vec![])
|
||||
.expect("Reshare::to_activity: cc error");
|
||||
.set_cc_link_vec::<Id>(vec![])?;
|
||||
|
||||
act
|
||||
Ok(act)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromActivity<Announce, Connection> for Reshare {
|
||||
fn from_activity(conn: &Connection, announce: Announce, _actor: Id) -> Reshare {
|
||||
type Error = Error;
|
||||
|
||||
fn from_activity(conn: &Connection, announce: Announce, _actor: Id) -> Result<Reshare> {
|
||||
let user = User::from_url(
|
||||
conn,
|
||||
announce
|
||||
.announce_props
|
||||
.actor_link::<Id>()
|
||||
.expect("Reshare::from_activity: actor error")
|
||||
.actor_link::<Id>()?
|
||||
.as_ref(),
|
||||
);
|
||||
)?;
|
||||
let post = Post::find_by_ap_url(
|
||||
conn,
|
||||
announce
|
||||
.announce_props
|
||||
.object_link::<Id>()
|
||||
.expect("Reshare::from_activity: object error")
|
||||
.object_link::<Id>()?
|
||||
.as_ref(),
|
||||
);
|
||||
)?;
|
||||
let reshare = Reshare::insert(
|
||||
conn,
|
||||
NewReshare {
|
||||
post_id: post.expect("Reshare::from_activity: post error").id,
|
||||
user_id: user.expect("Reshare::from_activity: user error").id,
|
||||
post_id: post.id,
|
||||
user_id: user.id,
|
||||
ap_url: announce
|
||||
.object_props
|
||||
.id_string()
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
);
|
||||
reshare.notify(conn);
|
||||
reshare
|
||||
)?;
|
||||
reshare.notify(conn)?;
|
||||
Ok(reshare)
|
||||
}
|
||||
}
|
||||
|
||||
impl Notify<Connection> for Reshare {
|
||||
fn notify(&self, conn: &Connection) {
|
||||
let post = self.get_post(conn).expect("Reshare::notify: post error");
|
||||
for author in post.get_authors(conn) {
|
||||
type Error = Error;
|
||||
|
||||
fn notify(&self, conn: &Connection) -> Result<()> {
|
||||
let post = self.get_post(conn)?;
|
||||
for author in post.get_authors(conn)? {
|
||||
Notification::insert(
|
||||
conn,
|
||||
NewNotification {
|
||||
@@ -132,55 +121,47 @@ impl Notify<Connection> for Reshare {
|
||||
object_id: self.id,
|
||||
user_id: author.id,
|
||||
},
|
||||
);
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deletable<Connection, Undo> for Reshare {
|
||||
fn delete(&self, conn: &Connection) -> Undo {
|
||||
type Error = Error;
|
||||
|
||||
fn delete(&self, conn: &Connection) -> Result<Undo> {
|
||||
diesel::delete(self)
|
||||
.execute(conn)
|
||||
.expect("Reshare::delete: delete error");
|
||||
.execute(conn)?;
|
||||
|
||||
// delete associated notification if any
|
||||
if let Some(notif) = Notification::find(conn, notification_kind::RESHARE, self.id) {
|
||||
if let Ok(notif) = Notification::find(conn, notification_kind::RESHARE, self.id) {
|
||||
diesel::delete(¬if)
|
||||
.execute(conn)
|
||||
.expect("Reshare::delete: notification error");
|
||||
.execute(conn)?;
|
||||
}
|
||||
|
||||
let mut act = Undo::default();
|
||||
act.undo_props
|
||||
.set_actor_link(
|
||||
User::get(conn, self.user_id)
|
||||
.expect("Reshare::delete: user error")
|
||||
.into_id(),
|
||||
)
|
||||
.expect("Reshare::delete: actor error");
|
||||
.set_actor_link(User::get(conn, self.user_id)?.into_id())?;
|
||||
act.undo_props
|
||||
.set_object_object(self.to_activity(conn))
|
||||
.expect("Reshare::delete: object error");
|
||||
.set_object_object(self.to_activity(conn)?)?;
|
||||
act.object_props
|
||||
.set_id_string(format!("{}#delete", self.ap_url))
|
||||
.expect("Reshare::delete: id error");
|
||||
.set_id_string(format!("{}#delete", self.ap_url))?;
|
||||
act.object_props
|
||||
.set_to_link(Id::new(PUBLIC_VISIBILTY.to_string()))
|
||||
.expect("Reshare::delete: to error");
|
||||
.set_to_link(Id::new(PUBLIC_VISIBILTY.to_string()))?;
|
||||
act.object_props
|
||||
.set_cc_link_vec::<Id>(vec![])
|
||||
.expect("Reshare::delete: cc error");
|
||||
.set_cc_link_vec::<Id>(vec![])?;
|
||||
|
||||
act
|
||||
Ok(act)
|
||||
}
|
||||
|
||||
fn delete_id(id: &str, actor_id: &str, conn: &Connection) {
|
||||
if let Some(reshare) = Reshare::find_by_ap_url(conn, id) {
|
||||
if let Some(actor) = User::find_by_ap_url(conn, actor_id) {
|
||||
if actor.id == reshare.user_id {
|
||||
reshare.delete(conn);
|
||||
}
|
||||
}
|
||||
fn delete_id(id: &str, actor_id: &str, conn: &Connection) -> Result<Undo> {
|
||||
let reshare = Reshare::find_by_ap_url(conn, id)?;
|
||||
let actor = User::find_by_ap_url(conn, actor_id)?;
|
||||
if actor.id == reshare.user_id {
|
||||
reshare.delete(conn)
|
||||
} else {
|
||||
Err(Error::Unauthorized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ pub(crate) mod tests {
|
||||
conn.test_transaction::<_, (), _>(|| {
|
||||
let searcher = get_searcher();
|
||||
let blog = &fill_database(conn).1[0];
|
||||
let author = &blog.list_authors(conn)[0];
|
||||
let author = &blog.list_authors(conn).unwrap()[0];
|
||||
|
||||
let title = random_hex()[..8].to_owned();
|
||||
|
||||
@@ -134,23 +134,23 @@ pub(crate) mod tests {
|
||||
subtitle: "".to_owned(),
|
||||
source: "".to_owned(),
|
||||
cover_id: None,
|
||||
}, &searcher);
|
||||
}, &searcher).unwrap();
|
||||
PostAuthor::insert(conn, NewPostAuthor {
|
||||
post_id: post.id,
|
||||
author_id: author.id,
|
||||
});
|
||||
}).unwrap();
|
||||
|
||||
searcher.commit();
|
||||
assert_eq!(searcher.search_document(conn, Query::from_str(&title), (0,1))[0].id, post.id);
|
||||
|
||||
let newtitle = random_hex()[..8].to_owned();
|
||||
post.title = newtitle.clone();
|
||||
post.update(conn, &searcher);
|
||||
post.update(conn, &searcher).unwrap();
|
||||
searcher.commit();
|
||||
assert_eq!(searcher.search_document(conn, Query::from_str(&newtitle), (0,1))[0].id, post.id);
|
||||
assert!(searcher.search_document(conn, Query::from_str(&title), (0,1)).is_empty());
|
||||
|
||||
post.delete(&(conn, &searcher));
|
||||
post.delete(&(conn, &searcher)).unwrap();
|
||||
searcher.commit();
|
||||
assert!(searcher.search_document(conn, Query::from_str(&newtitle), (0,1)).is_empty());
|
||||
|
||||
|
||||
@@ -14,9 +14,10 @@ use std::{cmp, fs::create_dir_all, path::Path, sync::Mutex};
|
||||
|
||||
use search::query::PlumeQuery;
|
||||
use super::tokenizer;
|
||||
use Result;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SearcherError{
|
||||
pub enum SearcherError {
|
||||
IndexCreationError,
|
||||
WriteLockAcquisitionError,
|
||||
IndexOpeningError,
|
||||
@@ -66,7 +67,7 @@ impl Searcher {
|
||||
}
|
||||
|
||||
|
||||
pub fn create(path: &AsRef<Path>) -> Result<Self,SearcherError> {
|
||||
pub fn create(path: &AsRef<Path>) -> Result<Self> {
|
||||
let whitespace_tokenizer = tokenizer::WhitespaceTokenizer
|
||||
.filter(LowerCaser);
|
||||
|
||||
@@ -94,7 +95,7 @@ impl Searcher {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open(path: &AsRef<Path>) -> Result<Self, SearcherError> {
|
||||
pub fn open(path: &AsRef<Path>) -> Result<Self> {
|
||||
let whitespace_tokenizer = tokenizer::WhitespaceTokenizer
|
||||
.filter(LowerCaser);
|
||||
|
||||
@@ -121,7 +122,7 @@ impl Searcher {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_document(&self, conn: &Connection, post: &Post) {
|
||||
pub fn add_document(&self, conn: &Connection, post: &Post) -> Result<()> {
|
||||
let schema = self.index.schema();
|
||||
|
||||
let post_id = schema.get_field("post_id").unwrap();
|
||||
@@ -142,18 +143,19 @@ impl Searcher {
|
||||
let mut writer = self.writer.lock().unwrap();
|
||||
let writer = writer.as_mut().unwrap();
|
||||
writer.add_document(doc!(
|
||||
post_id => i64::from(post.id),
|
||||
author => post.get_authors(conn).into_iter().map(|u| u.get_fqn(conn)).join(" "),
|
||||
creation_date => i64::from(post.creation_date.num_days_from_ce()),
|
||||
instance => Instance::get(conn, post.get_blog(conn).instance_id).unwrap().public_domain.clone(),
|
||||
tag => Tag::for_post(conn, post.id).into_iter().map(|t| t.tag).join(" "),
|
||||
blog_name => post.get_blog(conn).title,
|
||||
content => post.content.get().clone(),
|
||||
subtitle => post.subtitle.clone(),
|
||||
title => post.title.clone(),
|
||||
lang => detect_lang(post.content.get()).and_then(|i| if i.is_reliable() { Some(i.lang()) } else {None} ).unwrap_or(Lang::Eng).name(),
|
||||
license => post.license.clone(),
|
||||
));
|
||||
post_id => i64::from(post.id),
|
||||
author => post.get_authors(conn)?.into_iter().map(|u| u.get_fqn(conn)).join(" "),
|
||||
creation_date => i64::from(post.creation_date.num_days_from_ce()),
|
||||
instance => Instance::get(conn, post.get_blog(conn)?.instance_id)?.public_domain.clone(),
|
||||
tag => Tag::for_post(conn, post.id)?.into_iter().map(|t| t.tag).join(" "),
|
||||
blog_name => post.get_blog(conn)?.title,
|
||||
content => post.content.get().clone(),
|
||||
subtitle => post.subtitle.clone(),
|
||||
title => post.title.clone(),
|
||||
lang => detect_lang(post.content.get()).and_then(|i| if i.is_reliable() { Some(i.lang()) } else {None} ).unwrap_or(Lang::Eng).name(),
|
||||
license => post.license.clone(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_document(&self, post: &Post) {
|
||||
@@ -166,9 +168,9 @@ impl Searcher {
|
||||
writer.delete_term(doc_id);
|
||||
}
|
||||
|
||||
pub fn update_document(&self, conn: &Connection, post: &Post) {
|
||||
pub fn update_document(&self, conn: &Connection, post: &Post) -> Result<()> {
|
||||
self.delete_document(post);
|
||||
self.add_document(conn, post);
|
||||
self.add_document(conn, post)
|
||||
}
|
||||
|
||||
pub fn search_document(&self, conn: &Connection, query: PlumeQuery, (min, max): (i32, i32)) -> Vec<Post>{
|
||||
@@ -185,9 +187,9 @@ impl Searcher {
|
||||
.filter_map(|doc_add| {
|
||||
let doc = searcher.doc(*doc_add).ok()?;
|
||||
let id = doc.get_first(post_id)?;
|
||||
Post::get(conn, id.i64_value() as i32)
|
||||
//borrow checker don't want me to use filter_map or and_then here
|
||||
})
|
||||
Post::get(conn, id.i64_value() as i32).ok()
|
||||
//borrow checker don't want me to use filter_map or and_then here
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
+16
-21
@@ -3,7 +3,7 @@ use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
|
||||
use instance::Instance;
|
||||
use plume_common::activity_pub::Hashtag;
|
||||
use schema::tags;
|
||||
use {ap_url, Connection};
|
||||
use {ap_url, Connection, Error, Result};
|
||||
|
||||
#[derive(Clone, Identifiable, Serialize, Queryable)]
|
||||
pub struct Tag {
|
||||
@@ -27,48 +27,43 @@ impl Tag {
|
||||
find_by!(tags, find_by_name, tag as &str);
|
||||
list_by!(tags, for_post, post_id as i32);
|
||||
|
||||
pub fn to_activity(&self, conn: &Connection) -> Hashtag {
|
||||
pub fn to_activity(&self, conn: &Connection) -> Result<Hashtag> {
|
||||
let mut ht = Hashtag::default();
|
||||
ht.set_href_string(ap_url(&format!(
|
||||
"{}/tag/{}",
|
||||
Instance::get_local(conn)
|
||||
.expect("Tag::to_activity: local instance not found error")
|
||||
.public_domain,
|
||||
Instance::get_local(conn)?.public_domain,
|
||||
self.tag
|
||||
))).expect("Tag::to_activity: href error");
|
||||
ht.set_name_string(self.tag.clone())
|
||||
.expect("Tag::to_activity: name error");
|
||||
ht
|
||||
)))?;
|
||||
ht.set_name_string(self.tag.clone())?;
|
||||
Ok(ht)
|
||||
}
|
||||
|
||||
pub fn from_activity(conn: &Connection, tag: &Hashtag, post: i32, is_hashtag: bool) -> Tag {
|
||||
pub fn from_activity(conn: &Connection, tag: &Hashtag, post: i32, is_hashtag: bool) -> Result<Tag> {
|
||||
Tag::insert(
|
||||
conn,
|
||||
NewTag {
|
||||
tag: tag.name_string().expect("Tag::from_activity: name error"),
|
||||
tag: tag.name_string()?,
|
||||
is_hashtag,
|
||||
post_id: post,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn build_activity(conn: &Connection, tag: String) -> Hashtag {
|
||||
pub fn build_activity(conn: &Connection, tag: String) -> Result<Hashtag> {
|
||||
let mut ht = Hashtag::default();
|
||||
ht.set_href_string(ap_url(&format!(
|
||||
"{}/tag/{}",
|
||||
Instance::get_local(conn)
|
||||
.expect("Tag::to_activity: local instance not found error")
|
||||
.public_domain,
|
||||
Instance::get_local(conn)?.public_domain,
|
||||
tag
|
||||
))).expect("Tag::to_activity: href error");
|
||||
ht.set_name_string(tag)
|
||||
.expect("Tag::to_activity: name error");
|
||||
ht
|
||||
)))?;
|
||||
ht.set_name_string(tag)?;
|
||||
Ok(ht)
|
||||
}
|
||||
|
||||
pub fn delete(&self, conn: &Connection) {
|
||||
pub fn delete(&self, conn: &Connection) -> Result<()> {
|
||||
diesel::delete(self)
|
||||
.execute(conn)
|
||||
.expect("Tag::delete: database error");
|
||||
.map(|_| ())
|
||||
.map_err(Error::from)
|
||||
}
|
||||
}
|
||||
|
||||
+264
-384
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user