diff --git a/Cargo.lock b/Cargo.lock index 69be8e8d1..79ad8f5f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1787,6 +1787,50 @@ 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" @@ -1887,6 +1931,7 @@ dependencies = [ "itertools", "jsonwebtoken", "lazy_static", + "lemmy_api", "lemmy_apub", "lemmy_db", "lemmy_rate_limit", diff --git a/Cargo.toml b/Cargo.toml index ccfc4742d..e66b39a02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ lto = true [workspace] members = [ + "lemmy_api", "lemmy_apub", "lemmy_utils", "lemmy_db", @@ -17,12 +18,13 @@ members = [ ] [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" } -lemmy_apub = { path = "./lemmy_apub" } diesel = "1.4" diesel_migrations = "1.4" bcrypt = "0.8" diff --git a/docker/prod/deploy.sh b/docker/prod/deploy.sh index a0a8dd6c7..8358902d6 100755 --- a/docker/prod/deploy.sh +++ b/docker/prod/deploy.sh @@ -13,8 +13,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..c75abe091 --- /dev/null +++ b/lemmy_api/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "lemmy_api" +version = "0.1.0" +authors = ["Felix Ableitner "] +edition = "2018" + +[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 99% rename from src/api/comment.rs rename to lemmy_api/src/comment.rs index c169f01f5..5a78ba914 100644 --- a/src/api/comment.rs +++ b/lemmy_api/src/comment.rs @@ -1,4 +1,4 @@ -use crate::api::{ +use crate::{ check_community_ban, get_post, get_user_from_jwt, diff --git a/src/api/community.rs b/lemmy_api/src/community.rs similarity index 99% rename from src/api/community.rs rename to lemmy_api/src/community.rs index 0c414f1b6..c9470b2d5 100644 --- a/src/api/community.rs +++ b/lemmy_api/src/community.rs @@ -1,4 +1,4 @@ -use crate::api::{get_user_from_jwt, get_user_from_jwt_opt, is_admin, is_mod_or_admin, Perform}; +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; diff --git a/src/api/mod.rs b/lemmy_api/src/lib.rs similarity index 59% rename from src/api/mod.rs rename to lemmy_api/src/lib.rs index d20631057..905075b81 100644 --- a/src/api/mod.rs +++ b/lemmy_api/src/lib.rs @@ -1,16 +1,29 @@ -use crate::{api::claims::Claims, DbPool}; +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::{APIError, ConnectionId, LemmyError}; +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; @@ -18,6 +31,7 @@ pub mod community; pub mod post; pub mod site; pub mod user; +pub mod version; #[async_trait::async_trait(?Send)] pub trait Perform { @@ -30,7 +44,7 @@ pub trait Perform { ) -> Result; } -pub(in crate::api) async fn is_mod_or_admin( +pub(in crate) async fn is_mod_or_admin( pool: &DbPool, user_id: i32, community_id: i32, @@ -52,17 +66,14 @@ pub async fn is_admin(pool: &DbPool, user_id: i32) -> Result<(), LemmyError> { Ok(()) } -pub(in crate::api) async fn get_post(post_id: i32, pool: &DbPool) -> Result { +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::api) async fn get_user_from_jwt( - jwt: &str, - pool: &DbPool, -) -> Result { +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()), @@ -76,7 +87,7 @@ pub(in crate::api) async fn get_user_from_jwt( Ok(user) } -pub(in crate::api) async fn get_user_from_jwt_opt( +pub(in crate) async fn get_user_from_jwt_opt( jwt: &Option, pool: &DbPool, ) -> Result, LemmyError> { @@ -86,7 +97,7 @@ pub(in crate::api) async fn get_user_from_jwt_opt( } } -pub(in crate::api) async fn check_community_ban( +pub(in crate) async fn check_community_ban( user_id: i32, community_id: i32, pool: &DbPool, @@ -272,3 +283,227 @@ where .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 99% rename from src/api/post.rs rename to lemmy_api/src/post.rs index debf5d7b5..e8fb9d984 100644 --- a/src/api/post.rs +++ b/lemmy_api/src/post.rs @@ -1,6 +1,10 @@ use crate::{ - api::{check_community_ban, get_user_from_jwt, get_user_from_jwt_opt, is_mod_or_admin, Perform}, + check_community_ban, fetch_iframely_and_pictrs_data, + get_user_from_jwt, + get_user_from_jwt_opt, + is_mod_or_admin, + Perform, }; use actix_web::web::Data; use lemmy_apub::{ApubLikeableType, ApubObjectType}; diff --git a/src/api/site.rs b/lemmy_api/src/site.rs similarity index 99% rename from src/api/site.rs rename to lemmy_api/src/site.rs index 963ac1c53..9db838ff1 100644 --- a/src/api/site.rs +++ b/lemmy_api/src/site.rs @@ -1,7 +1,4 @@ -use crate::{ - api::{get_user_from_jwt, get_user_from_jwt_opt, is_admin, Perform}, - version, -}; +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; 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 1e06fcfa9..aad5f2a8a 100644 --- a/src/api/user.rs +++ b/lemmy_api/src/user.rs @@ -1,6 +1,10 @@ use crate::{ - api::{claims::Claims, get_user_from_jwt, get_user_from_jwt_opt, is_admin, Perform}, captcha_espeak_wav_base64, + claims::Claims, + get_user_from_jwt, + get_user_from_jwt_opt, + is_admin, + Perform, }; use actix_web::web::Data; use anyhow::Context; 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/src/lib.rs b/src/lib.rs index d8c31f049..7e5754d46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,238 +1,4 @@ #![recursion_limit = "512"] -pub mod api; pub mod code_migrations; pub mod routes; -pub mod version; - -use anyhow::anyhow; -use lemmy_db::DbPool; -use lemmy_utils::{ - apub::get_apub_protocol_string, - request::{retry, RecvError}, - settings::Settings, - LemmyError, -}; -use log::error; -use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; -use reqwest::Client; -use serde::Deserialize; -use std::process::Command; - -#[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 c37a1f30f..dea60c0a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,14 +16,11 @@ 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::{ - api::match_websocket_operation, - code_migrations::run_advanced_migrations, - routes::*, -}; +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}; diff --git a/src/routes/api.rs b/src/routes/api.rs index d8244fef0..7a8ddbf1e 100644 --- a/src/routes/api.rs +++ b/src/routes/api.rs @@ -1,5 +1,5 @@ -use crate::api::Perform; 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; diff --git a/src/routes/feeds.rs b/src/routes/feeds.rs index 45e1c5374..2c36ac233 100644 --- a/src/routes/feeds.rs +++ b/src/routes/feeds.rs @@ -1,8 +1,8 @@ -use crate::api::claims::Claims; 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, diff --git a/src/routes/nodeinfo.rs b/src/routes/nodeinfo.rs index efa689ca5..984151c42 100644 --- a/src/routes/nodeinfo.rs +++ b/src/routes/nodeinfo.rs @@ -1,6 +1,6 @@ -use crate::version; 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};