diff --git a/Cargo.lock b/Cargo.lock index cefc49705..fe119382e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1113,12 +1113,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - [[package]] name = "dtoa" version = "0.4.6" @@ -1809,6 +1803,92 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lemmy_api" +version = "0.1.0" +dependencies = [ + "actix", + "actix-rt", + "actix-web", + "anyhow", + "async-trait", + "awc", + "background-jobs", + "base64 0.12.3", + "bcrypt", + "captcha", + "chrono", + "diesel", + "futures", + "http", + "http-signature-normalization-actix", + "itertools", + "jsonwebtoken", + "lazy_static", + "lemmy_apub", + "lemmy_db", + "lemmy_rate_limit", + "lemmy_structs", + "lemmy_utils", + "lemmy_websocket", + "log", + "openssl", + "percent-encoding", + "rand 0.7.3", + "reqwest", + "serde 1.0.116", + "serde_json", + "sha2", + "strum", + "strum_macros", + "thiserror", + "tokio", + "url", + "uuid 0.8.1", +] + +[[package]] +name = "lemmy_apub" +version = "0.1.0" +dependencies = [ + "activitystreams", + "activitystreams-ext", + "actix", + "actix-rt", + "actix-web", + "anyhow", + "async-trait", + "awc", + "background-jobs", + "base64 0.12.3", + "bcrypt", + "chrono", + "diesel", + "futures", + "http", + "http-signature-normalization-actix", + "itertools", + "lazy_static", + "lemmy_db", + "lemmy_structs", + "lemmy_utils", + "lemmy_websocket", + "log", + "openssl", + "percent-encoding", + "rand 0.7.3", + "reqwest", + "serde 1.0.116", + "serde_json", + "sha2", + "strum", + "strum_macros", + "thiserror", + "tokio", + "url", + "uuid 0.8.1", +] + [[package]] name = "lemmy_db" version = "0.1.0" @@ -1844,58 +1924,41 @@ dependencies = [ name = "lemmy_server" version = "0.0.1" dependencies = [ - "activitystreams", - "activitystreams-ext", "actix", "actix-files", - "actix-rt", "actix-web", "actix-web-actors", "anyhow", - "async-trait", "awc", - "background-jobs", - "base64 0.12.3", - "bcrypt", - "captcha", "cargo-husky", "chrono", "diesel", "diesel_migrations", - "dotenv", "env_logger", - "futures", - "http", "http-signature-normalization-actix", - "itertools", - "jsonwebtoken", "lazy_static", + "lemmy_api", + "lemmy_apub", "lemmy_db", "lemmy_rate_limit", "lemmy_structs", "lemmy_utils", + "lemmy_websocket", "log", "openssl", - "percent-encoding", - "rand 0.7.3", "reqwest", "rss", "serde 1.0.116", - "serde_json", "sha2", "strum", - "strum_macros", - "thiserror", "tokio", "url", - "uuid 0.8.1", ] [[package]] name = "lemmy_structs" version = "0.1.0" dependencies = [ - "actix", "actix-web", "chrono", "diesel", @@ -1903,8 +1966,7 @@ dependencies = [ "lemmy_utils", "log", "serde 1.0.116", - "strum", - "strum_macros", + "serde_json", ] [[package]] @@ -1924,12 +1986,36 @@ dependencies = [ "openssl", "rand 0.7.3", "regex", + "reqwest", "serde 1.0.116", "serde_json", "thiserror", "url", ] +[[package]] +name = "lemmy_websocket" +version = "0.1.0" +dependencies = [ + "actix", + "anyhow", + "background-jobs", + "chrono", + "diesel", + "lemmy_db", + "lemmy_rate_limit", + "lemmy_structs", + "lemmy_utils", + "log", + "rand 0.7.3", + "reqwest", + "serde 1.0.116", + "serde_json", + "strum", + "strum_macros", + "tokio", +] + [[package]] name = "lettre" version = "0.9.3" diff --git a/Cargo.toml b/Cargo.toml index 2545e268e..d8980157e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,56 +8,43 @@ lto = true [workspace] members = [ + "lemmy_api", + "lemmy_apub", "lemmy_utils", "lemmy_db", "lemmy_structs", "lemmy_rate_limit", + "lemmy_websocket", ] [dependencies] +lemmy_api = { path = "./lemmy_api" } +lemmy_apub = { path = "./lemmy_apub" } lemmy_utils = { path = "./lemmy_utils" } lemmy_db = { path = "./lemmy_db" } lemmy_structs = { path = "./lemmy_structs" } lemmy_rate_limit = { path = "./lemmy_rate_limit" } +lemmy_websocket = { path = "./lemmy_websocket" } diesel = "1.4" diesel_migrations = "1.4" -dotenv = "0.15" -activitystreams = "0.7.0-alpha.4" -activitystreams-ext = "0.1.0-alpha.2" -bcrypt = "0.8" chrono = { version = "0.4", features = ["serde"] } -serde_json = { version = "1.0", features = ["preserve_order"]} serde = { version = "1.0", features = ["derive"] } actix = "0.10" actix-web = { version = "3.0", default-features = false, features = ["rustls"] } actix-files = { version = "0.3", default-features = false } actix-web-actors = { version = "3.0", default-features = false } -actix-rt = { version = "1.1", default-features = false } awc = { version = "2.0", default-features = false } log = "0.4" env_logger = "0.7" -rand = "0.7" strum = "0.19" -strum_macros = "0.19" -jsonwebtoken = "7.0" lazy_static = "1.3" rss = "1.9" url = { version = "2.1", features = ["serde"] } -percent-encoding = "2.1" openssl = "0.10" -http = "0.2" http-signature-normalization-actix = { version = "0.4", default-features = false, features = ["sha-2"] } -base64 = "0.12" tokio = "0.2" -futures = "0.3" -itertools = "0.9" -uuid = { version = "0.8", features = ["serde", "v4"] } sha2 = "0.9" -async-trait = "0.1" -captcha = "0.0" anyhow = "1.0" -thiserror = "1.0" -background-jobs = " 0.8" reqwest = { version = "0.10", features = ["json"] } [dev-dependencies.cargo-husky] diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index 17e58fda4..a4341f3d0 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -12,6 +12,9 @@ RUN mkdir -p lemmy_db/src/ \ lemmy_utils/src/ \ lemmy_structs/src/ \ lemmy_rate_limit/src/ \ + lemmy_api/src/ \ + lemmy_apub/src/ \ + lemmy_websocket/src/ \ lemmy # Copy the cargo tomls @@ -20,6 +23,9 @@ COPY lemmy_db/Cargo.toml ./lemmy_db/ COPY lemmy_utils/Cargo.toml ./lemmy_utils/ COPY lemmy_structs/Cargo.toml ./lemmy_structs/ COPY lemmy_rate_limit/Cargo.toml ./lemmy_rate_limit/ +COPY lemmy_api/Cargo.toml ./lemmy_api/ +COPY lemmy_apub/Cargo.toml ./lemmy_apub/ +COPY lemmy_websocket/Cargo.toml ./lemmy_websocket/ # Cache the deps RUN cargo build-deps @@ -30,6 +36,9 @@ COPY lemmy_db/src ./lemmy_db/src/ COPY lemmy_utils/src/ ./lemmy_utils/src/ COPY lemmy_structs/src/ ./lemmy_structs/src/ COPY lemmy_rate_limit/src/ ./lemmy_rate_limit/src/ +COPY lemmy_api/src/ ./lemmy_api/src/ +COPY lemmy_apub/src/ ./lemmy_apub/src/ +COPY lemmy_websocket/src/ ./lemmy_websocket/src/ COPY migrations ./migrations/ # Build for debug diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile index 20e910a42..137adc462 100644 --- a/docker/prod/Dockerfile +++ b/docker/prod/Dockerfile @@ -15,6 +15,9 @@ COPY lemmy_db ./lemmy_db COPY lemmy_utils ./lemmy_utils COPY lemmy_structs ./lemmy_structs COPY lemmy_rate_limit ./lemmy_rate_limit +COPY lemmy_api ./lemmy_api +COPY lemmy_apub ./lemmy_apub +COPY lemmy_websocket ./lemmy_websocket RUN mkdir -p ./src/bin \ && echo 'fn main() { println!("Dummy") }' > ./src/bin/main.rs RUN cargo build --release diff --git a/docker/prod/deploy.sh b/docker/prod/deploy.sh index 94287126c..7d84738b2 100755 --- a/docker/prod/deploy.sh +++ b/docker/prod/deploy.sh @@ -9,8 +9,8 @@ third_semver=$(echo $new_tag | cut -d "." -f 3) # Setting the version on the front end cd ../../ # Setting the version on the backend -echo "pub const VERSION: &str = \"$new_tag\";" > "src/version.rs" -git add "src/version.rs" +echo "pub const VERSION: &str = \"$new_tag\";" > "lemmy_api/src/version.rs" +git add "lemmy_api/src/version.rs" # Setting the version for Ansible echo $new_tag > "ansible/VERSION" git add "ansible/VERSION" diff --git a/lemmy_api/Cargo.toml b/lemmy_api/Cargo.toml new file mode 100644 index 000000000..b302854f1 --- /dev/null +++ b/lemmy_api/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "lemmy_api" +version = "0.1.0" +authors = ["Felix Ableitner "] +edition = "2018" + +[lib] +name = "lemmy_api" +path = "src/lib.rs" + +[dependencies] +lemmy_apub = { path = "../lemmy_apub" } +lemmy_utils = { path = "../lemmy_utils" } +lemmy_db = { path = "../lemmy_db" } +lemmy_structs = { path = "../lemmy_structs" } +lemmy_rate_limit = { path = "../lemmy_rate_limit" } +lemmy_websocket = { path = "../lemmy_websocket" } +diesel = "1.4" +bcrypt = "0.8" +chrono = { version = "0.4", features = ["serde"] } +serde_json = { version = "1.0", features = ["preserve_order"]} +serde = { version = "1.0", features = ["derive"] } +actix = "0.10" +actix-web = { version = "3.0", default-features = false } +actix-rt = { version = "1.1", default-features = false } +awc = { version = "2.0", default-features = false } +log = "0.4" +rand = "0.7" +strum = "0.19" +strum_macros = "0.19" +jsonwebtoken = "7.0" +lazy_static = "1.3" +url = { version = "2.1", features = ["serde"] } +percent-encoding = "2.1" +openssl = "0.10" +http = "0.2" +http-signature-normalization-actix = { version = "0.4", default-features = false, features = ["sha-2"] } +base64 = "0.12" +tokio = "0.2" +futures = "0.3" +itertools = "0.9" +uuid = { version = "0.8", features = ["serde", "v4"] } +sha2 = "0.9" +async-trait = "0.1" +captcha = "0.0" +anyhow = "1.0" +thiserror = "1.0" +background-jobs = " 0.8" +reqwest = { version = "0.10", features = ["json"] } diff --git a/src/api/claims.rs b/lemmy_api/src/claims.rs similarity index 100% rename from src/api/claims.rs rename to lemmy_api/src/claims.rs diff --git a/src/api/comment.rs b/lemmy_api/src/comment.rs similarity index 98% rename from src/api/comment.rs rename to lemmy_api/src/comment.rs index 69853c3e7..5a78ba914 100644 --- a/src/api/comment.rs +++ b/lemmy_api/src/comment.rs @@ -1,16 +1,13 @@ use crate::{ - api::{ - check_community_ban, - get_post, - get_user_from_jwt, - get_user_from_jwt_opt, - is_mod_or_admin, - Perform, - }, - apub::{ApubLikeableType, ApubObjectType}, - LemmyContext, + check_community_ban, + get_post, + get_user_from_jwt, + get_user_from_jwt_opt, + is_mod_or_admin, + Perform, }; use actix_web::web::Data; +use lemmy_apub::{ApubLikeableType, ApubObjectType}; use lemmy_db::{ comment::*, comment_view::*, @@ -24,12 +21,7 @@ use lemmy_db::{ Saveable, SortType, }; -use lemmy_structs::{ - blocking, - comment::*, - send_local_notifs, - websocket::{SendComment, UserOperation}, -}; +use lemmy_structs::{blocking, comment::*, send_local_notifs}; use lemmy_utils::{ apub::{make_apub_endpoint, EndpointType}, utils::{remove_slurs, scrape_text_for_mentions}, @@ -37,6 +29,7 @@ use lemmy_utils::{ ConnectionId, LemmyError, }; +use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation}; use std::str::FromStr; #[async_trait::async_trait(?Send)] diff --git a/src/api/community.rs b/lemmy_api/src/community.rs similarity index 98% rename from src/api/community.rs rename to lemmy_api/src/community.rs index c5199fe54..1d3e63a9c 100644 --- a/src/api/community.rs +++ b/lemmy_api/src/community.rs @@ -1,10 +1,7 @@ -use crate::{ - api::{get_user_from_jwt, get_user_from_jwt_opt, is_admin, is_mod_or_admin, Perform}, - apub::ActorType, - LemmyContext, -}; +use crate::{get_user_from_jwt, get_user_from_jwt_opt, is_admin, is_mod_or_admin, Perform}; use actix_web::web::Data; use anyhow::Context; +use lemmy_apub::ActorType; use lemmy_db::{ comment::Comment, comment_view::CommentQueryBuilder, @@ -22,16 +19,7 @@ use lemmy_db::{ Joinable, SortType, }; -use lemmy_structs::{ - blocking, - community::*, - websocket::{ - GetCommunityUsersOnline, - JoinCommunityRoom, - SendCommunityRoomMessage, - UserOperation, - }, -}; +use lemmy_structs::{blocking, community::*}; use lemmy_utils::{ apub::{generate_actor_keypair, make_apub_endpoint, EndpointType}, location_info, @@ -40,6 +28,11 @@ use lemmy_utils::{ ConnectionId, LemmyError, }; +use lemmy_websocket::{ + messages::{GetCommunityUsersOnline, JoinCommunityRoom, SendCommunityRoomMessage}, + LemmyContext, + UserOperation, +}; use std::str::FromStr; #[async_trait::async_trait(?Send)] diff --git a/lemmy_api/src/lib.rs b/lemmy_api/src/lib.rs new file mode 100644 index 000000000..905075b81 --- /dev/null +++ b/lemmy_api/src/lib.rs @@ -0,0 +1,509 @@ +use crate::claims::Claims; +use actix_web::{web, web::Data}; +use anyhow::anyhow; +use lemmy_db::{ + community::Community, + community_view::CommunityUserBanView, + post::Post, + user::User_, + Crud, + DbPool, +}; +use lemmy_structs::{blocking, comment::*, community::*, post::*, site::*, user::*}; +use lemmy_utils::{ + apub::get_apub_protocol_string, + request::{retry, RecvError}, + settings::Settings, + APIError, + ConnectionId, + LemmyError, +}; +use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation}; +use log::error; +use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; +use reqwest::Client; +use serde::Deserialize; +use std::process::Command; + +pub mod claims; +pub mod comment; +pub mod community; +pub mod post; +pub mod site; +pub mod user; +pub mod version; + +#[async_trait::async_trait(?Send)] +pub trait Perform { + type Response: serde::ser::Serialize + Send; + + async fn perform( + &self, + context: &Data, + websocket_id: Option, + ) -> Result; +} + +pub(in crate) async fn is_mod_or_admin( + pool: &DbPool, + user_id: i32, + community_id: i32, +) -> Result<(), LemmyError> { + let is_mod_or_admin = blocking(pool, move |conn| { + Community::is_mod_or_admin(conn, user_id, community_id) + }) + .await?; + if !is_mod_or_admin { + return Err(APIError::err("not_a_mod_or_admin").into()); + } + Ok(()) +} +pub async fn is_admin(pool: &DbPool, user_id: i32) -> Result<(), LemmyError> { + let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; + if !user.admin { + return Err(APIError::err("not_an_admin").into()); + } + Ok(()) +} + +pub(in crate) async fn get_post(post_id: i32, pool: &DbPool) -> Result { + match blocking(pool, move |conn| Post::read(conn, post_id)).await? { + Ok(post) => Ok(post), + Err(_e) => Err(APIError::err("couldnt_find_post").into()), + } +} + +pub(in crate) async fn get_user_from_jwt(jwt: &str, pool: &DbPool) -> Result { + let claims = match Claims::decode(&jwt) { + Ok(claims) => claims.claims, + Err(_e) => return Err(APIError::err("not_logged_in").into()), + }; + let user_id = claims.id; + let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; + // Check for a site ban + if user.banned { + return Err(APIError::err("site_ban").into()); + } + Ok(user) +} + +pub(in crate) async fn get_user_from_jwt_opt( + jwt: &Option, + pool: &DbPool, +) -> Result, LemmyError> { + match jwt { + Some(jwt) => Ok(Some(get_user_from_jwt(jwt, pool).await?)), + None => Ok(None), + } +} + +pub(in crate) async fn check_community_ban( + user_id: i32, + community_id: i32, + pool: &DbPool, +) -> Result<(), LemmyError> { + let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); + if blocking(pool, is_banned).await? { + Err(APIError::err("community_ban").into()) + } else { + Ok(()) + } +} + +pub async fn match_websocket_operation( + context: LemmyContext, + id: ConnectionId, + op: UserOperation, + data: &str, +) -> Result { + match op { + // User ops + UserOperation::Login => do_websocket_operation::(context, id, op, data).await, + UserOperation::Register => do_websocket_operation::(context, id, op, data).await, + UserOperation::GetCaptcha => do_websocket_operation::(context, id, op, data).await, + UserOperation::GetUserDetails => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::GetReplies => do_websocket_operation::(context, id, op, data).await, + UserOperation::AddAdmin => do_websocket_operation::(context, id, op, data).await, + UserOperation::BanUser => do_websocket_operation::(context, id, op, data).await, + UserOperation::GetUserMentions => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::MarkUserMentionAsRead => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::MarkAllAsRead => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::DeleteAccount => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::PasswordReset => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::PasswordChange => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::UserJoin => do_websocket_operation::(context, id, op, data).await, + UserOperation::PostJoin => do_websocket_operation::(context, id, op, data).await, + UserOperation::CommunityJoin => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::SaveUserSettings => { + do_websocket_operation::(context, id, op, data).await + } + + // Private Message ops + UserOperation::CreatePrivateMessage => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::EditPrivateMessage => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::DeletePrivateMessage => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::MarkPrivateMessageAsRead => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::GetPrivateMessages => { + do_websocket_operation::(context, id, op, data).await + } + + // Site ops + UserOperation::GetModlog => do_websocket_operation::(context, id, op, data).await, + UserOperation::CreateSite => do_websocket_operation::(context, id, op, data).await, + UserOperation::EditSite => do_websocket_operation::(context, id, op, data).await, + UserOperation::GetSite => do_websocket_operation::(context, id, op, data).await, + UserOperation::GetSiteConfig => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::SaveSiteConfig => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::Search => do_websocket_operation::(context, id, op, data).await, + UserOperation::TransferCommunity => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::TransferSite => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::ListCategories => { + do_websocket_operation::(context, id, op, data).await + } + + // Community ops + UserOperation::GetCommunity => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::ListCommunities => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::CreateCommunity => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::EditCommunity => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::DeleteCommunity => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::RemoveCommunity => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::FollowCommunity => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::GetFollowedCommunities => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::BanFromCommunity => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::AddModToCommunity => { + do_websocket_operation::(context, id, op, data).await + } + + // Post ops + UserOperation::CreatePost => do_websocket_operation::(context, id, op, data).await, + UserOperation::GetPost => do_websocket_operation::(context, id, op, data).await, + UserOperation::GetPosts => do_websocket_operation::(context, id, op, data).await, + UserOperation::EditPost => do_websocket_operation::(context, id, op, data).await, + UserOperation::DeletePost => do_websocket_operation::(context, id, op, data).await, + UserOperation::RemovePost => do_websocket_operation::(context, id, op, data).await, + UserOperation::LockPost => do_websocket_operation::(context, id, op, data).await, + UserOperation::StickyPost => do_websocket_operation::(context, id, op, data).await, + UserOperation::CreatePostLike => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::SavePost => do_websocket_operation::(context, id, op, data).await, + + // Comment ops + UserOperation::CreateComment => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::EditComment => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::DeleteComment => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::RemoveComment => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::MarkCommentAsRead => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::SaveComment => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::GetComments => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::CreateCommentLike => { + do_websocket_operation::(context, id, op, data).await + } + } +} + +async fn do_websocket_operation<'a, 'b, Data>( + context: LemmyContext, + id: ConnectionId, + op: UserOperation, + data: &str, +) -> Result +where + for<'de> Data: Deserialize<'de> + 'a, + Data: Perform, +{ + let parsed_data: Data = serde_json::from_str(&data)?; + let res = parsed_data + .perform(&web::Data::new(context), Some(id)) + .await?; + serialize_websocket_message(&op, &res) +} + +pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result { + let mut built_text = String::new(); + + // Building proper speech text for espeak + for mut c in captcha.chars() { + let new_str = if c.is_alphabetic() { + if c.is_lowercase() { + c.make_ascii_uppercase(); + format!("lower case {} ... ", c) + } else { + c.make_ascii_uppercase(); + format!("capital {} ... ", c) + } + } else { + format!("{} ...", c) + }; + + built_text.push_str(&new_str); + } + + espeak_wav_base64(&built_text) +} + +pub(crate) fn espeak_wav_base64(text: &str) -> Result { + // Make a temp file path + let uuid = uuid::Uuid::new_v4().to_string(); + let file_path = format!("/tmp/lemmy_espeak_{}.wav", &uuid); + + // Write the wav file + Command::new("espeak") + .arg("-w") + .arg(&file_path) + .arg(text) + .status()?; + + // Read the wav file bytes + let bytes = std::fs::read(&file_path)?; + + // Delete the file + std::fs::remove_file(file_path)?; + + // Convert to base64 + let base64 = base64::encode(bytes); + + Ok(base64) +} + +#[derive(Deserialize, Debug)] +pub(crate) struct IframelyResponse { + title: Option, + description: Option, + thumbnail_url: Option, + html: Option, +} + +pub(crate) async fn fetch_iframely( + client: &Client, + url: &str, +) -> Result { + let fetch_url = format!("http://iframely/oembed?url={}", url); + + let response = retry(|| client.get(&fetch_url).send()).await?; + + let res: IframelyResponse = response + .json() + .await + .map_err(|e| RecvError(e.to_string()))?; + Ok(res) +} + +#[derive(Deserialize, Debug, Clone)] +pub(crate) struct PictrsResponse { + files: Vec, + msg: String, +} + +#[derive(Deserialize, Debug, Clone)] +pub(crate) struct PictrsFile { + file: String, + delete_token: String, +} + +pub(crate) async fn fetch_pictrs( + client: &Client, + image_url: &str, +) -> Result { + is_image_content_type(client, image_url).await?; + + let fetch_url = format!( + "http://pictrs:8080/image/download?url={}", + utf8_percent_encode(image_url, NON_ALPHANUMERIC) // TODO this might not be needed + ); + + let response = retry(|| client.get(&fetch_url).send()).await?; + + let response: PictrsResponse = response + .json() + .await + .map_err(|e| RecvError(e.to_string()))?; + + if response.msg == "ok" { + Ok(response) + } else { + Err(anyhow!("{}", &response.msg).into()) + } +} + +async fn fetch_iframely_and_pictrs_data( + client: &Client, + url: Option, +) -> ( + Option, + Option, + Option, + Option, +) { + match &url { + Some(url) => { + // Fetch iframely data + let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) = + match fetch_iframely(client, url).await { + Ok(res) => (res.title, res.description, res.thumbnail_url, res.html), + Err(e) => { + error!("iframely err: {}", e); + (None, None, None, None) + } + }; + + // Fetch pictrs thumbnail + let pictrs_hash = match iframely_thumbnail_url { + Some(iframely_thumbnail_url) => match fetch_pictrs(client, &iframely_thumbnail_url).await { + Ok(res) => Some(res.files[0].file.to_owned()), + Err(e) => { + error!("pictrs err: {}", e); + None + } + }, + // Try to generate a small thumbnail if iframely is not supported + None => match fetch_pictrs(client, &url).await { + Ok(res) => Some(res.files[0].file.to_owned()), + Err(e) => { + error!("pictrs err: {}", e); + None + } + }, + }; + + // The full urls are necessary for federation + let pictrs_thumbnail = if let Some(pictrs_hash) = pictrs_hash { + Some(format!( + "{}://{}/pictrs/image/{}", + get_apub_protocol_string(), + Settings::get().hostname, + pictrs_hash + )) + } else { + None + }; + + ( + iframely_title, + iframely_description, + iframely_html, + pictrs_thumbnail, + ) + } + None => (None, None, None, None), + } +} + +pub(crate) async fn is_image_content_type(client: &Client, test: &str) -> Result<(), LemmyError> { + let response = retry(|| client.get(test).send()).await?; + + if response + .headers() + .get("Content-Type") + .ok_or_else(|| anyhow!("No Content-Type header"))? + .to_str()? + .starts_with("image/") + { + Ok(()) + } else { + Err(anyhow!("Not an image type.").into()) + } +} + +#[cfg(test)] +mod tests { + use crate::{captcha_espeak_wav_base64, is_image_content_type}; + + #[test] + fn test_image() { + actix_rt::System::new("tset_image").block_on(async move { + let client = reqwest::Client::default(); + assert!(is_image_content_type(&client, "https://1734811051.rsc.cdn77.org/data/images/full/365645/as-virus-kills-navajos-in-their-homes-tribal-women-provide-lifeline.jpg?w=600?w=650").await.is_ok()); + assert!(is_image_content_type(&client, + "https://twitter.com/BenjaminNorton/status/1259922424272957440?s=20" + ) + .await.is_err() + ); + }); + } + + #[test] + fn test_espeak() { + assert!(captcha_espeak_wav_base64("WxRt2l").is_ok()) + } + + // These helped with testing + // #[test] + // fn test_iframely() { + // let res = fetch_iframely(client, "https://www.redspark.nu/?p=15341").await; + // assert!(res.is_ok()); + // } + + // #[test] + // fn test_pictshare() { + // let res = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpg"); + // assert!(res.is_ok()); + // let res_other = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpgaoeu"); + // assert!(res_other.is_err()); + // } +} diff --git a/src/api/post.rs b/lemmy_api/src/post.rs similarity index 98% rename from src/api/post.rs rename to lemmy_api/src/post.rs index ef5409d1f..e8fb9d984 100644 --- a/src/api/post.rs +++ b/lemmy_api/src/post.rs @@ -1,10 +1,13 @@ use crate::{ - api::{check_community_ban, get_user_from_jwt, get_user_from_jwt_opt, is_mod_or_admin, Perform}, - apub::{ApubLikeableType, ApubObjectType}, + check_community_ban, fetch_iframely_and_pictrs_data, - LemmyContext, + get_user_from_jwt, + get_user_from_jwt_opt, + is_mod_or_admin, + Perform, }; use actix_web::web::Data; +use lemmy_apub::{ApubLikeableType, ApubObjectType}; use lemmy_db::{ comment_view::*, community_view::*, @@ -19,11 +22,7 @@ use lemmy_db::{ Saveable, SortType, }; -use lemmy_structs::{ - blocking, - post::*, - websocket::{GetPostUsersOnline, JoinPostRoom, SendPost, UserOperation}, -}; +use lemmy_structs::{blocking, post::*}; use lemmy_utils::{ apub::{make_apub_endpoint, EndpointType}, utils::{check_slurs, check_slurs_opt, is_valid_post_title}, @@ -31,6 +30,11 @@ use lemmy_utils::{ ConnectionId, LemmyError, }; +use lemmy_websocket::{ + messages::{GetPostUsersOnline, JoinPostRoom, SendPost}, + LemmyContext, + UserOperation, +}; use std::str::FromStr; use url::Url; diff --git a/src/api/site.rs b/lemmy_api/src/site.rs similarity index 98% rename from src/api/site.rs rename to lemmy_api/src/site.rs index 3ff1e49d0..9db838ff1 100644 --- a/src/api/site.rs +++ b/lemmy_api/src/site.rs @@ -1,11 +1,7 @@ -use crate::{ - api::{get_user_from_jwt, get_user_from_jwt_opt, is_admin, Perform}, - apub::fetcher::search_by_apub_id, - version, - LemmyContext, -}; +use crate::{get_user_from_jwt, get_user_from_jwt_opt, is_admin, version, Perform}; use actix_web::web::Data; use anyhow::Context; +use lemmy_apub::fetcher::search_by_apub_id; use lemmy_db::{ category::*, comment_view::*, @@ -22,12 +18,7 @@ use lemmy_db::{ SearchType, SortType, }; -use lemmy_structs::{ - blocking, - site::*, - user::Register, - websocket::{GetUsersOnline, SendAllMessage, UserOperation}, -}; +use lemmy_structs::{blocking, site::*, user::Register}; use lemmy_utils::{ location_info, settings::Settings, @@ -36,6 +27,11 @@ use lemmy_utils::{ ConnectionId, LemmyError, }; +use lemmy_websocket::{ + messages::{GetUsersOnline, SendAllMessage}, + LemmyContext, + UserOperation, +}; use log::{debug, info}; use std::str::FromStr; diff --git a/src/api/user.rs b/lemmy_api/src/user.rs similarity index 99% rename from src/api/user.rs rename to lemmy_api/src/user.rs index 977250d34..e2b73c53e 100644 --- a/src/api/user.rs +++ b/lemmy_api/src/user.rs @@ -1,14 +1,17 @@ use crate::{ - api::{claims::Claims, get_user_from_jwt, get_user_from_jwt_opt, is_admin, Perform}, - apub::ApubObjectType, captcha_espeak_wav_base64, - LemmyContext, + claims::Claims, + get_user_from_jwt, + get_user_from_jwt_opt, + is_admin, + Perform, }; use actix_web::web::Data; use anyhow::Context; use bcrypt::verify; use captcha::{gen, Difficulty}; use chrono::Duration; +use lemmy_apub::ApubObjectType; use lemmy_db::{ comment::*, comment_view::*, @@ -34,18 +37,7 @@ use lemmy_db::{ ListingType, SortType, }; -use lemmy_structs::{ - blocking, - user::*, - websocket::{ - CaptchaItem, - CheckCaptcha, - JoinUserRoom, - SendAllMessage, - SendUserRoomMessage, - UserOperation, - }, -}; +use lemmy_structs::{blocking, user::*}; use lemmy_utils::{ apub::{generate_actor_keypair, make_apub_endpoint, EndpointType}, email::send_email, @@ -63,6 +55,11 @@ use lemmy_utils::{ ConnectionId, LemmyError, }; +use lemmy_websocket::{ + messages::{CaptchaItem, CheckCaptcha, JoinUserRoom, SendAllMessage, SendUserRoomMessage}, + LemmyContext, + UserOperation, +}; use log::error; use std::str::FromStr; diff --git a/src/version.rs b/lemmy_api/src/version.rs similarity index 100% rename from src/version.rs rename to lemmy_api/src/version.rs diff --git a/lemmy_apub/Cargo.toml b/lemmy_apub/Cargo.toml new file mode 100644 index 000000000..66dbbe4f1 --- /dev/null +++ b/lemmy_apub/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "lemmy_apub" +version = "0.1.0" +authors = ["Felix Ableitner "] +edition = "2018" + +[lib] +name = "lemmy_apub" +path = "src/lib.rs" + +[dependencies] +lemmy_utils = { path = "../lemmy_utils" } +lemmy_db = { path = "../lemmy_db" } +lemmy_structs = { path = "../lemmy_structs" } +lemmy_websocket = { path = "../lemmy_websocket" } +diesel = "1.4" +activitystreams = "0.7.0-alpha.4" +activitystreams-ext = "0.1.0-alpha.2" +bcrypt = "0.8" +chrono = { version = "0.4", features = ["serde"] } +serde_json = { version = "1.0", features = ["preserve_order"]} +serde = { version = "1.0", features = ["derive"] } +actix = "0.10" +actix-web = { version = "3.0", default-features = false } +actix-rt = { version = "1.1", default-features = false } +awc = { version = "2.0", default-features = false } +log = "0.4" +rand = "0.7" +strum = "0.19" +strum_macros = "0.19" +lazy_static = "1.3" +url = { version = "2.1", features = ["serde"] } +percent-encoding = "2.1" +openssl = "0.10" +http = "0.2" +http-signature-normalization-actix = { version = "0.4", default-features = false, features = ["sha-2"] } +base64 = "0.12" +tokio = "0.2" +futures = "0.3" +itertools = "0.9" +uuid = { version = "0.8", features = ["serde", "v4"] } +sha2 = "0.9" +async-trait = "0.1" +anyhow = "1.0" +thiserror = "1.0" +background-jobs = " 0.8" +reqwest = { version = "0.10", features = ["json"] } \ No newline at end of file diff --git a/src/apub/activities.rs b/lemmy_apub/src/activities.rs similarity index 87% rename from src/apub/activities.rs rename to lemmy_apub/src/activities.rs index dc1892693..3b1b12ab3 100644 --- a/src/apub/activities.rs +++ b/lemmy_apub/src/activities.rs @@ -1,13 +1,11 @@ -use crate::{ - apub::{activity_queue::send_activity, community::do_announce, insert_activity}, - LemmyContext, -}; +use crate::{activity_queue::send_activity, community::do_announce, insert_activity}; use activitystreams::{ base::{Extends, ExtendsExt}, object::AsObject, }; use lemmy_db::{community::Community, user::User_}; use lemmy_utils::{apub::get_apub_protocol_string, settings::Settings, LemmyError}; +use lemmy_websocket::LemmyContext; use serde::{export::fmt::Debug, Serialize}; use url::{ParseError, Url}; use uuid::Uuid; @@ -37,7 +35,7 @@ where Ok(()) } -pub(in crate::apub) fn generate_activity_id(kind: T) -> Result +pub(in crate) fn generate_activity_id(kind: T) -> Result where T: ToString, { diff --git a/src/apub/activity_queue.rs b/lemmy_apub/src/activity_queue.rs similarity index 97% rename from src/apub/activity_queue.rs rename to lemmy_apub/src/activity_queue.rs index a48cf5bd2..80c92c2f9 100644 --- a/src/apub/activity_queue.rs +++ b/lemmy_apub/src/activity_queue.rs @@ -1,4 +1,4 @@ -use crate::apub::{check_is_apub_id_valid, extensions::signatures::sign, ActorType}; +use crate::{check_is_apub_id_valid, extensions::signatures::sign, ActorType}; use activitystreams::{ base::{Extends, ExtendsExt}, object::AsObject, diff --git a/src/apub/comment.rs b/lemmy_apub/src/comment.rs similarity index 97% rename from src/apub/comment.rs rename to lemmy_apub/src/comment.rs index fc19ec334..4e5c173f8 100644 --- a/src/apub/comment.rs +++ b/lemmy_apub/src/comment.rs @@ -1,24 +1,20 @@ use crate::{ - apub::{ - activities::{generate_activity_id, send_activity_to_community}, - check_actor_domain, - create_apub_response, - create_apub_tombstone_response, - create_tombstone, - fetch_webfinger_url, - fetcher::{ - get_or_fetch_and_insert_comment, - get_or_fetch_and_insert_post, - get_or_fetch_and_upsert_user, - }, - ActorType, - ApubLikeableType, - ApubObjectType, - FromApub, - ToApub, + activities::{generate_activity_id, send_activity_to_community}, + check_actor_domain, + create_apub_response, + create_apub_tombstone_response, + create_tombstone, + fetch_webfinger_url, + fetcher::{ + get_or_fetch_and_insert_comment, + get_or_fetch_and_insert_post, + get_or_fetch_and_upsert_user, }, - DbPool, - LemmyContext, + ActorType, + ApubLikeableType, + ApubObjectType, + FromApub, + ToApub, }; use activitystreams::{ activity::{ @@ -46,6 +42,7 @@ use lemmy_db::{ post::Post, user::User_, Crud, + DbPool, }; use lemmy_structs::blocking; use lemmy_utils::{ @@ -53,6 +50,7 @@ use lemmy_utils::{ utils::{convert_datetime, remove_slurs, scrape_text_for_mentions, MentionData}, LemmyError, }; +use lemmy_websocket::LemmyContext; use log::debug; use serde::Deserialize; use serde_json::Error; diff --git a/src/apub/community.rs b/lemmy_apub/src/community.rs similarity index 97% rename from src/apub/community.rs rename to lemmy_apub/src/community.rs index 54b2957ed..8a41e8866 100644 --- a/src/apub/community.rs +++ b/lemmy_apub/src/community.rs @@ -1,21 +1,17 @@ use crate::{ - apub::{ - activities::generate_activity_id, - activity_queue::send_activity, - check_actor_domain, - create_apub_response, - create_apub_tombstone_response, - create_tombstone, - extensions::group_extensions::GroupExtension, - fetcher::{get_or_fetch_and_upsert_actor, get_or_fetch_and_upsert_user}, - insert_activity, - ActorType, - FromApub, - GroupExt, - ToApub, - }, - DbPool, - LemmyContext, + activities::generate_activity_id, + activity_queue::send_activity, + check_actor_domain, + create_apub_response, + create_apub_tombstone_response, + create_tombstone, + extensions::group_extensions::GroupExtension, + fetcher::{get_or_fetch_and_upsert_actor, get_or_fetch_and_upsert_user}, + insert_activity, + ActorType, + FromApub, + GroupExt, + ToApub, }; use activitystreams::{ activity::{ @@ -44,6 +40,7 @@ use lemmy_db::{ naive_now, post::Post, user::User_, + DbPool, }; use lemmy_structs::blocking; use lemmy_utils::{ @@ -52,6 +49,7 @@ use lemmy_utils::{ utils::{check_slurs, check_slurs_opt, convert_datetime}, LemmyError, }; +use lemmy_websocket::LemmyContext; use serde::Deserialize; use url::Url; diff --git a/src/apub/extensions/group_extensions.rs b/lemmy_apub/src/extensions/group_extensions.rs similarity index 100% rename from src/apub/extensions/group_extensions.rs rename to lemmy_apub/src/extensions/group_extensions.rs diff --git a/src/apub/extensions/mod.rs b/lemmy_apub/src/extensions/mod.rs similarity index 100% rename from src/apub/extensions/mod.rs rename to lemmy_apub/src/extensions/mod.rs diff --git a/src/apub/extensions/page_extension.rs b/lemmy_apub/src/extensions/page_extension.rs similarity index 100% rename from src/apub/extensions/page_extension.rs rename to lemmy_apub/src/extensions/page_extension.rs diff --git a/src/apub/extensions/signatures.rs b/lemmy_apub/src/extensions/signatures.rs similarity index 99% rename from src/apub/extensions/signatures.rs rename to lemmy_apub/src/extensions/signatures.rs index 4a261c17e..5471e19e4 100644 --- a/src/apub/extensions/signatures.rs +++ b/lemmy_apub/src/extensions/signatures.rs @@ -1,4 +1,4 @@ -use crate::apub::ActorType; +use crate::ActorType; use activitystreams::unparsed::UnparsedMutExt; use activitystreams_ext::UnparsedExtension; use actix_web::{client::ClientRequest, HttpRequest}; diff --git a/src/apub/fetcher.rs b/lemmy_apub/src/fetcher.rs similarity index 98% rename from src/apub/fetcher.rs rename to lemmy_apub/src/fetcher.rs index 5d772f062..4ce4082cd 100644 --- a/src/apub/fetcher.rs +++ b/lemmy_apub/src/fetcher.rs @@ -1,15 +1,11 @@ use crate::{ - apub::{ - check_is_apub_id_valid, - ActorType, - FromApub, - GroupExt, - PageExt, - PersonExt, - APUB_JSON_CONTENT_TYPE, - }, - request::{retry, RecvError}, - LemmyContext, + check_is_apub_id_valid, + ActorType, + FromApub, + GroupExt, + PageExt, + PersonExt, + APUB_JSON_CONTENT_TYPE, }; use activitystreams::{base::BaseExt, collection::OrderedCollection, object::Note, prelude::*}; use anyhow::{anyhow, Context}; @@ -30,7 +26,13 @@ use lemmy_db::{ SearchType, }; use lemmy_structs::{blocking, site::SearchResponse}; -use lemmy_utils::{apub::get_apub_protocol_string, location_info, LemmyError}; +use lemmy_utils::{ + apub::get_apub_protocol_string, + location_info, + request::{retry, RecvError}, + LemmyError, +}; +use lemmy_websocket::LemmyContext; use log::debug; use reqwest::Client; use serde::Deserialize; diff --git a/src/apub/inbox/activities/announce.rs b/lemmy_apub/src/inbox/activities/announce.rs similarity index 77% rename from src/apub/inbox/activities/announce.rs rename to lemmy_apub/src/inbox/activities/announce.rs index 47607a05d..d861e5f27 100644 --- a/src/apub/inbox/activities/announce.rs +++ b/lemmy_apub/src/inbox/activities/announce.rs @@ -1,17 +1,14 @@ -use crate::{ - apub::inbox::{ - activities::{ - create::receive_create, - delete::receive_delete, - dislike::receive_dislike, - like::receive_like, - remove::receive_remove, - undo::receive_undo, - update::receive_update, - }, - shared_inbox::{get_community_id_from_activity, receive_unhandled_activity}, +use crate::inbox::{ + activities::{ + create::receive_create, + delete::receive_delete, + dislike::receive_dislike, + like::receive_like, + remove::receive_remove, + undo::receive_undo, + update::receive_update, }, - LemmyContext, + shared_inbox::{get_community_id_from_activity, receive_unhandled_activity}, }; use activitystreams::{ activity::*, @@ -21,6 +18,7 @@ use activitystreams::{ use actix_web::HttpResponse; use anyhow::Context; use lemmy_utils::{location_info, LemmyError}; +use lemmy_websocket::LemmyContext; pub async fn receive_announce( activity: AnyBase, diff --git a/src/apub/inbox/activities/create.rs b/lemmy_apub/src/inbox/activities/create.rs similarity index 91% rename from src/apub/inbox/activities/create.rs rename to lemmy_apub/src/inbox/activities/create.rs index 961991a65..e25fdd979 100644 --- a/src/apub/inbox/activities/create.rs +++ b/lemmy_apub/src/inbox/activities/create.rs @@ -1,15 +1,12 @@ use crate::{ - apub::{ - inbox::shared_inbox::{ - announce_if_community_is_local, - get_user_from_activity, - receive_unhandled_activity, - }, - ActorType, - FromApub, - PageExt, + inbox::shared_inbox::{ + announce_if_community_is_local, + get_user_from_activity, + receive_unhandled_activity, }, - LemmyContext, + ActorType, + FromApub, + PageExt, }; use activitystreams::{activity::Create, base::AnyBase, object::Note, prelude::*}; use actix_web::HttpResponse; @@ -20,14 +17,13 @@ use lemmy_db::{ post::{Post, PostForm}, post_view::PostView, }; -use lemmy_structs::{ - blocking, - comment::CommentResponse, - post::PostResponse, - send_local_notifs, - websocket::{SendComment, SendPost, UserOperation}, -}; +use lemmy_structs::{blocking, comment::CommentResponse, post::PostResponse, send_local_notifs}; use lemmy_utils::{location_info, utils::scrape_text_for_mentions, LemmyError}; +use lemmy_websocket::{ + messages::{SendComment, SendPost}, + LemmyContext, + UserOperation, +}; pub async fn receive_create( activity: AnyBase, diff --git a/src/apub/inbox/activities/delete.rs b/lemmy_apub/src/inbox/activities/delete.rs similarity index 94% rename from src/apub/inbox/activities/delete.rs rename to lemmy_apub/src/inbox/activities/delete.rs index 131588bac..2c3760e42 100644 --- a/src/apub/inbox/activities/delete.rs +++ b/lemmy_apub/src/inbox/activities/delete.rs @@ -1,17 +1,14 @@ use crate::{ - apub::{ - fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, - inbox::shared_inbox::{ - announce_if_community_is_local, - get_user_from_activity, - receive_unhandled_activity, - }, - ActorType, - FromApub, - GroupExt, - PageExt, + fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, + inbox::shared_inbox::{ + announce_if_community_is_local, + get_user_from_activity, + receive_unhandled_activity, }, - LemmyContext, + ActorType, + FromApub, + GroupExt, + PageExt, }; use activitystreams::{activity::Delete, base::AnyBase, object::Note, prelude::*}; use actix_web::HttpResponse; @@ -31,9 +28,13 @@ use lemmy_structs::{ comment::CommentResponse, community::CommunityResponse, post::PostResponse, - websocket::{SendComment, SendCommunityRoomMessage, SendPost, UserOperation}, }; use lemmy_utils::{location_info, LemmyError}; +use lemmy_websocket::{ + messages::{SendComment, SendCommunityRoomMessage, SendPost}, + LemmyContext, + UserOperation, +}; pub async fn receive_delete( activity: AnyBase, diff --git a/src/apub/inbox/activities/dislike.rs b/lemmy_apub/src/inbox/activities/dislike.rs similarity index 89% rename from src/apub/inbox/activities/dislike.rs rename to lemmy_apub/src/inbox/activities/dislike.rs index df9bd8486..dd63011d4 100644 --- a/src/apub/inbox/activities/dislike.rs +++ b/lemmy_apub/src/inbox/activities/dislike.rs @@ -1,15 +1,12 @@ use crate::{ - apub::{ - fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, - inbox::shared_inbox::{ - announce_if_community_is_local, - get_user_from_activity, - receive_unhandled_activity, - }, - FromApub, - PageExt, + fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, + inbox::shared_inbox::{ + announce_if_community_is_local, + get_user_from_activity, + receive_unhandled_activity, }, - LemmyContext, + FromApub, + PageExt, }; use activitystreams::{activity::Dislike, base::AnyBase, object::Note, prelude::*}; use actix_web::HttpResponse; @@ -21,13 +18,13 @@ use lemmy_db::{ post_view::PostView, Likeable, }; -use lemmy_structs::{ - blocking, - comment::CommentResponse, - post::PostResponse, - websocket::{SendComment, SendPost, UserOperation}, -}; +use lemmy_structs::{blocking, comment::CommentResponse, post::PostResponse}; use lemmy_utils::{location_info, LemmyError}; +use lemmy_websocket::{ + messages::{SendComment, SendPost}, + LemmyContext, + UserOperation, +}; pub async fn receive_dislike( activity: AnyBase, diff --git a/src/apub/inbox/activities/like.rs b/lemmy_apub/src/inbox/activities/like.rs similarity index 89% rename from src/apub/inbox/activities/like.rs rename to lemmy_apub/src/inbox/activities/like.rs index ce067c879..7b56867b4 100644 --- a/src/apub/inbox/activities/like.rs +++ b/lemmy_apub/src/inbox/activities/like.rs @@ -1,15 +1,12 @@ use crate::{ - apub::{ - fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, - inbox::shared_inbox::{ - announce_if_community_is_local, - get_user_from_activity, - receive_unhandled_activity, - }, - FromApub, - PageExt, + fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, + inbox::shared_inbox::{ + announce_if_community_is_local, + get_user_from_activity, + receive_unhandled_activity, }, - LemmyContext, + FromApub, + PageExt, }; use activitystreams::{activity::Like, base::AnyBase, object::Note, prelude::*}; use actix_web::HttpResponse; @@ -21,13 +18,13 @@ use lemmy_db::{ post_view::PostView, Likeable, }; -use lemmy_structs::{ - blocking, - comment::CommentResponse, - post::PostResponse, - websocket::{SendComment, SendPost, UserOperation}, -}; +use lemmy_structs::{blocking, comment::CommentResponse, post::PostResponse}; use lemmy_utils::{location_info, LemmyError}; +use lemmy_websocket::{ + messages::{SendComment, SendPost}, + LemmyContext, + UserOperation, +}; pub async fn receive_like( activity: AnyBase, diff --git a/src/apub/inbox/activities/mod.rs b/lemmy_apub/src/inbox/activities/mod.rs similarity index 100% rename from src/apub/inbox/activities/mod.rs rename to lemmy_apub/src/inbox/activities/mod.rs diff --git a/src/apub/inbox/activities/remove.rs b/lemmy_apub/src/inbox/activities/remove.rs similarity index 94% rename from src/apub/inbox/activities/remove.rs rename to lemmy_apub/src/inbox/activities/remove.rs index e91a65c03..27a7775e6 100644 --- a/src/apub/inbox/activities/remove.rs +++ b/lemmy_apub/src/inbox/activities/remove.rs @@ -1,18 +1,15 @@ use crate::{ - apub::{ - fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, - inbox::shared_inbox::{ - announce_if_community_is_local, - get_community_id_from_activity, - get_user_from_activity, - receive_unhandled_activity, - }, - ActorType, - FromApub, - GroupExt, - PageExt, + fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, + inbox::shared_inbox::{ + announce_if_community_is_local, + get_community_id_from_activity, + get_user_from_activity, + receive_unhandled_activity, }, - LemmyContext, + ActorType, + FromApub, + GroupExt, + PageExt, }; use activitystreams::{activity::Remove, base::AnyBase, object::Note, prelude::*}; use actix_web::HttpResponse; @@ -32,9 +29,13 @@ use lemmy_structs::{ comment::CommentResponse, community::CommunityResponse, post::PostResponse, - websocket::{SendComment, SendCommunityRoomMessage, SendPost, UserOperation}, }; use lemmy_utils::{location_info, LemmyError}; +use lemmy_websocket::{ + messages::{SendComment, SendCommunityRoomMessage, SendPost}, + LemmyContext, + UserOperation, +}; pub async fn receive_remove( activity: AnyBase, diff --git a/src/apub/inbox/activities/undo.rs b/lemmy_apub/src/inbox/activities/undo.rs similarity index 98% rename from src/apub/inbox/activities/undo.rs rename to lemmy_apub/src/inbox/activities/undo.rs index c8b4d35bf..9a421ee69 100644 --- a/src/apub/inbox/activities/undo.rs +++ b/lemmy_apub/src/inbox/activities/undo.rs @@ -1,17 +1,14 @@ use crate::{ - apub::{ - fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, - inbox::shared_inbox::{ - announce_if_community_is_local, - get_user_from_activity, - receive_unhandled_activity, - }, - ActorType, - FromApub, - GroupExt, - PageExt, + fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, + inbox::shared_inbox::{ + announce_if_community_is_local, + get_user_from_activity, + receive_unhandled_activity, }, - LemmyContext, + ActorType, + FromApub, + GroupExt, + PageExt, }; use activitystreams::{ activity::*, @@ -37,9 +34,13 @@ use lemmy_structs::{ comment::CommentResponse, community::CommunityResponse, post::PostResponse, - websocket::{SendComment, SendCommunityRoomMessage, SendPost, UserOperation}, }; use lemmy_utils::{location_info, LemmyError}; +use lemmy_websocket::{ + messages::{SendComment, SendCommunityRoomMessage, SendPost}, + LemmyContext, + UserOperation, +}; pub async fn receive_undo( activity: AnyBase, diff --git a/src/apub/inbox/activities/update.rs b/lemmy_apub/src/inbox/activities/update.rs similarity index 89% rename from src/apub/inbox/activities/update.rs rename to lemmy_apub/src/inbox/activities/update.rs index ccf7534bc..17d9d7084 100644 --- a/src/apub/inbox/activities/update.rs +++ b/lemmy_apub/src/inbox/activities/update.rs @@ -1,16 +1,13 @@ use crate::{ - apub::{ - fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, - inbox::shared_inbox::{ - announce_if_community_is_local, - get_user_from_activity, - receive_unhandled_activity, - }, - ActorType, - FromApub, - PageExt, + fetcher::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post}, + inbox::shared_inbox::{ + announce_if_community_is_local, + get_user_from_activity, + receive_unhandled_activity, }, - LemmyContext, + ActorType, + FromApub, + PageExt, }; use activitystreams::{activity::Update, base::AnyBase, object::Note, prelude::*}; use actix_web::HttpResponse; @@ -22,14 +19,13 @@ use lemmy_db::{ post_view::PostView, Crud, }; -use lemmy_structs::{ - blocking, - comment::CommentResponse, - post::PostResponse, - send_local_notifs, - websocket::{SendComment, SendPost, UserOperation}, -}; +use lemmy_structs::{blocking, comment::CommentResponse, post::PostResponse, send_local_notifs}; use lemmy_utils::{location_info, utils::scrape_text_for_mentions, LemmyError}; +use lemmy_websocket::{ + messages::{SendComment, SendPost}, + LemmyContext, + UserOperation, +}; pub async fn receive_update( activity: AnyBase, diff --git a/src/apub/inbox/community_inbox.rs b/lemmy_apub/src/inbox/community_inbox.rs similarity index 95% rename from src/apub/inbox/community_inbox.rs rename to lemmy_apub/src/inbox/community_inbox.rs index 9dd010c3f..ee75fa005 100644 --- a/src/apub/inbox/community_inbox.rs +++ b/lemmy_apub/src/inbox/community_inbox.rs @@ -1,12 +1,9 @@ use crate::{ - apub::{ - check_is_apub_id_valid, - extensions::signatures::verify, - fetcher::get_or_fetch_and_upsert_user, - insert_activity, - ActorType, - }, - LemmyContext, + check_is_apub_id_valid, + extensions::signatures::verify, + fetcher::get_or_fetch_and_upsert_user, + insert_activity, + ActorType, }; use activitystreams::{ activity::{ActorAndObject, Follow, Undo}, @@ -22,6 +19,7 @@ use lemmy_db::{ }; use lemmy_structs::blocking; use lemmy_utils::{location_info, LemmyError}; +use lemmy_websocket::LemmyContext; use log::debug; use serde::{Deserialize, Serialize}; use std::fmt::Debug; diff --git a/src/apub/inbox/mod.rs b/lemmy_apub/src/inbox/mod.rs similarity index 100% rename from src/apub/inbox/mod.rs rename to lemmy_apub/src/inbox/mod.rs diff --git a/src/apub/inbox/shared_inbox.rs b/lemmy_apub/src/inbox/shared_inbox.rs similarity index 83% rename from src/apub/inbox/shared_inbox.rs rename to lemmy_apub/src/inbox/shared_inbox.rs index da7951082..b9077ebe9 100644 --- a/src/apub/inbox/shared_inbox.rs +++ b/lemmy_apub/src/inbox/shared_inbox.rs @@ -1,26 +1,23 @@ use crate::{ - apub::{ - check_is_apub_id_valid, - community::do_announce, - extensions::signatures::verify, - fetcher::{ - get_or_fetch_and_upsert_actor, - get_or_fetch_and_upsert_community, - get_or_fetch_and_upsert_user, - }, - inbox::activities::{ - announce::receive_announce, - create::receive_create, - delete::receive_delete, - dislike::receive_dislike, - like::receive_like, - remove::receive_remove, - undo::receive_undo, - update::receive_update, - }, - insert_activity, + check_is_apub_id_valid, + community::do_announce, + extensions::signatures::verify, + fetcher::{ + get_or_fetch_and_upsert_actor, + get_or_fetch_and_upsert_community, + get_or_fetch_and_upsert_user, }, - LemmyContext, + inbox::activities::{ + announce::receive_announce, + create::receive_create, + delete::receive_delete, + dislike::receive_dislike, + like::receive_like, + remove::receive_remove, + undo::receive_undo, + update::receive_update, + }, + insert_activity, }; use activitystreams::{ activity::{ActorAndObject, ActorAndObjectRef}, @@ -32,6 +29,7 @@ use actix_web::{web, HttpRequest, HttpResponse}; use anyhow::Context; use lemmy_db::user::User_; use lemmy_utils::{location_info, LemmyError}; +use lemmy_websocket::LemmyContext; use log::debug; use serde::{Deserialize, Serialize}; use std::fmt::Debug; @@ -97,7 +95,7 @@ pub async fn shared_inbox( res } -pub(in crate::apub::inbox) fn receive_unhandled_activity( +pub(in crate::inbox) fn receive_unhandled_activity( activity: A, ) -> Result where @@ -107,7 +105,7 @@ where Ok(HttpResponse::NotImplemented().finish()) } -pub(in crate::apub::inbox) async fn get_user_from_activity( +pub(in crate::inbox) async fn get_user_from_activity( activity: &T, context: &LemmyContext, ) -> Result @@ -119,7 +117,7 @@ where get_or_fetch_and_upsert_user(&user_uri, context).await } -pub(in crate::apub::inbox) fn get_community_id_from_activity( +pub(in crate::inbox) fn get_community_id_from_activity( activity: &T, ) -> Result where @@ -136,7 +134,7 @@ where ) } -pub(in crate::apub::inbox) async fn announce_if_community_is_local( +pub(in crate::inbox) async fn announce_if_community_is_local( activity: T, user: &User_, context: &LemmyContext, diff --git a/src/apub/inbox/user_inbox.rs b/lemmy_apub/src/inbox/user_inbox.rs similarity index 96% rename from src/apub/inbox/user_inbox.rs rename to lemmy_apub/src/inbox/user_inbox.rs index 5cbbedb6b..a7050c45c 100644 --- a/src/apub/inbox/user_inbox.rs +++ b/lemmy_apub/src/inbox/user_inbox.rs @@ -1,12 +1,9 @@ use crate::{ - apub::{ - check_is_apub_id_valid, - extensions::signatures::verify, - fetcher::{get_or_fetch_and_upsert_actor, get_or_fetch_and_upsert_community}, - insert_activity, - FromApub, - }, - LemmyContext, + check_is_apub_id_valid, + extensions::signatures::verify, + fetcher::{get_or_fetch_and_upsert_actor, get_or_fetch_and_upsert_community}, + insert_activity, + FromApub, }; use activitystreams::{ activity::{Accept, ActorAndObject, Create, Delete, Undo, Update}, @@ -25,12 +22,9 @@ use lemmy_db::{ Crud, Followable, }; -use lemmy_structs::{ - blocking, - user::PrivateMessageResponse, - websocket::{SendUserRoomMessage, UserOperation}, -}; +use lemmy_structs::{blocking, user::PrivateMessageResponse}; use lemmy_utils::{location_info, LemmyError}; +use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation}; use log::debug; use serde::{Deserialize, Serialize}; use std::fmt::Debug; diff --git a/src/apub/mod.rs b/lemmy_apub/src/lib.rs similarity index 96% rename from src/apub/mod.rs rename to lemmy_apub/src/lib.rs index 5fd5da364..22eb9fbe0 100644 --- a/src/apub/mod.rs +++ b/lemmy_apub/src/lib.rs @@ -1,3 +1,6 @@ +#[macro_use] +extern crate lazy_static; + pub mod activities; pub mod activity_queue; pub mod comment; @@ -9,16 +12,10 @@ pub mod post; pub mod private_message; pub mod user; -use crate::{ - apub::extensions::{ - group_extensions::GroupExtension, - page_extension::PageExtension, - signatures::{PublicKey, PublicKeyExtension}, - }, - request::{retry, RecvError}, - routes::webfinger::WebFingerResponse, - DbPool, - LemmyContext, +use crate::extensions::{ + group_extensions::GroupExtension, + page_extension::PageExtension, + signatures::{PublicKey, PublicKeyExtension}, }; use activitystreams::{ activity::Follow, @@ -32,15 +29,17 @@ use activitystreams_ext::{Ext1, Ext2}; use actix_web::{body::Body, HttpResponse}; use anyhow::{anyhow, Context}; use chrono::NaiveDateTime; -use lemmy_db::{activity::do_insert_activity, user::User_}; -use lemmy_structs::blocking; +use lemmy_db::{activity::do_insert_activity, user::User_, DbPool}; +use lemmy_structs::{blocking, WebFingerResponse}; use lemmy_utils::{ apub::get_apub_protocol_string, location_info, + request::{retry, RecvError}, settings::Settings, utils::{convert_datetime, MentionData}, LemmyError, }; +use lemmy_websocket::LemmyContext; use log::debug; use reqwest::Client; use serde::Serialize; @@ -191,7 +190,7 @@ pub trait ApubObjectType { async fn send_undo_remove(&self, mod_: &User_, context: &LemmyContext) -> Result<(), LemmyError>; } -pub(in crate::apub) fn check_actor_domain( +pub(in crate) fn check_actor_domain( apub: &T, expected_domain: Option, ) -> Result diff --git a/src/apub/post.rs b/lemmy_apub/src/post.rs similarity index 97% rename from src/apub/post.rs rename to lemmy_apub/src/post.rs index 85e00fb23..07ecb8f70 100644 --- a/src/apub/post.rs +++ b/lemmy_apub/src/post.rs @@ -1,21 +1,17 @@ use crate::{ - apub::{ - activities::{generate_activity_id, send_activity_to_community}, - check_actor_domain, - create_apub_response, - create_apub_tombstone_response, - create_tombstone, - extensions::page_extension::PageExtension, - fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user}, - ActorType, - ApubLikeableType, - ApubObjectType, - FromApub, - PageExt, - ToApub, - }, - DbPool, - LemmyContext, + activities::{generate_activity_id, send_activity_to_community}, + check_actor_domain, + create_apub_response, + create_apub_tombstone_response, + create_tombstone, + extensions::page_extension::PageExtension, + fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user}, + ActorType, + ApubLikeableType, + ApubObjectType, + FromApub, + PageExt, + ToApub, }; use activitystreams::{ activity::{ @@ -40,6 +36,7 @@ use lemmy_db::{ post::{Post, PostForm}, user::User_, Crud, + DbPool, }; use lemmy_structs::blocking; use lemmy_utils::{ @@ -47,6 +44,7 @@ use lemmy_utils::{ utils::{check_slurs, convert_datetime, remove_slurs}, LemmyError, }; +use lemmy_websocket::LemmyContext; use serde::Deserialize; use url::Url; diff --git a/src/apub/private_message.rs b/lemmy_apub/src/private_message.rs similarity index 95% rename from src/apub/private_message.rs rename to lemmy_apub/src/private_message.rs index 9e0549eeb..d61a7771e 100644 --- a/src/apub/private_message.rs +++ b/lemmy_apub/src/private_message.rs @@ -1,19 +1,15 @@ use crate::{ - apub::{ - activities::generate_activity_id, - activity_queue::send_activity, - check_actor_domain, - check_is_apub_id_valid, - create_tombstone, - fetcher::get_or_fetch_and_upsert_user, - insert_activity, - ActorType, - ApubObjectType, - FromApub, - ToApub, - }, - DbPool, - LemmyContext, + activities::generate_activity_id, + activity_queue::send_activity, + check_actor_domain, + check_is_apub_id_valid, + create_tombstone, + fetcher::get_or_fetch_and_upsert_user, + insert_activity, + ActorType, + ApubObjectType, + FromApub, + ToApub, }; use activitystreams::{ activity::{ @@ -31,9 +27,11 @@ use lemmy_db::{ private_message::{PrivateMessage, PrivateMessageForm}, user::User_, Crud, + DbPool, }; use lemmy_structs::blocking; use lemmy_utils::{location_info, utils::convert_datetime, LemmyError}; +use lemmy_websocket::LemmyContext; use url::Url; #[async_trait::async_trait(?Send)] diff --git a/src/apub/user.rs b/lemmy_apub/src/user.rs similarity index 96% rename from src/apub/user.rs rename to lemmy_apub/src/user.rs index 47b6a58a7..950f59a18 100644 --- a/src/apub/user.rs +++ b/lemmy_apub/src/user.rs @@ -1,18 +1,14 @@ use crate::{ - apub::{ - activities::generate_activity_id, - activity_queue::send_activity, - check_actor_domain, - create_apub_response, - fetcher::get_or_fetch_and_upsert_actor, - insert_activity, - ActorType, - FromApub, - PersonExt, - ToApub, - }, - DbPool, - LemmyContext, + activities::generate_activity_id, + activity_queue::send_activity, + check_actor_domain, + create_apub_response, + fetcher::get_or_fetch_and_upsert_actor, + insert_activity, + ActorType, + FromApub, + PersonExt, + ToApub, }; use activitystreams::{ activity::{ @@ -30,6 +26,7 @@ use anyhow::Context; use lemmy_db::{ naive_now, user::{UserForm, User_}, + DbPool, }; use lemmy_structs::blocking; use lemmy_utils::{ @@ -37,6 +34,7 @@ use lemmy_utils::{ utils::{check_slurs, check_slurs_opt, convert_datetime}, LemmyError, }; +use lemmy_websocket::LemmyContext; use serde::Deserialize; use url::Url; diff --git a/lemmy_db/src/lib.rs b/lemmy_db/src/lib.rs index fc660208d..40f6c3d26 100644 --- a/lemmy_db/src/lib.rs +++ b/lemmy_db/src/lib.rs @@ -4,14 +4,6 @@ extern crate diesel; extern crate strum_macros; #[macro_use] extern crate lazy_static; -extern crate bcrypt; -extern crate chrono; -extern crate log; -extern crate regex; -extern crate serde; -extern crate serde_json; -extern crate sha2; -extern crate strum; use chrono::NaiveDateTime; use diesel::{result::Error, *}; diff --git a/lemmy_rate_limit/src/lib.rs b/lemmy_rate_limit/src/lib.rs index 8f962bbe6..ecb812af8 100644 --- a/lemmy_rate_limit/src/lib.rs +++ b/lemmy_rate_limit/src/lib.rs @@ -1,9 +1,5 @@ #[macro_use] extern crate strum_macros; -extern crate actix_web; -extern crate futures; -extern crate log; -extern crate tokio; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; use futures::future::{ok, Ready}; diff --git a/lemmy_structs/Cargo.toml b/lemmy_structs/Cargo.toml index 58658b924..8cf522c39 100644 --- a/lemmy_structs/Cargo.toml +++ b/lemmy_structs/Cargo.toml @@ -14,8 +14,6 @@ lemmy_utils = { path = "../lemmy_utils" } serde = { version = "1.0", features = ["derive"] } log = "0.4" diesel = "1.4" -actix = "0.10" actix-web = { version = "3.0" } -strum = "0.19" -strum_macros = "0.19" chrono = { version = "0.4", features = ["serde"] } +serde_json = { version = "1.0", features = ["preserve_order"]} diff --git a/lemmy_structs/src/lib.rs b/lemmy_structs/src/lib.rs index f7140205a..3efe0bead 100644 --- a/lemmy_structs/src/lib.rs +++ b/lemmy_structs/src/lib.rs @@ -1,12 +1,3 @@ -extern crate actix; -extern crate actix_web; -extern crate diesel; -extern crate log; -extern crate serde; -#[macro_use] -extern crate strum_macros; -extern crate chrono; - pub mod comment; pub mod community; pub mod post; @@ -25,6 +16,24 @@ use lemmy_db::{ }; use lemmy_utils::{email::send_email, settings::Settings, utils::MentionData, LemmyError}; use log::error; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct WebFingerLink { + pub rel: Option, + #[serde(rename(serialize = "type", deserialize = "type"))] + pub type_: Option, + pub href: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub template: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct WebFingerResponse { + pub subject: String, + pub aliases: Vec, + pub links: Vec, +} pub async fn blocking(pool: &DbPool, f: F) -> Result where diff --git a/lemmy_structs/src/websocket.rs b/lemmy_structs/src/websocket.rs index c5c6c5d60..8b1378917 100644 --- a/lemmy_structs/src/websocket.rs +++ b/lemmy_structs/src/websocket.rs @@ -1,195 +1 @@ -use crate::{comment::CommentResponse, post::PostResponse}; -use actix::{prelude::*, Recipient}; -use lemmy_utils::{CommunityId, ConnectionId, IPAddr, PostId, UserId}; -use serde::{Deserialize, Serialize}; -#[derive(EnumString, ToString, Debug, Clone)] -pub enum UserOperation { - Login, - Register, - GetCaptcha, - CreateCommunity, - CreatePost, - ListCommunities, - ListCategories, - GetPost, - GetCommunity, - CreateComment, - EditComment, - DeleteComment, - RemoveComment, - MarkCommentAsRead, - SaveComment, - CreateCommentLike, - GetPosts, - CreatePostLike, - EditPost, - DeletePost, - RemovePost, - LockPost, - StickyPost, - SavePost, - EditCommunity, - DeleteCommunity, - RemoveCommunity, - FollowCommunity, - GetFollowedCommunities, - GetUserDetails, - GetReplies, - GetUserMentions, - MarkUserMentionAsRead, - GetModlog, - BanFromCommunity, - AddModToCommunity, - CreateSite, - EditSite, - GetSite, - AddAdmin, - BanUser, - Search, - MarkAllAsRead, - SaveUserSettings, - TransferCommunity, - TransferSite, - DeleteAccount, - PasswordReset, - PasswordChange, - CreatePrivateMessage, - EditPrivateMessage, - DeletePrivateMessage, - MarkPrivateMessageAsRead, - GetPrivateMessages, - UserJoin, - GetComments, - GetSiteConfig, - SaveSiteConfig, - PostJoin, - CommunityJoin, -} - -/// Chat server sends this messages to session -#[derive(Message)] -#[rtype(result = "()")] -pub struct WSMessage(pub String); - -/// Message for chat server communications - -/// New chat session is created -#[derive(Message)] -#[rtype(usize)] -pub struct Connect { - pub addr: Recipient, - pub ip: IPAddr, -} - -/// Session is disconnected -#[derive(Message)] -#[rtype(result = "()")] -pub struct Disconnect { - pub id: ConnectionId, - pub ip: IPAddr, -} - -/// The messages sent to websocket clients -#[derive(Serialize, Deserialize, Message)] -#[rtype(result = "Result")] -pub struct StandardMessage { - /// Id of the client session - pub id: ConnectionId, - /// Peer message - pub msg: String, -} - -#[derive(Message)] -#[rtype(result = "()")] -pub struct SendAllMessage { - pub op: UserOperation, - pub response: Response, - pub websocket_id: Option, -} - -#[derive(Message)] -#[rtype(result = "()")] -pub struct SendUserRoomMessage { - pub op: UserOperation, - pub response: Response, - pub recipient_id: UserId, - pub websocket_id: Option, -} - -#[derive(Message)] -#[rtype(result = "()")] -pub struct SendCommunityRoomMessage { - pub op: UserOperation, - pub response: Response, - pub community_id: CommunityId, - pub websocket_id: Option, -} - -#[derive(Message)] -#[rtype(result = "()")] -pub struct SendPost { - pub op: UserOperation, - pub post: PostResponse, - pub websocket_id: Option, -} - -#[derive(Message)] -#[rtype(result = "()")] -pub struct SendComment { - pub op: UserOperation, - pub comment: CommentResponse, - pub websocket_id: Option, -} - -#[derive(Message)] -#[rtype(result = "()")] -pub struct JoinUserRoom { - pub user_id: UserId, - pub id: ConnectionId, -} - -#[derive(Message)] -#[rtype(result = "()")] -pub struct JoinCommunityRoom { - pub community_id: CommunityId, - pub id: ConnectionId, -} - -#[derive(Message)] -#[rtype(result = "()")] -pub struct JoinPostRoom { - pub post_id: PostId, - pub id: ConnectionId, -} - -#[derive(Message)] -#[rtype(usize)] -pub struct GetUsersOnline; - -#[derive(Message)] -#[rtype(usize)] -pub struct GetPostUsersOnline { - pub post_id: PostId, -} - -#[derive(Message)] -#[rtype(usize)] -pub struct GetCommunityUsersOnline { - pub community_id: CommunityId, -} - -#[derive(Message, Debug)] -#[rtype(result = "()")] -pub struct CaptchaItem { - pub uuid: String, - pub answer: String, - pub expires: chrono::NaiveDateTime, -} - -#[derive(Message)] -#[rtype(bool)] -pub struct CheckCaptcha { - pub uuid: String, - pub answer: String, -} diff --git a/lemmy_utils/Cargo.toml b/lemmy_utils/Cargo.toml index 9ff9d7d34..a575bb18a 100644 --- a/lemmy_utils/Cargo.toml +++ b/lemmy_utils/Cargo.toml @@ -7,8 +7,6 @@ edition = "2018" name = "lemmy_utils" path = "src/lib.rs" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] regex = "1.3" config = { version = "0.10", default-features = false, features = ["hjson"] } @@ -27,3 +25,4 @@ openssl = "0.10" url = { version = "2.1", features = ["serde"] } actix-web = { version = "3.0", default-features = false, features = ["rustls"] } anyhow = "1.0" +reqwest = { version = "0.10", features = ["json"] } diff --git a/lemmy_utils/src/lib.rs b/lemmy_utils/src/lib.rs index 247705e42..19e7bb839 100644 --- a/lemmy_utils/src/lib.rs +++ b/lemmy_utils/src/lib.rs @@ -1,19 +1,9 @@ #[macro_use] extern crate lazy_static; -extern crate actix_web; -extern crate anyhow; -extern crate comrak; -extern crate lettre; -extern crate lettre_email; -extern crate openssl; -extern crate rand; -extern crate regex; -extern crate serde_json; -extern crate thiserror; -extern crate url; pub mod apub; pub mod email; +pub mod request; pub mod settings; #[cfg(test)] mod test; diff --git a/src/request.rs b/lemmy_utils/src/request.rs similarity index 97% rename from src/request.rs rename to lemmy_utils/src/request.rs index 137848f2a..490609e7d 100644 --- a/src/request.rs +++ b/lemmy_utils/src/request.rs @@ -1,5 +1,5 @@ +use crate::LemmyError; use anyhow::anyhow; -use lemmy_utils::LemmyError; use std::future::Future; use thiserror::Error; diff --git a/lemmy_websocket/Cargo.toml b/lemmy_websocket/Cargo.toml new file mode 100644 index 000000000..c61da426f --- /dev/null +++ b/lemmy_websocket/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "lemmy_websocket" +version = "0.1.0" +authors = ["Felix Ableitner "] +edition = "2018" + +[lib] +name = "lemmy_websocket" +path = "src/lib.rs" + +[dependencies] +lemmy_utils = { path = "../lemmy_utils" } +lemmy_structs = { path = "../lemmy_structs" } +lemmy_db = { path = "../lemmy_db" } +lemmy_rate_limit = { path = "../lemmy_rate_limit" } +reqwest = { version = "0.10", features = ["json"] } +log = "0.4" +rand = "0.7" +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0", features = ["preserve_order"]} +actix = "0.10" +anyhow = "1.0" +diesel = "1.4" +background-jobs = " 0.8" +tokio = "0.2" +strum = "0.19" +strum_macros = "0.19" +chrono = { version = "0.4", features = ["serde"] } diff --git a/src/websocket/chat_server.rs b/lemmy_websocket/src/chat_server.rs similarity index 58% rename from src/websocket/chat_server.rs rename to lemmy_websocket/src/chat_server.rs index 611f44f97..8346a32f6 100644 --- a/src/websocket/chat_server.rs +++ b/lemmy_websocket/src/chat_server.rs @@ -1,7 +1,4 @@ -use crate::{ - websocket::handlers::{do_user_operation, to_json_string, Args}, - LemmyContext, -}; +use crate::{messages::*, serialize_websocket_message, LemmyContext, UserOperation}; use actix::prelude::*; use anyhow::Context as acontext; use background_jobs::QueueHandle; @@ -10,7 +7,7 @@ use diesel::{ PgConnection, }; use lemmy_rate_limit::RateLimit; -use lemmy_structs::{comment::*, community::*, post::*, site::*, user::*, websocket::*}; +use lemmy_structs::{comment::*, post::*}; use lemmy_utils::{ location_info, APIError, @@ -29,6 +26,14 @@ use std::{ collections::{HashMap, HashSet}, str::FromStr, }; +use tokio::macros::support::Pin; + +type MessageHandlerType = fn( + context: LemmyContext, + id: ConnectionId, + op: UserOperation, + data: &str, +) -> Pin> + '_>>; /// `ChatServer` manages chat rooms and responsible for coordinating chat /// session. @@ -57,6 +62,8 @@ pub struct ChatServer { /// A list of the current captchas pub(super) captchas: Vec, + message_handler: MessageHandlerType, + /// An HTTP Client client: Client, @@ -75,6 +82,7 @@ impl ChatServer { pub fn startup( pool: Pool>, rate_limiter: RateLimit, + message_handler: MessageHandlerType, client: Client, activity_queue: QueueHandle, ) -> ChatServer { @@ -87,6 +95,7 @@ impl ChatServer { pool, rate_limiter, captchas: Vec::new(), + message_handler, client, activity_queue, } @@ -180,7 +189,7 @@ impl ChatServer { where Response: Serialize, { - let res_str = &to_json_string(op, response)?; + let res_str = &serialize_websocket_message(op, response)?; if let Some(sessions) = self.post_rooms.get(&post_id) { for id in sessions { if let Some(my_id) = websocket_id { @@ -204,7 +213,7 @@ impl ChatServer { where Response: Serialize, { - let res_str = &to_json_string(op, response)?; + let res_str = &serialize_websocket_message(op, response)?; if let Some(sessions) = self.community_rooms.get(&community_id) { for id in sessions { if let Some(my_id) = websocket_id { @@ -227,7 +236,7 @@ impl ChatServer { where Response: Serialize, { - let res_str = &to_json_string(op, response)?; + let res_str = &serialize_websocket_message(op, response)?; for id in self.sessions.keys() { if let Some(my_id) = websocket_id { if *id == my_id { @@ -249,7 +258,7 @@ impl ChatServer { where Response: Serialize, { - let res_str = &to_json_string(op, response)?; + let res_str = &serialize_websocket_message(op, response)?; if let Some(sessions) = self.user_rooms.get(&recipient_id) { for id in sessions { if let Some(my_id) = websocket_id { @@ -340,8 +349,6 @@ impl ChatServer { msg: StandardMessage, ctx: &mut Context, ) -> impl Future> { - let addr = ctx.address(); - let pool = self.pool.clone(); let rate_limiter = self.rate_limiter.clone(); let ip: IPAddr = match self.sessions.get(&msg.id) { @@ -349,110 +356,27 @@ impl ChatServer { None => "blank_ip".to_string(), }; - let client = self.client.clone(); - let activity_queue = self.activity_queue.clone(); + let context = LemmyContext { + pool: self.pool.clone(), + chat_server: ctx.address(), + client: self.client.to_owned(), + activity_queue: self.activity_queue.to_owned(), + }; + let message_handler = self.message_handler; async move { - let msg = msg; let json: Value = serde_json::from_str(&msg.msg)?; let data = &json["data"].to_string(); let op = &json["op"].as_str().ok_or(APIError { message: "Unknown op type".to_string(), })?; - let user_operation: UserOperation = UserOperation::from_str(&op)?; - - let context = LemmyContext::new(pool, addr, client, activity_queue); - let args = Args { - context, - rate_limiter, - id: msg.id, - ip, - op: user_operation.clone(), - data, - }; - + let user_operation = UserOperation::from_str(&op)?; + let fut = (message_handler)(context, msg.id, user_operation.clone(), data); match user_operation { - // User ops - UserOperation::Login => do_user_operation::(args).await, - UserOperation::Register => do_user_operation::(args).await, - UserOperation::GetCaptcha => do_user_operation::(args).await, - UserOperation::GetUserDetails => do_user_operation::(args).await, - UserOperation::GetReplies => do_user_operation::(args).await, - UserOperation::AddAdmin => do_user_operation::(args).await, - UserOperation::BanUser => do_user_operation::(args).await, - UserOperation::GetUserMentions => do_user_operation::(args).await, - UserOperation::MarkUserMentionAsRead => { - do_user_operation::(args).await - } - UserOperation::MarkAllAsRead => do_user_operation::(args).await, - UserOperation::DeleteAccount => do_user_operation::(args).await, - UserOperation::PasswordReset => do_user_operation::(args).await, - UserOperation::PasswordChange => do_user_operation::(args).await, - UserOperation::UserJoin => do_user_operation::(args).await, - UserOperation::PostJoin => do_user_operation::(args).await, - UserOperation::CommunityJoin => do_user_operation::(args).await, - UserOperation::SaveUserSettings => do_user_operation::(args).await, - - // Private Message ops - UserOperation::CreatePrivateMessage => { - do_user_operation::(args).await - } - UserOperation::EditPrivateMessage => do_user_operation::(args).await, - UserOperation::DeletePrivateMessage => { - do_user_operation::(args).await - } - UserOperation::MarkPrivateMessageAsRead => { - do_user_operation::(args).await - } - UserOperation::GetPrivateMessages => do_user_operation::(args).await, - - // Site ops - UserOperation::GetModlog => do_user_operation::(args).await, - UserOperation::CreateSite => do_user_operation::(args).await, - UserOperation::EditSite => do_user_operation::(args).await, - UserOperation::GetSite => do_user_operation::(args).await, - UserOperation::GetSiteConfig => do_user_operation::(args).await, - UserOperation::SaveSiteConfig => do_user_operation::(args).await, - UserOperation::Search => do_user_operation::(args).await, - UserOperation::TransferCommunity => do_user_operation::(args).await, - UserOperation::TransferSite => do_user_operation::(args).await, - UserOperation::ListCategories => do_user_operation::(args).await, - - // Community ops - UserOperation::GetCommunity => do_user_operation::(args).await, - UserOperation::ListCommunities => do_user_operation::(args).await, - UserOperation::CreateCommunity => do_user_operation::(args).await, - UserOperation::EditCommunity => do_user_operation::(args).await, - UserOperation::DeleteCommunity => do_user_operation::(args).await, - UserOperation::RemoveCommunity => do_user_operation::(args).await, - UserOperation::FollowCommunity => do_user_operation::(args).await, - UserOperation::GetFollowedCommunities => { - do_user_operation::(args).await - } - UserOperation::BanFromCommunity => do_user_operation::(args).await, - UserOperation::AddModToCommunity => do_user_operation::(args).await, - - // Post ops - UserOperation::CreatePost => do_user_operation::(args).await, - UserOperation::GetPost => do_user_operation::(args).await, - UserOperation::GetPosts => do_user_operation::(args).await, - UserOperation::EditPost => do_user_operation::(args).await, - UserOperation::DeletePost => do_user_operation::(args).await, - UserOperation::RemovePost => do_user_operation::(args).await, - UserOperation::LockPost => do_user_operation::(args).await, - UserOperation::StickyPost => do_user_operation::(args).await, - UserOperation::CreatePostLike => do_user_operation::(args).await, - UserOperation::SavePost => do_user_operation::(args).await, - - // Comment ops - UserOperation::CreateComment => do_user_operation::(args).await, - UserOperation::EditComment => do_user_operation::(args).await, - UserOperation::DeleteComment => do_user_operation::(args).await, - UserOperation::RemoveComment => do_user_operation::(args).await, - UserOperation::MarkCommentAsRead => do_user_operation::(args).await, - UserOperation::SaveComment => do_user_operation::(args).await, - UserOperation::GetComments => do_user_operation::(args).await, - UserOperation::CreateCommentLike => do_user_operation::(args).await, + UserOperation::Register => rate_limiter.register().wrap(ip, fut).await, + UserOperation::CreatePost => rate_limiter.post().wrap(ip, fut).await, + UserOperation::CreateCommunity => rate_limiter.register().wrap(ip, fut).await, + _ => rate_limiter.message().wrap(ip, fut).await, } } } diff --git a/src/websocket/handlers.rs b/lemmy_websocket/src/handlers.rs similarity index 76% rename from src/websocket/handlers.rs rename to lemmy_websocket/src/handlers.rs index 75e638f94..258098d62 100644 --- a/src/websocket/handlers.rs +++ b/lemmy_websocket/src/handlers.rs @@ -1,59 +1,12 @@ use crate::{ - api::Perform, - websocket::chat_server::{ChatServer, SessionInfo}, - LemmyContext, + chat_server::{ChatServer, SessionInfo}, + messages::*, }; use actix::{Actor, Context, Handler, ResponseFuture}; -use actix_web::web; use lemmy_db::naive_now; -use lemmy_rate_limit::RateLimit; -use lemmy_structs::websocket::*; -use lemmy_utils::{ConnectionId, IPAddr, LemmyError}; use log::{error, info}; use rand::Rng; -use serde::{Deserialize, Serialize}; - -pub(super) struct Args<'a> { - pub(super) context: LemmyContext, - pub(super) rate_limiter: RateLimit, - pub(super) id: ConnectionId, - pub(super) ip: IPAddr, - pub(super) op: UserOperation, - pub(super) data: &'a str, -} - -pub(super) async fn do_user_operation<'a, 'b, Data>(args: Args<'b>) -> Result -where - for<'de> Data: Deserialize<'de> + 'a, - Data: Perform, -{ - let Args { - context, - rate_limiter, - id, - ip, - op, - data, - } = args; - - let data = data.to_string(); - let op2 = op.clone(); - - let fut = async move { - let parsed_data: Data = serde_json::from_str(&data)?; - let res = parsed_data - .perform(&web::Data::new(context), Some(id)) - .await?; - to_json_string(&op, &res) - }; - - match op2 { - UserOperation::Register => rate_limiter.register().wrap(ip, fut).await, - UserOperation::CreatePost => rate_limiter.post().wrap(ip, fut).await, - UserOperation::CreateCommunity => rate_limiter.register().wrap(ip, fut).await, - _ => rate_limiter.message().wrap(ip, fut).await, - } -} +use serde::Serialize; /// Make actor from `ChatServer` impl Actor for ChatServer { @@ -241,26 +194,6 @@ impl Handler for ChatServer { } } -#[derive(Serialize)] -struct WebsocketResponse { - op: String, - data: T, -} - -pub(super) fn to_json_string( - op: &UserOperation, - data: &Response, -) -> Result -where - Response: Serialize, -{ - let response = WebsocketResponse { - op: op.to_string(), - data, - }; - Ok(serde_json::to_string(&response)?) -} - impl Handler for ChatServer { type Result = (); diff --git a/lemmy_websocket/src/lib.rs b/lemmy_websocket/src/lib.rs new file mode 100644 index 000000000..26b00a061 --- /dev/null +++ b/lemmy_websocket/src/lib.rs @@ -0,0 +1,144 @@ +#[macro_use] +extern crate strum_macros; + +use crate::chat_server::ChatServer; +use actix::Addr; +use background_jobs::QueueHandle; +use lemmy_db::DbPool; +use lemmy_utils::LemmyError; +use reqwest::Client; +use serde::Serialize; + +pub mod chat_server; +pub mod handlers; +pub mod messages; + +pub struct LemmyContext { + pub pool: DbPool, + pub chat_server: Addr, + pub client: Client, + pub activity_queue: QueueHandle, +} + +impl LemmyContext { + pub fn create( + pool: DbPool, + chat_server: Addr, + client: Client, + activity_queue: QueueHandle, + ) -> LemmyContext { + LemmyContext { + pool, + chat_server, + client, + activity_queue, + } + } + pub fn pool(&self) -> &DbPool { + &self.pool + } + pub fn chat_server(&self) -> &Addr { + &self.chat_server + } + pub fn client(&self) -> &Client { + &self.client + } + pub fn activity_queue(&self) -> &QueueHandle { + &self.activity_queue + } +} + +impl Clone for LemmyContext { + fn clone(&self) -> Self { + LemmyContext { + pool: self.pool.clone(), + chat_server: self.chat_server.clone(), + client: self.client.clone(), + activity_queue: self.activity_queue.clone(), + } + } +} + +#[derive(Serialize)] +struct WebsocketResponse { + op: String, + data: T, +} + +pub fn serialize_websocket_message( + op: &UserOperation, + data: &Response, +) -> Result +where + Response: Serialize, +{ + let response = WebsocketResponse { + op: op.to_string(), + data, + }; + Ok(serde_json::to_string(&response)?) +} + +#[derive(EnumString, ToString, Debug, Clone)] +pub enum UserOperation { + Login, + Register, + GetCaptcha, + CreateCommunity, + CreatePost, + ListCommunities, + ListCategories, + GetPost, + GetCommunity, + CreateComment, + EditComment, + DeleteComment, + RemoveComment, + MarkCommentAsRead, + SaveComment, + CreateCommentLike, + GetPosts, + CreatePostLike, + EditPost, + DeletePost, + RemovePost, + LockPost, + StickyPost, + SavePost, + EditCommunity, + DeleteCommunity, + RemoveCommunity, + FollowCommunity, + GetFollowedCommunities, + GetUserDetails, + GetReplies, + GetUserMentions, + MarkUserMentionAsRead, + GetModlog, + BanFromCommunity, + AddModToCommunity, + CreateSite, + EditSite, + GetSite, + AddAdmin, + BanUser, + Search, + MarkAllAsRead, + SaveUserSettings, + TransferCommunity, + TransferSite, + DeleteAccount, + PasswordReset, + PasswordChange, + CreatePrivateMessage, + EditPrivateMessage, + DeletePrivateMessage, + MarkPrivateMessageAsRead, + GetPrivateMessages, + UserJoin, + GetComments, + GetSiteConfig, + SaveSiteConfig, + PostJoin, + CommunityJoin, +} diff --git a/lemmy_websocket/src/messages.rs b/lemmy_websocket/src/messages.rs new file mode 100644 index 000000000..d9f8320a8 --- /dev/null +++ b/lemmy_websocket/src/messages.rs @@ -0,0 +1,132 @@ +use crate::UserOperation; +use actix::{prelude::*, Recipient}; +use lemmy_structs::{comment::CommentResponse, post::PostResponse}; +use lemmy_utils::{CommunityId, ConnectionId, IPAddr, PostId, UserId}; +use serde::{Deserialize, Serialize}; + +/// Chat server sends this messages to session +#[derive(Message)] +#[rtype(result = "()")] +pub struct WSMessage(pub String); + +/// Message for chat server communications + +/// New chat session is created +#[derive(Message)] +#[rtype(usize)] +pub struct Connect { + pub addr: Recipient, + pub ip: IPAddr, +} + +/// Session is disconnected +#[derive(Message)] +#[rtype(result = "()")] +pub struct Disconnect { + pub id: ConnectionId, + pub ip: IPAddr, +} + +/// The messages sent to websocket clients +#[derive(Serialize, Deserialize, Message)] +#[rtype(result = "Result")] +pub struct StandardMessage { + /// Id of the client session + pub id: ConnectionId, + /// Peer message + pub msg: String, +} + +#[derive(Message)] +#[rtype(result = "()")] +pub struct SendAllMessage { + pub op: UserOperation, + pub response: Response, + pub websocket_id: Option, +} + +#[derive(Message)] +#[rtype(result = "()")] +pub struct SendUserRoomMessage { + pub op: UserOperation, + pub response: Response, + pub recipient_id: UserId, + pub websocket_id: Option, +} + +#[derive(Message)] +#[rtype(result = "()")] +pub struct SendCommunityRoomMessage { + pub op: UserOperation, + pub response: Response, + pub community_id: CommunityId, + pub websocket_id: Option, +} + +#[derive(Message)] +#[rtype(result = "()")] +pub struct SendPost { + pub op: UserOperation, + pub post: PostResponse, + pub websocket_id: Option, +} + +#[derive(Message)] +#[rtype(result = "()")] +pub struct SendComment { + pub op: UserOperation, + pub comment: CommentResponse, + pub websocket_id: Option, +} + +#[derive(Message)] +#[rtype(result = "()")] +pub struct JoinUserRoom { + pub user_id: UserId, + pub id: ConnectionId, +} + +#[derive(Message)] +#[rtype(result = "()")] +pub struct JoinCommunityRoom { + pub community_id: CommunityId, + pub id: ConnectionId, +} + +#[derive(Message)] +#[rtype(result = "()")] +pub struct JoinPostRoom { + pub post_id: PostId, + pub id: ConnectionId, +} + +#[derive(Message)] +#[rtype(usize)] +pub struct GetUsersOnline; + +#[derive(Message)] +#[rtype(usize)] +pub struct GetPostUsersOnline { + pub post_id: PostId, +} + +#[derive(Message)] +#[rtype(usize)] +pub struct GetCommunityUsersOnline { + pub community_id: CommunityId, +} + +#[derive(Message, Debug)] +#[rtype(result = "()")] +pub struct CaptchaItem { + pub uuid: String, + pub answer: String, + pub expires: chrono::NaiveDateTime, +} + +#[derive(Message)] +#[rtype(bool)] +pub struct CheckCaptcha { + pub uuid: String, + pub answer: String, +} diff --git a/src/api/mod.rs b/src/api/mod.rs deleted file mode 100644 index d63f38eb8..000000000 --- a/src/api/mod.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::{api::claims::Claims, DbPool, LemmyContext}; -use actix_web::web::Data; -use lemmy_db::{ - community::Community, - community_view::CommunityUserBanView, - post::Post, - user::User_, - Crud, -}; -use lemmy_structs::blocking; -use lemmy_utils::{APIError, ConnectionId, LemmyError}; - -pub mod claims; -pub mod comment; -pub mod community; -pub mod post; -pub mod site; -pub mod user; - -#[async_trait::async_trait(?Send)] -pub trait Perform { - type Response: serde::ser::Serialize + Send; - - async fn perform( - &self, - context: &Data, - websocket_id: Option, - ) -> Result; -} - -pub(in crate::api) async fn is_mod_or_admin( - pool: &DbPool, - user_id: i32, - community_id: i32, -) -> Result<(), LemmyError> { - let is_mod_or_admin = blocking(pool, move |conn| { - Community::is_mod_or_admin(conn, user_id, community_id) - }) - .await?; - if !is_mod_or_admin { - return Err(APIError::err("not_a_mod_or_admin").into()); - } - Ok(()) -} -pub async fn is_admin(pool: &DbPool, user_id: i32) -> Result<(), LemmyError> { - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if !user.admin { - return Err(APIError::err("not_an_admin").into()); - } - Ok(()) -} - -pub(in crate::api) async fn get_post(post_id: i32, pool: &DbPool) -> Result { - match blocking(pool, move |conn| Post::read(conn, post_id)).await? { - Ok(post) => Ok(post), - Err(_e) => Err(APIError::err("couldnt_find_post").into()), - } -} - -pub(in crate::api) async fn get_user_from_jwt( - jwt: &str, - pool: &DbPool, -) -> Result { - let claims = match Claims::decode(&jwt) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - let user_id = claims.id; - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - // Check for a site ban - if user.banned { - return Err(APIError::err("site_ban").into()); - } - Ok(user) -} - -pub(in crate::api) async fn get_user_from_jwt_opt( - jwt: &Option, - pool: &DbPool, -) -> Result, LemmyError> { - match jwt { - Some(jwt) => Ok(Some(get_user_from_jwt(jwt, pool).await?)), - None => Ok(None), - } -} - -pub(in crate::api) async fn check_community_ban( - user_id: i32, - community_id: i32, - pool: &DbPool, -) -> Result<(), LemmyError> { - let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); - if blocking(pool, is_banned).await? { - Err(APIError::err("community_ban").into()) - } else { - Ok(()) - } -} diff --git a/src/lib.rs b/src/lib.rs index 5d5f00b0d..7e5754d46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,307 +1,4 @@ #![recursion_limit = "512"] -#[macro_use] -extern crate lazy_static; -extern crate actix; -extern crate actix_web; -extern crate base64; -extern crate bcrypt; -extern crate captcha; -extern crate chrono; -extern crate diesel; -extern crate dotenv; -extern crate jsonwebtoken; -extern crate log; -extern crate openssl; -extern crate reqwest; -extern crate rss; -extern crate serde; -extern crate serde_json; -extern crate sha2; -extern crate strum; -pub mod api; -pub mod apub; pub mod code_migrations; -pub mod request; pub mod routes; -pub mod version; -pub mod websocket; - -use crate::{ - request::{retry, RecvError}, - websocket::chat_server::ChatServer, -}; -use actix::Addr; -use anyhow::anyhow; -use background_jobs::QueueHandle; -use lemmy_db::DbPool; -use lemmy_utils::{apub::get_apub_protocol_string, settings::Settings, LemmyError}; -use log::error; -use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; -use reqwest::Client; -use serde::Deserialize; -use std::process::Command; - -pub struct LemmyContext { - pub pool: DbPool, - pub chat_server: Addr, - pub client: Client, - pub activity_queue: QueueHandle, -} - -impl LemmyContext { - pub fn new( - pool: DbPool, - chat_server: Addr, - client: Client, - activity_queue: QueueHandle, - ) -> LemmyContext { - LemmyContext { - pool, - chat_server, - client, - activity_queue, - } - } - pub fn pool(&self) -> &DbPool { - &self.pool - } - pub fn chat_server(&self) -> &Addr { - &self.chat_server - } - pub fn client(&self) -> &Client { - &self.client - } - pub fn activity_queue(&self) -> &QueueHandle { - &self.activity_queue - } -} - -impl Clone for LemmyContext { - fn clone(&self) -> Self { - LemmyContext::new( - self.pool.clone(), - self.chat_server.clone(), - self.client.clone(), - self.activity_queue.clone(), - ) - } -} - -#[derive(Deserialize, Debug)] -pub struct IframelyResponse { - title: Option, - description: Option, - thumbnail_url: Option, - html: Option, -} - -pub async fn fetch_iframely(client: &Client, url: &str) -> Result { - let fetch_url = format!("http://iframely/oembed?url={}", url); - - let response = retry(|| client.get(&fetch_url).send()).await?; - - let res: IframelyResponse = response - .json() - .await - .map_err(|e| RecvError(e.to_string()))?; - Ok(res) -} - -#[derive(Deserialize, Debug, Clone)] -pub struct PictrsResponse { - files: Vec, - msg: String, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct PictrsFile { - file: String, - delete_token: String, -} - -pub async fn fetch_pictrs(client: &Client, image_url: &str) -> Result { - is_image_content_type(client, image_url).await?; - - let fetch_url = format!( - "http://pictrs:8080/image/download?url={}", - utf8_percent_encode(image_url, NON_ALPHANUMERIC) // TODO this might not be needed - ); - - let response = retry(|| client.get(&fetch_url).send()).await?; - - let response: PictrsResponse = response - .json() - .await - .map_err(|e| RecvError(e.to_string()))?; - - if response.msg == "ok" { - Ok(response) - } else { - Err(anyhow!("{}", &response.msg).into()) - } -} - -async fn fetch_iframely_and_pictrs_data( - client: &Client, - url: Option, -) -> ( - Option, - Option, - Option, - Option, -) { - match &url { - Some(url) => { - // Fetch iframely data - let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) = - match fetch_iframely(client, url).await { - Ok(res) => (res.title, res.description, res.thumbnail_url, res.html), - Err(e) => { - error!("iframely err: {}", e); - (None, None, None, None) - } - }; - - // Fetch pictrs thumbnail - let pictrs_hash = match iframely_thumbnail_url { - Some(iframely_thumbnail_url) => match fetch_pictrs(client, &iframely_thumbnail_url).await { - Ok(res) => Some(res.files[0].file.to_owned()), - Err(e) => { - error!("pictrs err: {}", e); - None - } - }, - // Try to generate a small thumbnail if iframely is not supported - None => match fetch_pictrs(client, &url).await { - Ok(res) => Some(res.files[0].file.to_owned()), - Err(e) => { - error!("pictrs err: {}", e); - None - } - }, - }; - - // The full urls are necessary for federation - let pictrs_thumbnail = if let Some(pictrs_hash) = pictrs_hash { - Some(format!( - "{}://{}/pictrs/image/{}", - get_apub_protocol_string(), - Settings::get().hostname, - pictrs_hash - )) - } else { - None - }; - - ( - iframely_title, - iframely_description, - iframely_html, - pictrs_thumbnail, - ) - } - None => (None, None, None, None), - } -} - -pub async fn is_image_content_type(client: &Client, test: &str) -> Result<(), LemmyError> { - let response = retry(|| client.get(test).send()).await?; - - if response - .headers() - .get("Content-Type") - .ok_or_else(|| anyhow!("No Content-Type header"))? - .to_str()? - .starts_with("image/") - { - Ok(()) - } else { - Err(anyhow!("Not an image type.").into()) - } -} - -pub fn captcha_espeak_wav_base64(captcha: &str) -> Result { - let mut built_text = String::new(); - - // Building proper speech text for espeak - for mut c in captcha.chars() { - let new_str = if c.is_alphabetic() { - if c.is_lowercase() { - c.make_ascii_uppercase(); - format!("lower case {} ... ", c) - } else { - c.make_ascii_uppercase(); - format!("capital {} ... ", c) - } - } else { - format!("{} ...", c) - }; - - built_text.push_str(&new_str); - } - - espeak_wav_base64(&built_text) -} - -pub fn espeak_wav_base64(text: &str) -> Result { - // Make a temp file path - let uuid = uuid::Uuid::new_v4().to_string(); - let file_path = format!("/tmp/lemmy_espeak_{}.wav", &uuid); - - // Write the wav file - Command::new("espeak") - .arg("-w") - .arg(&file_path) - .arg(text) - .status()?; - - // Read the wav file bytes - let bytes = std::fs::read(&file_path)?; - - // Delete the file - std::fs::remove_file(file_path)?; - - // Convert to base64 - let base64 = base64::encode(bytes); - - Ok(base64) -} - -#[cfg(test)] -mod tests { - use crate::{captcha_espeak_wav_base64, is_image_content_type}; - - #[test] - fn test_image() { - actix_rt::System::new("tset_image").block_on(async move { - let client = reqwest::Client::default(); - assert!(is_image_content_type(&client, "https://1734811051.rsc.cdn77.org/data/images/full/365645/as-virus-kills-navajos-in-their-homes-tribal-women-provide-lifeline.jpg?w=600?w=650").await.is_ok()); - assert!(is_image_content_type(&client, - "https://twitter.com/BenjaminNorton/status/1259922424272957440?s=20" - ) - .await.is_err() - ); - }); - } - - #[test] - fn test_espeak() { - assert!(captcha_espeak_wav_base64("WxRt2l").is_ok()) - } - - // These helped with testing - // #[test] - // fn test_iframely() { - // let res = fetch_iframely(client, "https://www.redspark.nu/?p=15341").await; - // assert!(res.is_ok()); - // } - - // #[test] - // fn test_pictshare() { - // let res = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpg"); - // assert!(res.is_ok()); - // let res_other = fetch_pictshare("https://upload.wikimedia.org/wikipedia/en/2/27/The_Mandalorian_logo.jpgaoeu"); - // assert!(res_other.is_err()); - // } -} diff --git a/src/main.rs b/src/main.rs index d14e006e2..dea60c0a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,17 +16,14 @@ use diesel::{ PgConnection, }; use lazy_static::lazy_static; +use lemmy_api::match_websocket_operation; +use lemmy_apub::activity_queue::create_activity_queue; use lemmy_db::get_database_url_from_env; use lemmy_rate_limit::{rate_limiter::RateLimiter, RateLimit}; -use lemmy_server::{ - apub::activity_queue::create_activity_queue, - code_migrations::run_advanced_migrations, - routes::*, - websocket::chat_server::ChatServer, - LemmyContext, -}; +use lemmy_server::{code_migrations::run_advanced_migrations, routes::*}; use lemmy_structs::blocking; use lemmy_utils::{settings::Settings, LemmyError, CACHE_CONTROL_REGEX}; +use lemmy_websocket::{chat_server::ChatServer, LemmyContext}; use reqwest::Client; use std::sync::Arc; use tokio::sync::Mutex; @@ -77,6 +74,7 @@ async fn main() -> Result<(), LemmyError> { let chat_server = ChatServer::startup( pool.clone(), rate_limiter.clone(), + |c, i, o, d| Box::pin(match_websocket_operation(c, i, o, d)), Client::default(), activity_queue.clone(), ) @@ -84,7 +82,7 @@ async fn main() -> Result<(), LemmyError> { // Create Http server with websocket support HttpServer::new(move || { - let context = LemmyContext::new( + let context = LemmyContext::create( pool.clone(), chat_server.to_owned(), Client::default(), diff --git a/src/routes/api.rs b/src/routes/api.rs index 7e019168b..7a8ddbf1e 100644 --- a/src/routes/api.rs +++ b/src/routes/api.rs @@ -1,7 +1,8 @@ -use crate::{api::Perform, LemmyContext}; use actix_web::{error::ErrorBadRequest, *}; +use lemmy_api::Perform; use lemmy_rate_limit::RateLimit; use lemmy_structs::{comment::*, community::*, post::*, site::*, user::*}; +use lemmy_websocket::LemmyContext; use serde::Deserialize; pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { diff --git a/src/routes/federation.rs b/src/routes/federation.rs index 2a0c81b23..36cfb8518 100644 --- a/src/routes/federation.rs +++ b/src/routes/federation.rs @@ -1,4 +1,6 @@ -use crate::apub::{ +use actix_web::*; +use http_signature_normalization_actix::digest::middleware::VerifyDigest; +use lemmy_apub::{ comment::get_apub_comment, community::*, inbox::{community_inbox::community_inbox, shared_inbox::shared_inbox, user_inbox::user_inbox}, @@ -6,8 +8,6 @@ use crate::apub::{ user::*, APUB_JSON_CONTENT_TYPE, }; -use actix_web::*; -use http_signature_normalization_actix::digest::middleware::VerifyDigest; use lemmy_utils::settings::Settings; use sha2::{Digest, Sha256}; diff --git a/src/routes/feeds.rs b/src/routes/feeds.rs index 9669bfcc0..2c36ac233 100644 --- a/src/routes/feeds.rs +++ b/src/routes/feeds.rs @@ -1,8 +1,8 @@ -use crate::{api::claims::Claims, LemmyContext}; use actix_web::{error::ErrorBadRequest, *}; use anyhow::anyhow; use chrono::{DateTime, NaiveDateTime, Utc}; use diesel::PgConnection; +use lemmy_api::claims::Claims; use lemmy_db::{ comment_view::{ReplyQueryBuilder, ReplyView}, community::Community, @@ -15,6 +15,7 @@ use lemmy_db::{ }; use lemmy_structs::blocking; use lemmy_utils::{settings::Settings, utils::markdown_to_html, LemmyError}; +use lemmy_websocket::LemmyContext; use rss::{CategoryBuilder, ChannelBuilder, GuidBuilder, Item, ItemBuilder}; use serde::Deserialize; use std::str::FromStr; diff --git a/src/routes/nodeinfo.rs b/src/routes/nodeinfo.rs index 15c5a2357..984151c42 100644 --- a/src/routes/nodeinfo.rs +++ b/src/routes/nodeinfo.rs @@ -1,9 +1,10 @@ -use crate::{version, LemmyContext}; use actix_web::{body::Body, error::ErrorBadRequest, *}; use anyhow::anyhow; +use lemmy_api::version; use lemmy_db::site_view::SiteView; use lemmy_structs::blocking; use lemmy_utils::{apub::get_apub_protocol_string, settings::Settings, LemmyError}; +use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; use url::Url; diff --git a/src/routes/webfinger.rs b/src/routes/webfinger.rs index 53a87b124..bef94e6f9 100644 --- a/src/routes/webfinger.rs +++ b/src/routes/webfinger.rs @@ -1,38 +1,21 @@ -use crate::LemmyContext; use actix_web::{error::ErrorBadRequest, web::Query, *}; use anyhow::anyhow; use lemmy_db::{community::Community, user::User_}; -use lemmy_structs::blocking; +use lemmy_structs::{blocking, WebFingerLink, WebFingerResponse}; use lemmy_utils::{ settings::Settings, LemmyError, WEBFINGER_COMMUNITY_REGEX, WEBFINGER_USER_REGEX, }; -use serde::{Deserialize, Serialize}; +use lemmy_websocket::LemmyContext; +use serde::Deserialize; #[derive(Deserialize)] pub struct Params { resource: String, } -#[derive(Serialize, Deserialize, Debug)] -pub struct WebFingerResponse { - pub subject: String, - pub aliases: Vec, - pub links: Vec, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct WebFingerLink { - pub rel: Option, - #[serde(rename(serialize = "type", deserialize = "type"))] - pub type_: Option, - pub href: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub template: Option, -} - pub fn config(cfg: &mut web::ServiceConfig) { if Settings::get().federation.enabled { cfg.route( diff --git a/src/routes/websocket.rs b/src/routes/websocket.rs index aaa7ef37a..9bfd6ca14 100644 --- a/src/routes/websocket.rs +++ b/src/routes/websocket.rs @@ -1,9 +1,12 @@ -use crate::{websocket::chat_server::ChatServer, LemmyContext}; use actix::prelude::*; use actix_web::*; use actix_web_actors::ws; -use lemmy_structs::websocket::{Connect, Disconnect, StandardMessage, WSMessage}; use lemmy_utils::utils::get_ip; +use lemmy_websocket::{ + chat_server::ChatServer, + messages::{Connect, Disconnect, StandardMessage, WSMessage}, + LemmyContext, +}; use log::{debug, error, info}; use std::time::{Duration, Instant}; diff --git a/src/websocket/mod.rs b/src/websocket/mod.rs deleted file mode 100644 index e892f78f8..000000000 --- a/src/websocket/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod chat_server; -pub mod handlers;