mirror of https://github.com/mastodon/flodgatt
Complete error handling for Events
This commit is contained in:
parent
638364883f
commit
b4488d14be
|
@ -453,7 +453,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flodgatt"
|
name = "flodgatt"
|
||||||
version = "0.8.0"
|
version = "0.8.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"dotenv 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"dotenv 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
|
@ -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.8.0"
|
version = "0.8.1"
|
||||||
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"
|
||||||
|
|
||||||
|
|
|
@ -35,20 +35,9 @@ impl From<ReceiverErr> for FatalErr {
|
||||||
Self::ReceiverErr(e)
|
Self::ReceiverErr(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn die_with_msg2(msg: impl fmt::Display) {
|
|
||||||
eprintln!("{}", msg);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// TODO delete vvvv when postgres_cfg.rs has better error handling
|
||||||
pub fn die_with_msg(msg: impl fmt::Display) -> ! {
|
pub fn die_with_msg(msg: impl fmt::Display) -> ! {
|
||||||
eprintln!("FATAL ERROR: {}", msg);
|
eprintln!("FATAL ERROR: {}", msg);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! log_fatal {
|
|
||||||
($str:expr, $var:expr) => {{
|
|
||||||
log::error!($str, $var);
|
|
||||||
panic!();
|
|
||||||
};};
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,10 +2,12 @@ use std::fmt;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum TimelineErr {
|
pub enum TimelineErr {
|
||||||
RedisNamespaceMismatch,
|
MissingHashtag,
|
||||||
InvalidInput,
|
InvalidInput,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for TimelineErr {}
|
||||||
|
|
||||||
impl From<std::num::ParseIntError> for TimelineErr {
|
impl From<std::num::ParseIntError> for TimelineErr {
|
||||||
fn from(_error: std::num::ParseIntError) -> Self {
|
fn from(_error: std::num::ParseIntError) -> Self {
|
||||||
Self::InvalidInput
|
Self::InvalidInput
|
||||||
|
@ -16,8 +18,8 @@ impl fmt::Display for TimelineErr {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
use TimelineErr::*;
|
use TimelineErr::*;
|
||||||
let msg = match self {
|
let msg = match self {
|
||||||
RedisNamespaceMismatch => "TODO: Cut this error",
|
InvalidInput => "The timeline text from Redis could not be parsed into a supported timeline. TODO: add incoming timeline text",
|
||||||
InvalidInput => "The timeline text from Redis could not be parsed into a supported timeline. TODO: add incoming timeline text"
|
MissingHashtag => "Attempted to send a hashtag timeline without supplying a tag name",
|
||||||
};
|
};
|
||||||
write!(f, "{}", msg)
|
write!(f, "{}", msg)
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,7 @@ fn main() -> Result<(), FatalErr> {
|
||||||
log::info!("Incoming websocket request for {:?}", subscription.timeline);
|
log::info!("Incoming websocket request for {:?}", subscription.timeline);
|
||||||
{
|
{
|
||||||
let mut receiver = ws_receiver.lock().unwrap_or_else(Receiver::recover);
|
let mut receiver = ws_receiver.lock().unwrap_or_else(Receiver::recover);
|
||||||
|
|
||||||
receiver.subscribe(&subscription).unwrap_or_else(|e| {
|
receiver.subscribe(&subscription).unwrap_or_else(|e| {
|
||||||
log::error!("Could not subscribe to the Redis channel: {}", e)
|
log::error!("Could not subscribe to the Redis channel: {}", e)
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,6 +37,12 @@ impl FromStr for Id {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Id {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Serialize for Id {
|
impl Serialize for Id {
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
use crate::parse_client_request::Blocks;
|
|
||||||
use hashbrown::HashSet;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::Value;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
|
|
||||||
pub struct DynamicEvent {
|
|
||||||
pub event: String,
|
|
||||||
pub payload: Value,
|
|
||||||
queued_at: Option<i64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DynamicEvent {
|
|
||||||
/// Returns `true` if the status is filtered out based on its language
|
|
||||||
pub fn language_not(&self, allowed_langs: &HashSet<String>) -> bool {
|
|
||||||
const ALLOW: bool = false;
|
|
||||||
const REJECT: bool = true;
|
|
||||||
|
|
||||||
if allowed_langs.is_empty() {
|
|
||||||
return ALLOW; // listing no allowed_langs results in allowing all languages
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.payload["language"].as_str() {
|
|
||||||
Some(toot_language) if allowed_langs.contains(toot_language) => ALLOW,
|
|
||||||
None => ALLOW, // If toot language is unknown, toot is always allowed
|
|
||||||
Some(empty) if empty == String::new() => ALLOW,
|
|
||||||
Some(_toot_language) => REJECT,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Returns `true` if the toot contained in this Event originated from a blocked domain,
|
|
||||||
/// is from an account that has blocked the current user, or if the User's list of
|
|
||||||
/// blocked/muted users includes a user involved in the toot.
|
|
||||||
///
|
|
||||||
/// A user is involved in the toot if they:
|
|
||||||
/// * Are mentioned in this toot
|
|
||||||
/// * Wrote this toot
|
|
||||||
/// * Wrote a toot that this toot is replying to (if any)
|
|
||||||
/// * Wrote the toot that this toot is boosting (if any)
|
|
||||||
pub fn involves_any(&self, blocks: &Blocks) -> bool {
|
|
||||||
const ALLOW: bool = false;
|
|
||||||
const REJECT: bool = true;
|
|
||||||
let Blocks {
|
|
||||||
blocked_users,
|
|
||||||
blocking_users,
|
|
||||||
blocked_domains,
|
|
||||||
} = blocks;
|
|
||||||
|
|
||||||
let id = self.payload["account"]["id"].as_str().expect("TODO");
|
|
||||||
let username = self.payload["account"]["acct"].as_str().expect("TODO");
|
|
||||||
|
|
||||||
if self.involves(blocked_users) || blocking_users.contains(&id.parse().expect("TODO")) {
|
|
||||||
REJECT
|
|
||||||
} else {
|
|
||||||
let full_username = &username;
|
|
||||||
match full_username.split('@').nth(1) {
|
|
||||||
Some(originating_domain) if blocked_domains.contains(originating_domain) => REJECT,
|
|
||||||
Some(_) | None => ALLOW, // None means the local instance, which can't be blocked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// involved_users = mentioned_users + author + replied-to user + boosted user
|
|
||||||
fn involves(&self, blocked_users: &HashSet<i64>) -> bool {
|
|
||||||
// mentions
|
|
||||||
let mentions = self.payload["mentions"].as_array().expect("TODO");
|
|
||||||
let mut involved_users: HashSet<i64> = mentions
|
|
||||||
.iter()
|
|
||||||
.map(|mention| mention["id"].as_str().expect("TODO").parse().expect("TODO"))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// author
|
|
||||||
let author_id = self.payload["account"]["id"].as_str().expect("TODO");
|
|
||||||
involved_users.insert(author_id.parse::<i64>().expect("TODO"));
|
|
||||||
// replied-to user
|
|
||||||
let replied_to_user = self.payload["in_reply_to_account_id"].as_str();
|
|
||||||
if let Some(user_id) = replied_to_user {
|
|
||||||
involved_users.insert(user_id.parse().expect("TODO"));
|
|
||||||
}
|
|
||||||
// boosted user
|
|
||||||
let id_of_boosted_user = self.payload["reblog"]["account"]["id"].as_str();
|
|
||||||
if let Some(user_id) = id_of_boosted_user {
|
|
||||||
involved_users.insert(user_id.parse().expect("TODO"));
|
|
||||||
}
|
|
||||||
|
|
||||||
!involved_users.is_disjoint(blocked_users)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -21,6 +21,7 @@ pub enum EventKind {
|
||||||
Update(DynStatus),
|
Update(DynStatus),
|
||||||
NonUpdate,
|
NonUpdate,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for EventKind {
|
impl Default for EventKind {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::NonUpdate
|
Self::NonUpdate
|
||||||
|
@ -35,10 +36,9 @@ pub struct DynStatus {
|
||||||
pub mentioned_users: HashSet<Id>,
|
pub mentioned_users: HashSet<Id>,
|
||||||
pub replied_to_user: Option<Id>,
|
pub replied_to_user: Option<Id>,
|
||||||
pub boosted_user: Option<Id>,
|
pub boosted_user: Option<Id>,
|
||||||
pub payload: Value,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, EventErr>; // TODO cut if not used more than once
|
type Result<T> = std::result::Result<T, EventErr>;
|
||||||
|
|
||||||
impl DynEvent {
|
impl DynEvent {
|
||||||
pub fn set_update(self) -> Result<Self> {
|
pub fn set_update(self) -> Result<Self> {
|
||||||
|
@ -65,7 +65,6 @@ impl DynStatus {
|
||||||
mentioned_users: HashSet::new(),
|
mentioned_users: HashSet::new(),
|
||||||
replied_to_user: Id::try_from(&payload["in_reply_to_account_id"]).ok(),
|
replied_to_user: Id::try_from(&payload["in_reply_to_account_id"]).ok(),
|
||||||
boosted_user: Id::try_from(&payload["reblog"]["account"]["id"]).ok(),
|
boosted_user: Id::try_from(&payload["reblog"]["account"]["id"]).ok(),
|
||||||
payload,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/// Returns `true` if the status is filtered out based on its language
|
/// Returns `true` if the status is filtered out based on its language
|
||||||
|
|
|
@ -8,7 +8,6 @@ pub use {
|
||||||
err::EventErr,
|
err::EventErr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::log_fatal;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::{convert::TryFrom, string::String};
|
use std::{convert::TryFrom, string::String};
|
||||||
|
|
||||||
|
@ -26,8 +25,7 @@ impl Event {
|
||||||
Some(payload) => SendableEvent::WithPayload { event, payload },
|
Some(payload) => SendableEvent::WithPayload { event, payload },
|
||||||
None => SendableEvent::NoPayload { event },
|
None => SendableEvent::NoPayload { event },
|
||||||
};
|
};
|
||||||
serde_json::to_string(&sendable_event)
|
serde_json::to_string(&sendable_event).expect("Guaranteed: SendableEvent is Serialize")
|
||||||
.unwrap_or_else(|_| log_fatal!("Could not serialize `{:?}`", &sendable_event))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn event_name(&self) -> String {
|
pub fn event_name(&self) -> String {
|
||||||
|
@ -47,7 +45,7 @@ impl Event {
|
||||||
..
|
..
|
||||||
}) => "update",
|
}) => "update",
|
||||||
Self::Dynamic(DynEvent { event, .. }) => event,
|
Self::Dynamic(DynEvent { event, .. }) => event,
|
||||||
Self::Ping => panic!("event_name() called on EventNotReady"),
|
Self::Ping => panic!("event_name() called on Ping"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +63,7 @@ impl Event {
|
||||||
FiltersChanged => None,
|
FiltersChanged => None,
|
||||||
},
|
},
|
||||||
Self::Dynamic(DynEvent { payload, .. }) => Some(payload.to_string()),
|
Self::Dynamic(DynEvent { payload, .. }) => Some(payload.to_string()),
|
||||||
Self::Ping => panic!("payload() called on EventNotReady"),
|
Self::Ping => panic!("payload() called on Ping"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,6 +103,5 @@ enum SendableEvent<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn escaped<T: Serialize + std::fmt::Debug>(content: T) -> String {
|
fn escaped<T: Serialize + std::fmt::Debug>(content: T) -> String {
|
||||||
serde_json::to_string(&content)
|
serde_json::to_string(&content).expect("Guaranteed by Serialize trait bound")
|
||||||
.unwrap_or_else(|_| log_fatal!("Could not parse Event with: `{:?}`", &content))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,17 +6,15 @@
|
||||||
// #[cfg(not(test))]
|
// #[cfg(not(test))]
|
||||||
|
|
||||||
use super::postgres::PgPool;
|
use super::postgres::PgPool;
|
||||||
|
use super::query;
|
||||||
use super::query::Query;
|
use super::query::Query;
|
||||||
use crate::err::TimelineErr;
|
use crate::err::TimelineErr;
|
||||||
use crate::log_fatal;
|
|
||||||
use crate::messages::Id;
|
use crate::messages::Id;
|
||||||
|
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use lru::LruCache;
|
use lru::LruCache;
|
||||||
use uuid::Uuid;
|
use warp::{filters::BoxedFilter, path, reject::Rejection, Filter};
|
||||||
use warp::reject::Rejection;
|
|
||||||
|
|
||||||
use super::query;
|
|
||||||
use warp::{filters::BoxedFilter, path, Filter};
|
|
||||||
|
|
||||||
/// Helper macro to match on the first of any of the provided filters
|
/// Helper macro to match on the first of any of the provided filters
|
||||||
macro_rules! any_of {
|
macro_rules! any_of {
|
||||||
|
@ -52,7 +50,6 @@ macro_rules! parse_sse_query {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct Subscription {
|
pub struct Subscription {
|
||||||
pub id: Uuid,
|
|
||||||
pub timeline: Timeline,
|
pub timeline: Timeline,
|
||||||
pub allowed_langs: HashSet<String>,
|
pub allowed_langs: HashSet<String>,
|
||||||
pub blocks: Blocks,
|
pub blocks: Blocks,
|
||||||
|
@ -70,7 +67,6 @@ pub struct Blocks {
|
||||||
impl Default for Subscription {
|
impl Default for Subscription {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: Uuid::new_v4(),
|
|
||||||
timeline: Timeline(Stream::Unset, Reach::Local, Content::Notification),
|
timeline: Timeline(Stream::Unset, Reach::Local, Content::Notification),
|
||||||
allowed_langs: HashSet::new(),
|
allowed_langs: HashSet::new(),
|
||||||
blocks: Blocks::default(),
|
blocks: Blocks::default(),
|
||||||
|
@ -134,7 +130,6 @@ impl Subscription {
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Subscription {
|
Ok(Subscription {
|
||||||
id: Uuid::new_v4(),
|
|
||||||
timeline,
|
timeline,
|
||||||
allowed_langs: user.allowed_langs,
|
allowed_langs: user.allowed_langs,
|
||||||
blocks: Blocks {
|
blocks: Blocks {
|
||||||
|
@ -183,30 +178,28 @@ impl Timeline {
|
||||||
Self(Unset, Local, Notification)
|
Self(Unset, Local, Notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_redis_raw_timeline(&self, hashtag: Option<&String>) -> String {
|
pub fn to_redis_raw_timeline(&self, hashtag: Option<&String>) -> Result<String, TimelineErr> {
|
||||||
use {Content::*, Reach::*, Stream::*};
|
use {Content::*, Reach::*, Stream::*};
|
||||||
match self {
|
Ok(match self {
|
||||||
Timeline(Public, Federated, All) => "timeline:public".into(),
|
Timeline(Public, Federated, All) => "timeline:public".into(),
|
||||||
Timeline(Public, Local, All) => "timeline:public:local".into(),
|
Timeline(Public, Local, All) => "timeline:public:local".into(),
|
||||||
Timeline(Public, Federated, Media) => "timeline:public:media".into(),
|
Timeline(Public, Federated, Media) => "timeline:public:media".into(),
|
||||||
Timeline(Public, Local, Media) => "timeline:public:local:media".into(),
|
Timeline(Public, Local, Media) => "timeline:public:local:media".into(),
|
||||||
|
|
||||||
Timeline(Hashtag(id), Federated, All) => format!(
|
Timeline(Hashtag(_id), Federated, All) => format!(
|
||||||
"timeline:hashtag:{}",
|
"timeline:hashtag:{}",
|
||||||
hashtag.unwrap_or_else(|| log_fatal!("Did not supply a name for hashtag #{}", id))
|
hashtag.ok_or_else(|| TimelineErr::MissingHashtag)?
|
||||||
),
|
),
|
||||||
Timeline(Hashtag(id), Local, All) => format!(
|
Timeline(Hashtag(_id), Local, All) => format!(
|
||||||
"timeline:hashtag:{}:local",
|
"timeline:hashtag:{}:local",
|
||||||
hashtag.unwrap_or_else(|| log_fatal!("Did not supply a name for hashtag #{}", id))
|
hashtag.ok_or_else(|| TimelineErr::MissingHashtag)?
|
||||||
),
|
),
|
||||||
Timeline(User(id), Federated, All) => format!("timeline:{}", id),
|
Timeline(User(id), Federated, All) => format!("timeline:{}", id),
|
||||||
Timeline(User(id), Federated, Notification) => format!("timeline:{}:notification", id),
|
Timeline(User(id), Federated, Notification) => format!("timeline:{}:notification", id),
|
||||||
Timeline(List(id), Federated, All) => format!("timeline:list:{}", id),
|
Timeline(List(id), Federated, All) => format!("timeline:list:{}", id),
|
||||||
Timeline(Direct(id), Federated, All) => format!("timeline:direct:{}", id),
|
Timeline(Direct(id), Federated, All) => format!("timeline:direct:{}", id),
|
||||||
Timeline(one, _two, _three) => {
|
Timeline(_one, _two, _three) => Err(TimelineErr::InvalidInput)?,
|
||||||
log_fatal!("Supposedly impossible timeline reached: {:?}", one)
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_redis_text(
|
pub fn from_redis_text(
|
||||||
|
@ -226,10 +219,10 @@ impl Timeline {
|
||||||
["public", "local", "media"] => Timeline(Public, Local, Media),
|
["public", "local", "media"] => Timeline(Public, Local, Media),
|
||||||
["hashtag", tag] => Timeline(Hashtag(id_from_tag(tag)?), Federated, All),
|
["hashtag", tag] => Timeline(Hashtag(id_from_tag(tag)?), Federated, All),
|
||||||
["hashtag", tag, "local"] => Timeline(Hashtag(id_from_tag(tag)?), Local, All),
|
["hashtag", tag, "local"] => Timeline(Hashtag(id_from_tag(tag)?), Local, All),
|
||||||
[id] => Timeline(User(id.parse().unwrap()), Federated, All),
|
[id] => Timeline(User(id.parse()?), Federated, All),
|
||||||
[id, "notification"] => Timeline(User(id.parse().unwrap()), Federated, Notification),
|
[id, "notification"] => Timeline(User(id.parse()?), Federated, Notification),
|
||||||
["list", id] => Timeline(List(id.parse().unwrap()), Federated, All),
|
["list", id] => Timeline(List(id.parse()?), Federated, All),
|
||||||
["direct", id] => Timeline(Direct(id.parse().unwrap()), Federated, All),
|
["direct", id] => Timeline(Direct(id.parse()?), Federated, All),
|
||||||
// Other endpoints don't exist:
|
// Other endpoints don't exist:
|
||||||
[..] => Err(TimelineErr::InvalidInput)?,
|
[..] => Err(TimelineErr::InvalidInput)?,
|
||||||
})
|
})
|
||||||
|
@ -255,11 +248,11 @@ impl Timeline {
|
||||||
"hashtag" => Timeline(Hashtag(id_from_hashtag()?), Federated, All),
|
"hashtag" => Timeline(Hashtag(id_from_hashtag()?), Federated, All),
|
||||||
"hashtag:local" => Timeline(Hashtag(id_from_hashtag()?), Local, All),
|
"hashtag:local" => Timeline(Hashtag(id_from_hashtag()?), Local, All),
|
||||||
"user" => match user.scopes.contains(&Statuses) {
|
"user" => match user.scopes.contains(&Statuses) {
|
||||||
true => Timeline(User(*user.id), Federated, All),
|
true => Timeline(User(user.id), Federated, All),
|
||||||
false => Err(custom("Error: Missing access token"))?,
|
false => Err(custom("Error: Missing access token"))?,
|
||||||
},
|
},
|
||||||
"user:notification" => match user.scopes.contains(&Statuses) {
|
"user:notification" => match user.scopes.contains(&Statuses) {
|
||||||
true => Timeline(User(*user.id), Federated, Notification),
|
true => Timeline(User(user.id), Federated, Notification),
|
||||||
false => Err(custom("Error: Missing access token"))?,
|
false => Err(custom("Error: Missing access token"))?,
|
||||||
},
|
},
|
||||||
"list" => match user.scopes.contains(&Lists) && user_owns_list() {
|
"list" => match user.scopes.contains(&Lists) && user_owns_list() {
|
||||||
|
@ -280,7 +273,8 @@ impl Timeline {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Copy, Eq, Hash, PartialEq)]
|
#[derive(Clone, Debug, Copy, Eq, Hash, PartialEq)]
|
||||||
pub enum Stream {
|
pub enum Stream {
|
||||||
User(i64),
|
User(Id),
|
||||||
|
// TODO consider whether List, Direct, and Hashtag should all be `id::Id`s
|
||||||
List(i64),
|
List(i64),
|
||||||
Direct(i64),
|
Direct(i64),
|
||||||
Hashtag(i64),
|
Hashtag(i64),
|
||||||
|
|
|
@ -133,7 +133,7 @@ impl SseStream {
|
||||||
.filter_map(move |(timeline, event)| {
|
.filter_map(move |(timeline, event)| {
|
||||||
if target_timeline == timeline {
|
if target_timeline == timeline {
|
||||||
use crate::messages::{
|
use crate::messages::{
|
||||||
CheckedEvent, CheckedEvent::Update, Event::*, EventKind,
|
CheckedEvent, CheckedEvent::Update, DynEvent, Event::*, EventKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::parse_client_request::Stream::Public;
|
use crate::parse_client_request::Stream::Public;
|
||||||
|
@ -148,13 +148,16 @@ impl SseStream {
|
||||||
},
|
},
|
||||||
TypeSafe(non_update) => Self::reply_with(Event::TypeSafe(non_update)),
|
TypeSafe(non_update) => Self::reply_with(Event::TypeSafe(non_update)),
|
||||||
Dynamic(dyn_event) => {
|
Dynamic(dyn_event) => {
|
||||||
if let EventKind::Update(s) = dyn_event.kind.clone() {
|
if let EventKind::Update(s) = dyn_event.kind {
|
||||||
match timeline {
|
match timeline {
|
||||||
Timeline(Public, _, _) if s.language_not(&allowed_langs) => {
|
Timeline(Public, _, _) if s.language_not(&allowed_langs) => {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
_ if s.involves_any(&blocks) => None,
|
_ if s.involves_any(&blocks) => None,
|
||||||
_ => Self::reply_with(Dynamic(dyn_event)),
|
_ => Self::reply_with(Dynamic(DynEvent {
|
||||||
|
kind: EventKind::Update(s),
|
||||||
|
..dyn_event
|
||||||
|
})),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
|
@ -59,7 +59,6 @@ impl Receiver {
|
||||||
|
|
||||||
pub fn subscribe(&mut self, subscription: &Subscription) -> Result<()> {
|
pub fn subscribe(&mut self, subscription: &Subscription) -> Result<()> {
|
||||||
let (tag, tl) = (subscription.hashtag_name.clone(), subscription.timeline);
|
let (tag, tl) = (subscription.hashtag_name.clone(), subscription.timeline);
|
||||||
|
|
||||||
if let (Some(hashtag), Timeline(Stream::Hashtag(id), _, _)) = (tag, tl) {
|
if let (Some(hashtag), Timeline(Stream::Hashtag(id), _, _)) = (tag, tl) {
|
||||||
self.redis_connection.update_cache(hashtag, id);
|
self.redis_connection.update_cache(hashtag, id);
|
||||||
};
|
};
|
||||||
|
@ -74,7 +73,6 @@ impl Receiver {
|
||||||
if *number_of_subscriptions == 1 {
|
if *number_of_subscriptions == 1 {
|
||||||
self.redis_connection.send_cmd(Subscribe, &tl)?
|
self.redis_connection.send_cmd(Subscribe, &tl)?
|
||||||
};
|
};
|
||||||
log::info!("Started stream for {:?}", tl);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::err::TimelineErr;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -8,6 +9,7 @@ pub enum RedisConnErr {
|
||||||
IncorrectPassword(String),
|
IncorrectPassword(String),
|
||||||
MissingPassword,
|
MissingPassword,
|
||||||
NotRedis(String),
|
NotRedis(String),
|
||||||
|
TimelineErr(TimelineErr),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RedisConnErr {
|
impl RedisConnErr {
|
||||||
|
@ -49,11 +51,18 @@ impl fmt::Display for RedisConnErr {
|
||||||
REDIS_PORT environmental variables and try again.",
|
REDIS_PORT environmental variables and try again.",
|
||||||
addr
|
addr
|
||||||
),
|
),
|
||||||
|
TimelineErr(inner) => format!("{}", inner),
|
||||||
};
|
};
|
||||||
write!(f, "{}", msg)
|
write!(f, "{}", msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<TimelineErr> for RedisConnErr {
|
||||||
|
fn from(e: TimelineErr) -> RedisConnErr {
|
||||||
|
RedisConnErr::TimelineErr(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<std::io::Error> for RedisConnErr {
|
impl From<std::io::Error> for RedisConnErr {
|
||||||
fn from(e: std::io::Error) -> RedisConnErr {
|
fn from(e: std::io::Error) -> RedisConnErr {
|
||||||
RedisConnErr::UnknownRedisErr(e)
|
RedisConnErr::UnknownRedisErr(e)
|
||||||
|
|
|
@ -166,8 +166,8 @@ impl RedisConn {
|
||||||
Timeline(Stream::Hashtag(id), _, _) => self.tag_name_cache.get(id),
|
Timeline(Stream::Hashtag(id), _, _) => self.tag_name_cache.get(id),
|
||||||
_non_hashtag_timeline => None,
|
_non_hashtag_timeline => None,
|
||||||
};
|
};
|
||||||
let tl = timeline.to_redis_raw_timeline(hashtag);
|
|
||||||
|
|
||||||
|
let tl = timeline.to_redis_raw_timeline(hashtag)?;
|
||||||
let (primary_cmd, secondary_cmd) = match cmd {
|
let (primary_cmd, secondary_cmd) = match cmd {
|
||||||
RedisCmd::Subscribe => (
|
RedisCmd::Subscribe => (
|
||||||
format!("*2\r\n$9\r\nsubscribe\r\n${}\r\n{}\r\n", tl.len(), tl),
|
format!("*2\r\n$9\r\nsubscribe\r\n${}\r\n{}\r\n", tl.len(), tl),
|
||||||
|
|
Loading…
Reference in New Issue