mirror of https://github.com/mastodon/flodgatt
Filter toots based on user and domain blocks (#89)
* Read user and domain blocks from Postgres This commit reads the blocks from pg and stores them in the User struct; it does not yet actually filter the responses. It also does not update the tests. * Update tests * Filter out toots involving blocked/muted users * Add support for domain blocks * Update test and bump version
This commit is contained in:
parent
ebe9aeccbc
commit
440d691b0f
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "flodgatt"
|
name = "flodgatt"
|
||||||
description = "A blazingly fast drop-in replacement for the Mastodon streaming api server"
|
description = "A blazingly fast drop-in replacement for the Mastodon streaming api server"
|
||||||
version = "0.4.8"
|
version = "0.5.0"
|
||||||
authors = ["Daniel Long Sockwell <daniel@codesections.com", "Julian Laubstein <contact@julianlaubstein.de>"]
|
authors = ["Daniel Long Sockwell <daniel@codesections.com", "Julian Laubstein <contact@julianlaubstein.de>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ fn main() {
|
||||||
|
|
||||||
let client_agent_sse = ClientAgent::blank(redis_cfg);
|
let client_agent_sse = ClientAgent::blank(redis_cfg);
|
||||||
let client_agent_ws = client_agent_sse.clone_with_shared_receiver();
|
let client_agent_ws = client_agent_sse.clone_with_shared_receiver();
|
||||||
let pg_pool = user::PostgresPool::new(postgres_cfg);
|
let pg_pool = user::PgPool::new(postgres_cfg);
|
||||||
|
|
||||||
log::warn!("Streaming server initialized and ready to accept connections");
|
log::warn!("Streaming server initialized and ready to accept connections");
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Filters for all the endpoints accessible for Server Sent Event updates
|
//! Filters for all the endpoints accessible for Server Sent Event updates
|
||||||
use super::{
|
use super::{
|
||||||
query::{self, Query},
|
query::{self, Query},
|
||||||
user::{PostgresPool, User},
|
user::{PgPool, User},
|
||||||
};
|
};
|
||||||
use warp::{filters::BoxedFilter, path, Filter};
|
use warp::{filters::BoxedFilter, path, Filter};
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -39,7 +39,7 @@ macro_rules! parse_query {
|
||||||
.boxed()
|
.boxed()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub fn extract_user_or_reject(pg_pool: PostgresPool) -> BoxedFilter<(User,)> {
|
pub fn extract_user_or_reject(pg_pool: PgPool) -> BoxedFilter<(User,)> {
|
||||||
any_of!(
|
any_of!(
|
||||||
parse_query!(
|
parse_query!(
|
||||||
path => "api" / "v1" / "streaming" / "user" / "notification"
|
path => "api" / "v1" / "streaming" / "user" / "notification"
|
||||||
|
@ -74,7 +74,7 @@ pub fn extract_user_or_reject(pg_pool: PostgresPool) -> BoxedFilter<(User,)> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::parse_client_request::user::{Filter, OauthScope, PostgresPool};
|
use crate::parse_client_request::user::{Blocks, Filter, OauthScope, PgPool};
|
||||||
|
|
||||||
macro_rules! test_public_endpoint {
|
macro_rules! test_public_endpoint {
|
||||||
($name:ident {
|
($name:ident {
|
||||||
|
@ -83,7 +83,7 @@ mod test {
|
||||||
}) => {
|
}) => {
|
||||||
#[test]
|
#[test]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
let mock_pg_pool = PostgresPool::new();
|
let mock_pg_pool = PgPool::new();
|
||||||
let user = warp::test::request()
|
let user = warp::test::request()
|
||||||
.path($path)
|
.path($path)
|
||||||
.filter(&extract_user_or_reject(mock_pg_pool))
|
.filter(&extract_user_or_reject(mock_pg_pool))
|
||||||
|
@ -101,7 +101,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
let path = format!("{}?access_token=TEST_USER", $path);
|
let path = format!("{}?access_token=TEST_USER", $path);
|
||||||
let mock_pg_pool = PostgresPool::new();
|
let mock_pg_pool = PgPool::new();
|
||||||
$(let path = format!("{}&{}", path, $query);)*
|
$(let path = format!("{}&{}", path, $query);)*
|
||||||
let user = warp::test::request()
|
let user = warp::test::request()
|
||||||
.path(&path)
|
.path(&path)
|
||||||
|
@ -127,7 +127,7 @@ mod test {
|
||||||
fn $name() {
|
fn $name() {
|
||||||
let path = format!("{}?access_token=INVALID", $path);
|
let path = format!("{}?access_token=INVALID", $path);
|
||||||
$(let path = format!("{}&{}", path, $query);)*
|
$(let path = format!("{}&{}", path, $query);)*
|
||||||
let mock_pg_pool = PostgresPool::new();
|
let mock_pg_pool = PgPool::new();
|
||||||
warp::test::request()
|
warp::test::request()
|
||||||
.path(&path)
|
.path(&path)
|
||||||
.filter(&extract_user_or_reject(mock_pg_pool))
|
.filter(&extract_user_or_reject(mock_pg_pool))
|
||||||
|
@ -146,7 +146,7 @@ mod test {
|
||||||
let path = $path;
|
let path = $path;
|
||||||
$(let path = format!("{}?{}", path, $query);)*
|
$(let path = format!("{}?{}", path, $query);)*
|
||||||
|
|
||||||
let mock_pg_pool = PostgresPool::new();
|
let mock_pg_pool = PgPool::new();
|
||||||
warp::test::request()
|
warp::test::request()
|
||||||
.path(&path)
|
.path(&path)
|
||||||
.header("Authorization", "Bearer: INVALID")
|
.header("Authorization", "Bearer: INVALID")
|
||||||
|
@ -165,7 +165,7 @@ mod test {
|
||||||
fn $name() {
|
fn $name() {
|
||||||
let path = $path;
|
let path = $path;
|
||||||
$(let path = format!("{}?{}", path, $query);)*
|
$(let path = format!("{}?{}", path, $query);)*
|
||||||
let mock_pg_pool = PostgresPool::new();
|
let mock_pg_pool = PgPool::new();
|
||||||
warp::test::request()
|
warp::test::request()
|
||||||
.path(&path)
|
.path(&path)
|
||||||
.filter(&extract_user_or_reject(mock_pg_pool))
|
.filter(&extract_user_or_reject(mock_pg_pool))
|
||||||
|
@ -180,7 +180,7 @@ mod test {
|
||||||
target_timeline: "public:media".to_string(),
|
target_timeline: "public:media".to_string(),
|
||||||
id: -1,
|
id: -1,
|
||||||
email: "".to_string(),
|
email: "".to_string(),
|
||||||
access_token: "no access token".to_string(),
|
access_token: "".to_string(),
|
||||||
langs: None,
|
langs: None,
|
||||||
scopes: OauthScope {
|
scopes: OauthScope {
|
||||||
all: false,
|
all: false,
|
||||||
|
@ -189,6 +189,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: false,
|
logged_in: false,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::Language,
|
filter: Filter::Language,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -198,7 +199,7 @@ mod test {
|
||||||
target_timeline: "public:media".to_string(),
|
target_timeline: "public:media".to_string(),
|
||||||
id: -1,
|
id: -1,
|
||||||
email: "".to_string(),
|
email: "".to_string(),
|
||||||
access_token: "no access token".to_string(),
|
access_token: "".to_string(),
|
||||||
langs: None,
|
langs: None,
|
||||||
scopes: OauthScope {
|
scopes: OauthScope {
|
||||||
all: false,
|
all: false,
|
||||||
|
@ -207,6 +208,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: false,
|
logged_in: false,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::Language,
|
filter: Filter::Language,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -216,7 +218,7 @@ mod test {
|
||||||
target_timeline: "public:local".to_string(),
|
target_timeline: "public:local".to_string(),
|
||||||
id: -1,
|
id: -1,
|
||||||
email: "".to_string(),
|
email: "".to_string(),
|
||||||
access_token: "no access token".to_string(),
|
access_token: "".to_string(),
|
||||||
langs: None,
|
langs: None,
|
||||||
scopes: OauthScope {
|
scopes: OauthScope {
|
||||||
all: false,
|
all: false,
|
||||||
|
@ -225,6 +227,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: false,
|
logged_in: false,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::Language,
|
filter: Filter::Language,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -234,7 +237,7 @@ mod test {
|
||||||
target_timeline: "public:local:media".to_string(),
|
target_timeline: "public:local:media".to_string(),
|
||||||
id: -1,
|
id: -1,
|
||||||
email: "".to_string(),
|
email: "".to_string(),
|
||||||
access_token: "no access token".to_string(),
|
access_token: "".to_string(),
|
||||||
langs: None,
|
langs: None,
|
||||||
scopes: OauthScope {
|
scopes: OauthScope {
|
||||||
all: false,
|
all: false,
|
||||||
|
@ -243,6 +246,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: false,
|
logged_in: false,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::Language,
|
filter: Filter::Language,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -252,7 +256,7 @@ mod test {
|
||||||
target_timeline: "public:local:media".to_string(),
|
target_timeline: "public:local:media".to_string(),
|
||||||
id: -1,
|
id: -1,
|
||||||
email: "".to_string(),
|
email: "".to_string(),
|
||||||
access_token: "no access token".to_string(),
|
access_token: "".to_string(),
|
||||||
langs: None,
|
langs: None,
|
||||||
scopes: OauthScope {
|
scopes: OauthScope {
|
||||||
all: false,
|
all: false,
|
||||||
|
@ -261,6 +265,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: false,
|
logged_in: false,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::Language,
|
filter: Filter::Language,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -270,7 +275,7 @@ mod test {
|
||||||
target_timeline: "hashtag:a".to_string(),
|
target_timeline: "hashtag:a".to_string(),
|
||||||
id: -1,
|
id: -1,
|
||||||
email: "".to_string(),
|
email: "".to_string(),
|
||||||
access_token: "no access token".to_string(),
|
access_token: "".to_string(),
|
||||||
langs: None,
|
langs: None,
|
||||||
scopes: OauthScope {
|
scopes: OauthScope {
|
||||||
all: false,
|
all: false,
|
||||||
|
@ -279,6 +284,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: false,
|
logged_in: false,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::Language,
|
filter: Filter::Language,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -288,7 +294,7 @@ mod test {
|
||||||
target_timeline: "hashtag:local:a".to_string(),
|
target_timeline: "hashtag:local:a".to_string(),
|
||||||
id: -1,
|
id: -1,
|
||||||
email: "".to_string(),
|
email: "".to_string(),
|
||||||
access_token: "no access token".to_string(),
|
access_token: "".to_string(),
|
||||||
langs: None,
|
langs: None,
|
||||||
scopes: OauthScope {
|
scopes: OauthScope {
|
||||||
all: false,
|
all: false,
|
||||||
|
@ -297,6 +303,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: false,
|
logged_in: false,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::Language,
|
filter: Filter::Language,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -316,6 +323,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: true,
|
logged_in: true,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::NoFilter,
|
filter: Filter::NoFilter,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -334,6 +342,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: true,
|
logged_in: true,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::Notification,
|
filter: Filter::Notification,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -352,6 +361,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: true,
|
logged_in: true,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::NoFilter,
|
filter: Filter::NoFilter,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -372,6 +382,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: true,
|
logged_in: true,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::NoFilter,
|
filter: Filter::NoFilter,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -448,7 +459,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "NotFound")]
|
#[should_panic(expected = "NotFound")]
|
||||||
fn nonexistant_endpoint() {
|
fn nonexistant_endpoint() {
|
||||||
let mock_pg_pool = PostgresPool::new();
|
let mock_pg_pool = PgPool::new();
|
||||||
warp::test::request()
|
warp::test::request()
|
||||||
.path("/api/v1/streaming/DOES_NOT_EXIST")
|
.path("/api/v1/streaming/DOES_NOT_EXIST")
|
||||||
.filter(&extract_user_or_reject(mock_pg_pool))
|
.filter(&extract_user_or_reject(mock_pg_pool))
|
||||||
|
|
|
@ -1,37 +1,40 @@
|
||||||
//! Mock Postgres connection (for use in unit testing)
|
//! Mock Postgres connection (for use in unit testing)
|
||||||
|
use super::{OauthScope, User};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PostgresPool;
|
pub struct PgPool;
|
||||||
impl PostgresPool {
|
impl PgPool {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query_for_user_data(
|
pub fn select_user(access_token: &str, _pg_pool: PgPool) -> Result<User, warp::reject::Rejection> {
|
||||||
access_token: &str,
|
let mut user = User::default();
|
||||||
_pg_pool: PostgresPool,
|
if access_token == "TEST_USER" {
|
||||||
) -> (i64, String, Option<Vec<String>>, Vec<String>) {
|
user.id = 1;
|
||||||
let (user_id, email, lang, scopes) = if access_token == "TEST_USER" {
|
user.logged_in = true;
|
||||||
(
|
user.access_token = "TEST_USER".to_string();
|
||||||
1,
|
user.email = "user@example.com".to_string();
|
||||||
"user@example.com".to_string(),
|
user.scopes = OauthScope::from(vec![
|
||||||
None,
|
"read".to_string(),
|
||||||
vec![
|
"write".to_string(),
|
||||||
"read".to_string(),
|
"follow".to_string(),
|
||||||
"write".to_string(),
|
]);
|
||||||
"follow".to_string(),
|
} else if access_token == "INVALID" {
|
||||||
],
|
return Err(warp::reject::custom("Error: Invalid access token"));
|
||||||
)
|
}
|
||||||
} else {
|
Ok(user)
|
||||||
(-1, "".to_string(), None, Vec::new())
|
|
||||||
};
|
|
||||||
(user_id, email, lang, scopes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query_list_owner(list_id: i64, _pg_pool: PostgresPool) -> Option<i64> {
|
pub fn select_user_blocks(_id: i64, _pg_pool: PgPool) -> HashSet<i64> {
|
||||||
match list_id {
|
HashSet::new()
|
||||||
1 => Some(1),
|
}
|
||||||
_ => None,
|
pub fn select_domain_blocks(_pg_pool: PgPool) -> HashSet<String> {
|
||||||
}
|
HashSet::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn user_owns_list(user_id: i64, list_id: i64, _pg_pool: PgPool) -> bool {
|
||||||
|
user_id == list_id
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,9 @@ mod mock_postgres;
|
||||||
use mock_postgres as postgres;
|
use mock_postgres as postgres;
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
mod postgres;
|
mod postgres;
|
||||||
pub use self::postgres::PostgresPool;
|
pub use self::postgres::PgPool;
|
||||||
use super::query::Query;
|
use super::query::Query;
|
||||||
|
use std::collections::HashSet;
|
||||||
use warp::reject::Rejection;
|
use warp::reject::Rejection;
|
||||||
|
|
||||||
/// The filters that can be applied to toots after they come from Redis
|
/// The filters that can be applied to toots after they come from Redis
|
||||||
|
@ -18,23 +19,10 @@ pub enum Filter {
|
||||||
}
|
}
|
||||||
impl Default for Filter {
|
impl Default for Filter {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Filter::NoFilter
|
Filter::Language
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The User (with data read from Postgres)
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
|
||||||
pub struct User {
|
|
||||||
pub target_timeline: String,
|
|
||||||
pub email: String, // We only use email for logging; we could cut it for performance
|
|
||||||
pub id: i64,
|
|
||||||
pub access_token: String,
|
|
||||||
pub scopes: OauthScope,
|
|
||||||
pub langs: Option<Vec<String>>,
|
|
||||||
pub logged_in: bool,
|
|
||||||
pub filter: Filter,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq)]
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
pub struct OauthScope {
|
pub struct OauthScope {
|
||||||
pub all: bool,
|
pub all: bool,
|
||||||
|
@ -58,51 +46,59 @@ impl From<Vec<String>> for OauthScope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug, PartialEq)]
|
||||||
|
pub struct Blocks {
|
||||||
|
pub domain_blocks: HashSet<String>,
|
||||||
|
pub user_blocks: HashSet<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The User (with data read from Postgres)
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct User {
|
||||||
|
pub target_timeline: String,
|
||||||
|
pub email: String, // We only use email for logging; we could cut it for performance
|
||||||
|
pub access_token: String, // We only need this once (to send back with the WS reply). Cut?
|
||||||
|
pub id: i64,
|
||||||
|
pub scopes: OauthScope,
|
||||||
|
pub langs: Option<Vec<String>>,
|
||||||
|
pub logged_in: bool,
|
||||||
|
pub filter: Filter,
|
||||||
|
pub blocks: Blocks,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for User {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
id: -1,
|
||||||
|
email: "".to_string(),
|
||||||
|
access_token: "".to_string(),
|
||||||
|
scopes: OauthScope::default(),
|
||||||
|
langs: None,
|
||||||
|
logged_in: false,
|
||||||
|
target_timeline: String::new(),
|
||||||
|
filter: Filter::default(),
|
||||||
|
blocks: Blocks::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
pub fn from_query(q: Query, pg_pool: PostgresPool) -> Result<Self, Rejection> {
|
pub fn from_query(q: Query, pool: PgPool) -> Result<Self, Rejection> {
|
||||||
let (id, access_token, email, scopes, langs, logged_in) = match q.access_token.clone() {
|
println!("Creating user...");
|
||||||
None => (
|
let mut user: User = match q.access_token.clone() {
|
||||||
-1,
|
None => User::default(),
|
||||||
"no access token".to_owned(),
|
Some(token) => postgres::select_user(&token, pool.clone())?,
|
||||||
"".to_string(),
|
|
||||||
OauthScope::default(),
|
|
||||||
None,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
Some(token) => {
|
|
||||||
let (id, email, langs, scope_list) =
|
|
||||||
postgres::query_for_user_data(&token, pg_pool.clone());
|
|
||||||
|
|
||||||
if id == -1 {
|
|
||||||
return Err(warp::reject::custom("Error: Invalid access token"));
|
|
||||||
}
|
|
||||||
let scopes = OauthScope::from(scope_list);
|
|
||||||
(id, token, email, scopes, langs, true)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mut user = User {
|
|
||||||
id,
|
|
||||||
email,
|
|
||||||
target_timeline: "PLACEHOLDER".to_string(),
|
|
||||||
access_token,
|
|
||||||
scopes,
|
|
||||||
langs,
|
|
||||||
logged_in,
|
|
||||||
filter: Filter::Language,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
user = user.update_timeline_and_filter(q, pg_pool.clone())?;
|
user = user.set_timeline_and_filter(q, pool.clone())?;
|
||||||
|
user.blocks.user_blocks = postgres::select_user_blocks(user.id, pool.clone());
|
||||||
|
user.blocks.domain_blocks = postgres::select_domain_blocks(pool.clone());
|
||||||
|
dbg!(&user);
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_timeline_and_filter(
|
fn set_timeline_and_filter(mut self, q: Query, pool: PgPool) -> Result<Self, Rejection> {
|
||||||
mut self,
|
|
||||||
q: Query,
|
|
||||||
pg_pool: PostgresPool,
|
|
||||||
) -> Result<Self, Rejection> {
|
|
||||||
let read_scope = self.scopes.clone();
|
let read_scope = self.scopes.clone();
|
||||||
|
|
||||||
let timeline = match q.stream.as_ref() {
|
let timeline = match q.stream.as_ref() {
|
||||||
// Public endpoints:
|
// Public endpoints:
|
||||||
tl @ "public" | tl @ "public:local" if q.media => format!("{}:media", tl),
|
tl @ "public" | tl @ "public:local" if q.media => format!("{}:media", tl),
|
||||||
|
@ -110,7 +106,7 @@ impl User {
|
||||||
tl @ "public" | tl @ "public:local" => tl.to_string(),
|
tl @ "public" | tl @ "public:local" => tl.to_string(),
|
||||||
// Hashtag endpoints:
|
// Hashtag endpoints:
|
||||||
tl @ "hashtag" | tl @ "hashtag:local" => format!("{}:{}", tl, q.hashtag),
|
tl @ "hashtag" | tl @ "hashtag:local" => format!("{}:{}", tl, q.hashtag),
|
||||||
// Private endpoints: User
|
// Private endpoints: User:
|
||||||
"user" if self.logged_in && (read_scope.all || read_scope.statuses) => {
|
"user" if self.logged_in && (read_scope.all || read_scope.statuses) => {
|
||||||
self.filter = Filter::NoFilter;
|
self.filter = Filter::NoFilter;
|
||||||
format!("{}", self.id)
|
format!("{}", self.id)
|
||||||
|
@ -120,7 +116,7 @@ impl User {
|
||||||
format!("{}", self.id)
|
format!("{}", self.id)
|
||||||
}
|
}
|
||||||
// List endpoint:
|
// List endpoint:
|
||||||
"list" if self.owns_list(q.list, pg_pool) && (read_scope.all || read_scope.lists) => {
|
"list" if self.owns_list(q.list, pool) && (read_scope.all || read_scope.lists) => {
|
||||||
self.filter = Filter::NoFilter;
|
self.filter = Filter::NoFilter;
|
||||||
format!("list:{}", q.list)
|
format!("list:{}", q.list)
|
||||||
}
|
}
|
||||||
|
@ -142,11 +138,7 @@ impl User {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine whether the User is authorised for a specified list
|
fn owns_list(&self, list: i64, pool: PgPool) -> bool {
|
||||||
pub fn owns_list(&self, list: i64, pg_pool: PostgresPool) -> bool {
|
postgres::user_owns_list(self.id, list, pool)
|
||||||
match postgres::query_list_owner(list, pg_pool) {
|
|
||||||
Some(i) if i == self.id => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
//! Postgres queries
|
//! Postgres queries
|
||||||
use crate::config;
|
use crate::{
|
||||||
|
config,
|
||||||
|
parse_client_request::user::{OauthScope, User},
|
||||||
|
};
|
||||||
use ::postgres;
|
use ::postgres;
|
||||||
use r2d2_postgres::PostgresConnectionManager;
|
use r2d2_postgres::PostgresConnectionManager;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use warp::reject::Rejection;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PostgresPool(pub r2d2::Pool<PostgresConnectionManager<postgres::NoTls>>);
|
pub struct PgPool(pub r2d2::Pool<PostgresConnectionManager<postgres::NoTls>>);
|
||||||
impl PostgresPool {
|
impl PgPool {
|
||||||
pub fn new(pg_cfg: config::PostgresConfig) -> Self {
|
pub fn new(pg_cfg: config::PostgresConfig) -> Self {
|
||||||
let mut cfg = postgres::Config::new();
|
let mut cfg = postgres::Config::new();
|
||||||
cfg.user(&pg_cfg.user)
|
cfg.user(&pg_cfg.user)
|
||||||
|
@ -25,12 +30,12 @@ impl PostgresPool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query_for_user_data(
|
/// Build a user based on the result of querying Postgres with the access token
|
||||||
access_token: &str,
|
///
|
||||||
pg_pool: PostgresPool,
|
/// This does _not_ set the timeline, filter, or blocks fields. Use the various `User`
|
||||||
) -> (i64, String, Option<Vec<String>>, Vec<String>) {
|
/// methods to do so. In general, this function shouldn't be needed outside `User`.
|
||||||
|
pub fn select_user(access_token: &str, pg_pool: PgPool) -> Result<User, Rejection> {
|
||||||
let mut conn = pg_pool.0.get().unwrap();
|
let mut conn = pg_pool.0.get().unwrap();
|
||||||
|
|
||||||
let query_result = conn
|
let query_result = conn
|
||||||
.query(
|
.query(
|
||||||
"
|
"
|
||||||
|
@ -45,41 +50,70 @@ LIMIT 1",
|
||||||
&[&access_token.to_owned()],
|
&[&access_token.to_owned()],
|
||||||
)
|
)
|
||||||
.expect("Hard-coded query will return Some([0 or more rows])");
|
.expect("Hard-coded query will return Some([0 or more rows])");
|
||||||
if !query_result.is_empty() {
|
if query_result.is_empty() {
|
||||||
|
Err(warp::reject::custom("Error: Invalid access token"))
|
||||||
|
} else {
|
||||||
let only_row: &postgres::Row = query_result.get(0).unwrap();
|
let only_row: &postgres::Row = query_result.get(0).unwrap();
|
||||||
let id: i64 = only_row.get(1);
|
let scope_vec: Vec<String> = only_row
|
||||||
let email: String = only_row.get(2);
|
|
||||||
let scopes = only_row
|
|
||||||
.get::<_, String>(4)
|
.get::<_, String>(4)
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.map(|s| s.to_owned())
|
.map(|s| s.to_owned())
|
||||||
.collect();
|
.collect();
|
||||||
let langs: Option<Vec<String>> = only_row.get(3);
|
Ok(User {
|
||||||
(id, email, langs, scopes)
|
id: only_row.get(1),
|
||||||
} else {
|
access_token: access_token.to_string(),
|
||||||
(-1, "".to_string(), None, Vec::new())
|
email: only_row.get(2),
|
||||||
|
logged_in: true,
|
||||||
|
scopes: OauthScope::from(scope_vec),
|
||||||
|
langs: only_row.get(3),
|
||||||
|
..User::default()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
/// Query Postgres for everyone the user has blocked or muted
|
||||||
pub fn query_for_user_data(access_token: &str) -> (i64, Option<Vec<String>>, Vec<String>) {
|
///
|
||||||
let (user_id, lang, scopes) = if access_token == "TEST_USER" {
|
/// **NOTE**: because we check this when the user connects, it will not include any blocks
|
||||||
(
|
/// the user adds until they refresh/reconnect.
|
||||||
1,
|
pub fn select_user_blocks(user_id: i64, pg_pool: PgPool) -> HashSet<i64> {
|
||||||
None,
|
pg_pool
|
||||||
vec![
|
.0
|
||||||
"read".to_string(),
|
.get()
|
||||||
"write".to_string(),
|
.unwrap()
|
||||||
"follow".to_string(),
|
.query(
|
||||||
],
|
"
|
||||||
|
SELECT target_account_id
|
||||||
|
FROM blocks
|
||||||
|
WHERE account_id = $1
|
||||||
|
UNION SELECT target_account_id
|
||||||
|
FROM mutes
|
||||||
|
WHERE account_id = $1",
|
||||||
|
&[&user_id],
|
||||||
)
|
)
|
||||||
} else {
|
.expect("Hard-coded query will return Some([0 or more rows])")
|
||||||
(-1, None, Vec::new())
|
.iter()
|
||||||
};
|
.map(|row| row.get(0))
|
||||||
(user_id, lang, scopes)
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query_list_owner(list_id: i64, pg_pool: PostgresPool) -> Option<i64> {
|
/// Query Postgres for all current domain blocks
|
||||||
|
///
|
||||||
|
/// **NOTE**: because we check this when the user connects, it will not include any blocks
|
||||||
|
/// the user adds until they refresh/reconnect.
|
||||||
|
pub fn select_domain_blocks(pg_pool: PgPool) -> HashSet<String> {
|
||||||
|
pg_pool
|
||||||
|
.0
|
||||||
|
.get()
|
||||||
|
.unwrap()
|
||||||
|
.query("SELECT domain FROM account_domain_blocks", &[])
|
||||||
|
.expect("Hard-coded query will return Some([0 or more rows])")
|
||||||
|
.iter()
|
||||||
|
.map(|row| row.get(0))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test whether a user owns a list
|
||||||
|
pub fn user_owns_list(user_id: i64, list_id: i64, pg_pool: PgPool) -> bool {
|
||||||
let mut conn = pg_pool.0.get().unwrap();
|
let mut conn = pg_pool.0.get().unwrap();
|
||||||
// For the Postgres query, `id` = list number; `account_id` = user.id
|
// For the Postgres query, `id` = list number; `account_id` = user.id
|
||||||
let rows = &conn
|
let rows = &conn
|
||||||
|
@ -92,9 +126,12 @@ LIMIT 1",
|
||||||
&[&list_id],
|
&[&list_id],
|
||||||
)
|
)
|
||||||
.expect("Hard-coded query will return Some([0 or more rows])");
|
.expect("Hard-coded query will return Some([0 or more rows])");
|
||||||
if rows.is_empty() {
|
|
||||||
None
|
match rows.get(0) {
|
||||||
} else {
|
None => false,
|
||||||
Some(rows.get(0).unwrap().get(1))
|
Some(row) => {
|
||||||
|
let list_owner_id: i64 = row.get(1);
|
||||||
|
list_owner_id == user_id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//! Filters for the WebSocket endpoint
|
//! Filters for the WebSocket endpoint
|
||||||
use super::{
|
use super::{
|
||||||
query::{self, Query},
|
query::{self, Query},
|
||||||
user::{PostgresPool, User},
|
user::{PgPool, User},
|
||||||
};
|
};
|
||||||
use warp::{filters::BoxedFilter, path, Filter};
|
use warp::{filters::BoxedFilter, path, Filter};
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ fn parse_query() -> BoxedFilter<(Query,)> {
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_user_or_reject(pg_pool: PostgresPool) -> BoxedFilter<(User,)> {
|
pub fn extract_user_or_reject(pg_pool: PgPool) -> BoxedFilter<(User,)> {
|
||||||
parse_query()
|
parse_query()
|
||||||
.and(query::OptionalAccessToken::from_ws_header())
|
.and(query::OptionalAccessToken::from_ws_header())
|
||||||
.and_then(Query::update_access_token)
|
.and_then(Query::update_access_token)
|
||||||
|
@ -43,7 +43,7 @@ pub fn extract_user_or_reject(pg_pool: PostgresPool) -> BoxedFilter<(User,)> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::parse_client_request::user::{Filter, OauthScope};
|
use crate::parse_client_request::user::{Blocks, Filter, OauthScope};
|
||||||
|
|
||||||
macro_rules! test_public_endpoint {
|
macro_rules! test_public_endpoint {
|
||||||
($name:ident {
|
($name:ident {
|
||||||
|
@ -52,7 +52,7 @@ mod test {
|
||||||
}) => {
|
}) => {
|
||||||
#[test]
|
#[test]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
let mock_pg_pool = PostgresPool::new();
|
let mock_pg_pool = PgPool::new();
|
||||||
let user = warp::test::request()
|
let user = warp::test::request()
|
||||||
.path($path)
|
.path($path)
|
||||||
.header("connection", "upgrade")
|
.header("connection", "upgrade")
|
||||||
|
@ -72,7 +72,7 @@ mod test {
|
||||||
}) => {
|
}) => {
|
||||||
#[test]
|
#[test]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
let mock_pg_pool = PostgresPool::new();
|
let mock_pg_pool = PgPool::new();
|
||||||
let path = format!("{}&access_token=TEST_USER", $path);
|
let path = format!("{}&access_token=TEST_USER", $path);
|
||||||
let user = warp::test::request()
|
let user = warp::test::request()
|
||||||
.path(&path)
|
.path(&path)
|
||||||
|
@ -96,7 +96,7 @@ mod test {
|
||||||
|
|
||||||
fn $name() {
|
fn $name() {
|
||||||
let path = format!("{}&access_token=INVALID", $path);
|
let path = format!("{}&access_token=INVALID", $path);
|
||||||
let mock_pg_pool = PostgresPool::new();
|
let mock_pg_pool = PgPool::new();
|
||||||
warp::test::request()
|
warp::test::request()
|
||||||
.path(&path)
|
.path(&path)
|
||||||
.filter(&extract_user_or_reject(mock_pg_pool))
|
.filter(&extract_user_or_reject(mock_pg_pool))
|
||||||
|
@ -112,7 +112,7 @@ mod test {
|
||||||
#[should_panic(expected = "Error: Missing access token")]
|
#[should_panic(expected = "Error: Missing access token")]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
let path = $path;
|
let path = $path;
|
||||||
let mock_pg_pool = PostgresPool::new();
|
let mock_pg_pool = PgPool::new();
|
||||||
warp::test::request()
|
warp::test::request()
|
||||||
.path(&path)
|
.path(&path)
|
||||||
.filter(&extract_user_or_reject(mock_pg_pool))
|
.filter(&extract_user_or_reject(mock_pg_pool))
|
||||||
|
@ -127,7 +127,7 @@ mod test {
|
||||||
target_timeline: "public:media".to_string(),
|
target_timeline: "public:media".to_string(),
|
||||||
id: -1,
|
id: -1,
|
||||||
email: "".to_string(),
|
email: "".to_string(),
|
||||||
access_token: "no access token".to_string(),
|
access_token: "".to_string(),
|
||||||
langs: None,
|
langs: None,
|
||||||
scopes: OauthScope {
|
scopes: OauthScope {
|
||||||
all: false,
|
all: false,
|
||||||
|
@ -136,6 +136,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: false,
|
logged_in: false,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::Language,
|
filter: Filter::Language,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -145,7 +146,7 @@ mod test {
|
||||||
target_timeline: "public:local".to_string(),
|
target_timeline: "public:local".to_string(),
|
||||||
id: -1,
|
id: -1,
|
||||||
email: "".to_string(),
|
email: "".to_string(),
|
||||||
access_token: "no access token".to_string(),
|
access_token: "".to_string(),
|
||||||
langs: None,
|
langs: None,
|
||||||
scopes: OauthScope {
|
scopes: OauthScope {
|
||||||
all: false,
|
all: false,
|
||||||
|
@ -154,6 +155,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: false,
|
logged_in: false,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::Language,
|
filter: Filter::Language,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -163,7 +165,7 @@ mod test {
|
||||||
target_timeline: "public:local:media".to_string(),
|
target_timeline: "public:local:media".to_string(),
|
||||||
id: -1,
|
id: -1,
|
||||||
email: "".to_string(),
|
email: "".to_string(),
|
||||||
access_token: "no access token".to_string(),
|
access_token: "".to_string(),
|
||||||
langs: None,
|
langs: None,
|
||||||
scopes: OauthScope {
|
scopes: OauthScope {
|
||||||
all: false,
|
all: false,
|
||||||
|
@ -172,6 +174,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: false,
|
logged_in: false,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::Language,
|
filter: Filter::Language,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -181,7 +184,7 @@ mod test {
|
||||||
target_timeline: "hashtag:a".to_string(),
|
target_timeline: "hashtag:a".to_string(),
|
||||||
id: -1,
|
id: -1,
|
||||||
email: "".to_string(),
|
email: "".to_string(),
|
||||||
access_token: "no access token".to_string(),
|
access_token: "".to_string(),
|
||||||
langs: None,
|
langs: None,
|
||||||
scopes: OauthScope {
|
scopes: OauthScope {
|
||||||
all: false,
|
all: false,
|
||||||
|
@ -190,6 +193,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: false,
|
logged_in: false,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::Language,
|
filter: Filter::Language,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -199,7 +203,7 @@ mod test {
|
||||||
target_timeline: "hashtag:local:a".to_string(),
|
target_timeline: "hashtag:local:a".to_string(),
|
||||||
id: -1,
|
id: -1,
|
||||||
email: "".to_string(),
|
email: "".to_string(),
|
||||||
access_token: "no access token".to_string(),
|
access_token: "".to_string(),
|
||||||
langs: None,
|
langs: None,
|
||||||
scopes: OauthScope {
|
scopes: OauthScope {
|
||||||
all: false,
|
all: false,
|
||||||
|
@ -208,6 +212,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: false,
|
logged_in: false,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::Language,
|
filter: Filter::Language,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -227,6 +232,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: true,
|
logged_in: true,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::NoFilter,
|
filter: Filter::NoFilter,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -245,6 +251,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: true,
|
logged_in: true,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::Notification,
|
filter: Filter::Notification,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -263,6 +270,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: true,
|
logged_in: true,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::NoFilter,
|
filter: Filter::NoFilter,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -281,6 +289,7 @@ mod test {
|
||||||
lists: false,
|
lists: false,
|
||||||
},
|
},
|
||||||
logged_in: true,
|
logged_in: true,
|
||||||
|
blocks: Blocks::default(),
|
||||||
filter: Filter::NoFilter,
|
filter: Filter::NoFilter,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -325,7 +334,7 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "NotFound")]
|
#[should_panic(expected = "NotFound")]
|
||||||
fn nonexistant_endpoint() {
|
fn nonexistant_endpoint() {
|
||||||
let mock_pg_pool = PostgresPool::new();
|
let mock_pg_pool = PgPool::new();
|
||||||
warp::test::request()
|
warp::test::request()
|
||||||
.path("/api/v1/streaming/DOES_NOT_EXIST")
|
.path("/api/v1/streaming/DOES_NOT_EXIST")
|
||||||
.header("connection", "upgrade")
|
.header("connection", "upgrade")
|
||||||
|
|
|
@ -19,7 +19,7 @@ use super::receiver::Receiver;
|
||||||
use crate::{config, parse_client_request::user::User};
|
use crate::{config, parse_client_request::user::User};
|
||||||
use futures::{Async, Poll};
|
use futures::{Async, Poll};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::sync;
|
use std::{collections::HashSet, sync};
|
||||||
use tokio::io::Error;
|
use tokio::io::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -110,6 +110,7 @@ impl futures::stream::Stream for ClientAgent {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The message to send to the client (which might not literally be a toot in some cases).
|
/// The message to send to the client (which might not literally be a toot in some cases).
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Toot {
|
pub struct Toot {
|
||||||
pub category: String,
|
pub category: String,
|
||||||
pub payload: Value,
|
pub payload: Value,
|
||||||
|
@ -118,7 +119,7 @@ pub struct Toot {
|
||||||
|
|
||||||
impl Toot {
|
impl Toot {
|
||||||
/// Construct a `Toot` from well-formed JSON.
|
/// Construct a `Toot` from well-formed JSON.
|
||||||
fn from_json(value: Value) -> Self {
|
pub fn from_json(value: Value) -> Self {
|
||||||
let category = value["event"].as_str().expect("Redis string").to_owned();
|
let category = value["event"].as_str().expect("Redis string").to_owned();
|
||||||
let language = if category == "update" {
|
let language = if category == "update" {
|
||||||
Some(value["payload"]["language"].to_string())
|
Some(value["payload"]["language"].to_string())
|
||||||
|
@ -133,8 +134,44 @@ impl Toot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_originating_domain(&self) -> HashSet<String> {
|
||||||
|
let api = "originating Invariant Violation: JSON value does not conform to Mastdon API";
|
||||||
|
let mut originating_domain = HashSet::new();
|
||||||
|
originating_domain.insert(
|
||||||
|
self.payload["account"]["acct"]
|
||||||
|
.as_str()
|
||||||
|
.expect(&api)
|
||||||
|
.split("@")
|
||||||
|
.nth(1)
|
||||||
|
.expect(&api)
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
originating_domain
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_involved_users(&self) -> HashSet<i64> {
|
||||||
|
let mut involved_users: HashSet<i64> = HashSet::new();
|
||||||
|
let msg = self.payload.clone();
|
||||||
|
|
||||||
|
let api = "Invariant Violation: JSON value does not conform to Mastdon API";
|
||||||
|
involved_users.insert(msg["account"]["id"].str_to_i64().expect(&api));
|
||||||
|
if let Some(mentions) = msg["mentions"].as_array() {
|
||||||
|
for mention in mentions {
|
||||||
|
involved_users.insert(mention["id"].str_to_i64().expect(&api));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(replied_to_account) = msg["in_reply_to_account_id"].as_str() {
|
||||||
|
involved_users.insert(replied_to_account.parse().expect(&api));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(reblog) = msg["reblog"].as_object() {
|
||||||
|
involved_users.insert(reblog["account"]["id"].str_to_i64().expect(&api));
|
||||||
|
}
|
||||||
|
involved_users
|
||||||
|
}
|
||||||
|
|
||||||
/// Filter out any `Toot`'s that fail the provided filter.
|
/// Filter out any `Toot`'s that fail the provided filter.
|
||||||
fn filter(self, user: &User) -> Result<Async<Option<Self>>, Error> {
|
pub fn filter(self, user: &User) -> Result<Async<Option<Self>>, Error> {
|
||||||
let toot = self;
|
let toot = self;
|
||||||
|
|
||||||
let category = toot.category.clone();
|
let category = toot.category.clone();
|
||||||
|
@ -161,3 +198,17 @@ impl Toot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait ConvertValue {
|
||||||
|
fn str_to_i64(&self) -> Result<i64, Box<dyn std::error::Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConvertValue for Value {
|
||||||
|
fn str_to_i64(&self) -> Result<i64, Box<dyn std::error::Error>> {
|
||||||
|
Ok(self
|
||||||
|
.as_str()
|
||||||
|
.ok_or(format!("{} is not a string", &self))?
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| "Could not parse str")?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -82,29 +82,36 @@ pub fn send_updates_to_ws(
|
||||||
|
|
||||||
let mut time = time::Instant::now();
|
let mut time = time::Instant::now();
|
||||||
|
|
||||||
let (tl, email, id) = (
|
let (tl, email, id, blocked_users, blocked_domains) = (
|
||||||
client_agent.current_user.target_timeline.clone(),
|
client_agent.current_user.target_timeline.clone(),
|
||||||
client_agent.current_user.email.clone(),
|
client_agent.current_user.email.clone(),
|
||||||
client_agent.current_user.id,
|
client_agent.current_user.id,
|
||||||
|
client_agent.current_user.blocks.user_blocks.clone(),
|
||||||
|
client_agent.current_user.blocks.domain_blocks.clone(),
|
||||||
);
|
);
|
||||||
// Every time you get an event from that stream, send it through the pipe
|
// Every time you get an event from that stream, send it through the pipe
|
||||||
event_stream
|
event_stream
|
||||||
.for_each(move |_instant| {
|
.for_each(move |_instant| {
|
||||||
if let Ok(Async::Ready(Some(toot))) = client_agent.poll() {
|
if let Ok(Async::Ready(Some(toot))) = client_agent.poll() {
|
||||||
let txt = &toot.payload["content"];
|
if blocked_domains.is_disjoint(&toot.get_originating_domain())
|
||||||
log::warn!("toot: {}\n in TL: {}\nuser: {}({})", txt, tl, email, id);
|
&& blocked_users.is_disjoint(&toot.get_involved_users())
|
||||||
|
{
|
||||||
|
let txt = &toot.payload["content"];
|
||||||
|
log::warn!("toot: {}\nTL: {}\nUser: {}({})", txt, tl, email, id);
|
||||||
|
|
||||||
let msg = warp::ws::Message::text(
|
tx.unbounded_send(warp::ws::Message::text(
|
||||||
json!({"event": toot.category,
|
json!({ "event": toot.category,
|
||||||
"payload": toot.payload.to_string()})
|
"payload": &toot.payload.to_string() })
|
||||||
.to_string(),
|
.to_string(),
|
||||||
);
|
))
|
||||||
|
.expect("No send error");
|
||||||
tx.unbounded_send(msg).expect("No send error");
|
} else {
|
||||||
|
log::info!("Blocked a message to {}", email);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if time.elapsed() > time::Duration::from_secs(30) {
|
if time.elapsed() > time::Duration::from_secs(30) {
|
||||||
let msg = warp::ws::Message::text("{}");
|
tx.unbounded_send(warp::ws::Message::text("{}"))
|
||||||
tx.unbounded_send(msg).expect("Can ping");
|
.expect("Can ping");
|
||||||
time = time::Instant::now();
|
time = time::Instant::now();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in New Issue