Use URL type in most outstanding struct fields (#1468)

* Use URL type in most outstanding struct fields

This fixes all known remaining cases where url fields are stored as
plain strings, with the exception of form fields where empty strings
are used as sentinels (see `diesel_option_overwrite_to_url`).

Tested for regressions in the federated docker setup attempting to
exercise all changed fields, including through apub federation.

Fixes #1385

* Add migration to fix blank-string post.url values to be null

This also then fixes #602

* Address review feedback

- Fixed some unwraps and err message formatting
- Bumped the `url` library to 2.2.1 to fix a bug with serde error
  messages
- Add unit tests for the two diesel option override functions
- Fix migration teardown by adding a no-op

* Rename lemmy_db_queries::Url to lemmy_db_queries::DbUrl

* fix compile error

* box PostOrComment variants
This commit is contained in:
Andrew Yoon 2021-03-02 07:41:48 -05:00 committed by GitHub
parent 45e05dac30
commit e78ba38e94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 312 additions and 265 deletions

4
Cargo.lock generated
View File

@ -3655,9 +3655,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]] [[package]]
name = "url" name = "url"
version = "2.2.0" version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e" checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b"
dependencies = [ dependencies = [
"form_urlencoded", "form_urlencoded",
"idna", "idna",

View File

@ -45,7 +45,7 @@ actix-web = { version = "3.3.2", default-features = false, features = ["rustls"]
log = "0.4.14" log = "0.4.14"
env_logger = "0.8.2" env_logger = "0.8.2"
strum = "0.20.0" strum = "0.20.0"
url = { version = "2.2.0", features = ["serde"] } url = { version = "2.2.1", features = ["serde"] }
openssl = "0.10.32" openssl = "0.10.32"
http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] } http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] }
tokio = "0.3.6" tokio = "0.3.6"

View File

@ -32,7 +32,7 @@ rand = "0.8.3"
strum = "0.20.0" strum = "0.20.0"
strum_macros = "0.20.1" strum_macros = "0.20.1"
lazy_static = "1.4.0" lazy_static = "1.4.0"
url = { version = "2.2.0", features = ["serde"] } url = { version = "2.2.1", features = ["serde"] }
openssl = "0.10.32" openssl = "0.10.32"
http = "0.2.3" http = "0.2.3"
http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] } http-signature-normalization-actix = { version = "0.4.1", default-features = false, features = ["sha-2"] }

View File

@ -1,6 +1,5 @@
use crate::{ use crate::{
check_community_ban, check_community_ban,
check_optional_url,
get_user_from_jwt, get_user_from_jwt,
get_user_from_jwt_opt, get_user_from_jwt_opt,
is_admin, is_admin,
@ -19,7 +18,7 @@ use lemmy_apub::{
EndpointType, EndpointType,
}; };
use lemmy_db_queries::{ use lemmy_db_queries::{
diesel_option_overwrite, diesel_option_overwrite_to_url,
source::{ source::{
comment::Comment_, comment::Comment_,
community::{CommunityModerator_, Community_}, community::{CommunityModerator_, Community_},
@ -155,11 +154,8 @@ impl Perform for CreateCommunity {
} }
// Check to make sure the icon and banners are urls // Check to make sure the icon and banners are urls
let icon = diesel_option_overwrite(&data.icon); let icon = diesel_option_overwrite_to_url(&data.icon)?;
let banner = diesel_option_overwrite(&data.banner); let banner = diesel_option_overwrite_to_url(&data.banner)?;
check_optional_url(&icon)?;
check_optional_url(&banner)?;
// When you create a community, make sure the user becomes a moderator and a follower // When you create a community, make sure the user becomes a moderator and a follower
let keypair = generate_actor_keypair()?; let keypair = generate_actor_keypair()?;
@ -260,11 +256,8 @@ impl Perform for EditCommunity {
}) })
.await??; .await??;
let icon = diesel_option_overwrite(&data.icon); let icon = diesel_option_overwrite_to_url(&data.icon)?;
let banner = diesel_option_overwrite(&data.banner); let banner = diesel_option_overwrite_to_url(&data.banner)?;
check_optional_url(&icon)?;
check_optional_url(&banner)?;
let community_form = CommunityForm { let community_form = CommunityForm {
name: read_community.name, name: read_community.name,

View File

@ -186,15 +186,6 @@ pub(crate) async fn collect_moderated_communities(
} }
} }
pub(crate) fn check_optional_url(item: &Option<Option<String>>) -> Result<(), LemmyError> {
if let Some(Some(item)) = &item {
if Url::parse(item).is_err() {
return Err(ApiError::err("invalid_url").into());
}
}
Ok(())
}
pub(crate) async fn build_federated_instances( pub(crate) async fn build_federated_instances(
pool: &DbPool, pool: &DbPool,
) -> Result<Option<FederatedInstances>, LemmyError> { ) -> Result<Option<FederatedInstances>, LemmyError> {

View File

@ -1,7 +1,6 @@
use crate::{ use crate::{
check_community_ban, check_community_ban,
check_downvotes_enabled, check_downvotes_enabled,
check_optional_url,
collect_moderated_communities, collect_moderated_communities,
get_user_from_jwt, get_user_from_jwt,
get_user_from_jwt_opt, get_user_from_jwt_opt,
@ -72,15 +71,14 @@ impl Perform for CreatePost {
check_community_ban(user.id, data.community_id, context.pool()).await?; check_community_ban(user.id, data.community_id, context.pool()).await?;
check_optional_url(&Some(data.url.to_owned()))?;
// Fetch Iframely and pictrs cached image // Fetch Iframely and pictrs cached image
let data_url = data.url.as_ref();
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) = let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
fetch_iframely_and_pictrs_data(context.client(), data.url.to_owned()).await; fetch_iframely_and_pictrs_data(context.client(), data_url).await;
let post_form = PostForm { let post_form = PostForm {
name: data.name.trim().to_owned(), name: data.name.trim().to_owned(),
url: data.url.to_owned(), url: data_url.map(|u| u.to_owned().into()),
body: data.body.to_owned(), body: data.body.to_owned(),
community_id: data.community_id, community_id: data.community_id,
creator_id: user.id, creator_id: user.id,
@ -93,7 +91,7 @@ impl Perform for CreatePost {
embed_title: iframely_title, embed_title: iframely_title,
embed_description: iframely_description, embed_description: iframely_description,
embed_html: iframely_html, embed_html: iframely_html,
thumbnail_url: pictrs_thumbnail, thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
ap_id: None, ap_id: None,
local: true, local: true,
published: None, published: None,
@ -385,12 +383,13 @@ impl Perform for EditPost {
} }
// Fetch Iframely and Pictrs cached image // Fetch Iframely and Pictrs cached image
let data_url = data.url.as_ref();
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) = let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
fetch_iframely_and_pictrs_data(context.client(), data.url.to_owned()).await; fetch_iframely_and_pictrs_data(context.client(), data_url).await;
let post_form = PostForm { let post_form = PostForm {
name: data.name.trim().to_owned(), name: data.name.trim().to_owned(),
url: data.url.to_owned(), url: data_url.map(|u| u.to_owned().into()),
body: data.body.to_owned(), body: data.body.to_owned(),
nsfw: data.nsfw, nsfw: data.nsfw,
creator_id: orig_post.creator_id.to_owned(), creator_id: orig_post.creator_id.to_owned(),
@ -403,7 +402,7 @@ impl Perform for EditPost {
embed_title: iframely_title, embed_title: iframely_title,
embed_description: iframely_description, embed_description: iframely_description,
embed_html: iframely_html, embed_html: iframely_html,
thumbnail_url: pictrs_thumbnail, thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
ap_id: Some(orig_post.ap_id), ap_id: Some(orig_post.ap_id),
local: orig_post.local, local: orig_post.local,
published: None, published: None,

View File

@ -11,7 +11,13 @@ use actix_web::web::Data;
use anyhow::Context; use anyhow::Context;
use lemmy_api_structs::{blocking, site::*, user::Register}; use lemmy_api_structs::{blocking, site::*, user::Register};
use lemmy_apub::fetcher::search::search_by_apub_id; use lemmy_apub::fetcher::search::search_by_apub_id;
use lemmy_db_queries::{diesel_option_overwrite, source::site::Site_, Crud, SearchType, SortType}; use lemmy_db_queries::{
diesel_option_overwrite_to_url,
source::site::Site_,
Crud,
SearchType,
SortType,
};
use lemmy_db_schema::{ use lemmy_db_schema::{
naive_now, naive_now,
source::{ source::{
@ -157,8 +163,8 @@ impl Perform for CreateSite {
let site_form = SiteForm { let site_form = SiteForm {
name: data.name.to_owned(), name: data.name.to_owned(),
description: data.description.to_owned(), description: data.description.to_owned(),
icon: Some(data.icon.to_owned()), icon: Some(data.icon.to_owned().map(|url| url.into())),
banner: Some(data.banner.to_owned()), banner: Some(data.banner.to_owned().map(|url| url.into())),
creator_id: user.id, creator_id: user.id,
enable_downvotes: data.enable_downvotes, enable_downvotes: data.enable_downvotes,
open_registration: data.open_registration, open_registration: data.open_registration,
@ -196,8 +202,8 @@ impl Perform for EditSite {
let found_site = blocking(context.pool(), move |conn| Site::read_simple(conn)).await??; let found_site = blocking(context.pool(), move |conn| Site::read_simple(conn)).await??;
let icon = diesel_option_overwrite(&data.icon); let icon = diesel_option_overwrite_to_url(&data.icon)?;
let banner = diesel_option_overwrite(&data.banner); let banner = diesel_option_overwrite_to_url(&data.banner)?;
let site_form = SiteForm { let site_form = SiteForm {
name: data.name.to_owned(), name: data.name.to_owned(),

View File

@ -1,6 +1,5 @@
use crate::{ use crate::{
captcha_espeak_wav_base64, captcha_espeak_wav_base64,
check_optional_url,
collect_moderated_communities, collect_moderated_communities,
get_user_from_jwt, get_user_from_jwt,
get_user_from_jwt_opt, get_user_from_jwt_opt,
@ -23,6 +22,7 @@ use lemmy_apub::{
}; };
use lemmy_db_queries::{ use lemmy_db_queries::{
diesel_option_overwrite, diesel_option_overwrite,
diesel_option_overwrite_to_url,
source::{ source::{
comment::Comment_, comment::Comment_,
community::Community_, community::Community_,
@ -366,17 +366,13 @@ impl Perform for SaveUserSettings {
let data: &SaveUserSettings = &self; let data: &SaveUserSettings = &self;
let user = get_user_from_jwt(&data.auth, context.pool()).await?; let user = get_user_from_jwt(&data.auth, context.pool()).await?;
let avatar = diesel_option_overwrite(&data.avatar); let avatar = diesel_option_overwrite_to_url(&data.avatar)?;
let banner = diesel_option_overwrite(&data.banner); let banner = diesel_option_overwrite_to_url(&data.banner)?;
let email = diesel_option_overwrite(&data.email); let email = diesel_option_overwrite(&data.email);
let bio = diesel_option_overwrite(&data.bio); let bio = diesel_option_overwrite(&data.bio);
let preferred_username = diesel_option_overwrite(&data.preferred_username); let preferred_username = diesel_option_overwrite(&data.preferred_username);
let matrix_user_id = diesel_option_overwrite(&data.matrix_user_id); let matrix_user_id = diesel_option_overwrite(&data.matrix_user_id);
// Check to make sure the avatar and banners are urls
check_optional_url(&avatar)?;
check_optional_url(&banner)?;
if let Some(Some(bio)) = &bio { if let Some(Some(bio)) = &bio {
if bio.chars().count() > 300 { if bio.chars().count() > 300 {
return Err(ApiError::err("bio_length_overflow").into()); return Err(ApiError::err("bio_length_overflow").into());

View File

@ -21,4 +21,4 @@ diesel = "1.4.5"
actix-web = "3.3.2" actix-web = "3.3.2"
chrono = { version = "0.4.19", features = ["serde"] } chrono = { version = "0.4.19", features = ["serde"] }
serde_json = { version = "1.0.61", features = ["preserve_order"] } serde_json = { version = "1.0.61", features = ["preserve_order"] }
url = "2.2.0" url = "2.2.1"

View File

@ -8,11 +8,12 @@ use lemmy_db_views_actor::{
community_view::CommunityView, community_view::CommunityView,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct CreatePost { pub struct CreatePost {
pub name: String, pub name: String,
pub url: Option<String>, pub url: Option<Url>,
pub body: Option<String>, pub body: Option<String>,
pub nsfw: bool, pub nsfw: bool,
pub community_id: i32, pub community_id: i32,
@ -66,7 +67,7 @@ pub struct CreatePostLike {
pub struct EditPost { pub struct EditPost {
pub post_id: i32, pub post_id: i32,
pub name: String, pub name: String,
pub url: Option<String>, pub url: Option<Url>,
pub body: Option<String>, pub body: Option<String>,
pub nsfw: bool, pub nsfw: bool,
pub auth: String, pub auth: String,

View File

@ -13,6 +13,7 @@ use lemmy_db_views_moderator::{
mod_sticky_post_view::ModStickyPostView, mod_sticky_post_view::ModStickyPostView,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct Search { pub struct Search {
@ -60,8 +61,8 @@ pub struct GetModlogResponse {
pub struct CreateSite { pub struct CreateSite {
pub name: String, pub name: String,
pub description: Option<String>, pub description: Option<String>,
pub icon: Option<String>, pub icon: Option<Url>,
pub banner: Option<String>, pub banner: Option<Url>,
pub enable_downvotes: bool, pub enable_downvotes: bool,
pub open_registration: bool, pub open_registration: bool,
pub enable_nsfw: bool, pub enable_nsfw: bool,

View File

@ -32,7 +32,7 @@ rand = "0.8.3"
strum = "0.20.0" strum = "0.20.0"
strum_macros = "0.20.1" strum_macros = "0.20.1"
lazy_static = "1.4.0" lazy_static = "1.4.0"
url = { version = "2.2.0", features = ["serde"] } url = { version = "2.2.1", features = ["serde"] }
percent-encoding = "2.1.0" percent-encoding = "2.1.0"
openssl = "0.10.32" openssl = "0.10.32"
http = "0.2.3" http = "0.2.3"

View File

@ -7,6 +7,7 @@ use lemmy_db_schema::source::activity::Activity;
use lemmy_utils::{settings::structs::Settings, LemmyError}; use lemmy_utils::{settings::structs::Settings, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url;
pub mod comment; pub mod comment;
pub mod community; pub mod community;
@ -46,12 +47,13 @@ pub async fn get_activity(
context: web::Data<LemmyContext>, context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, LemmyError> { ) -> Result<HttpResponse<Body>, LemmyError> {
let settings = Settings::get(); let settings = Settings::get();
let activity_id = format!( let activity_id = Url::parse(&format!(
"{}/activities/{}/{}", "{}/activities/{}/{}",
settings.get_protocol_and_hostname(), settings.get_protocol_and_hostname(),
info.type_, info.type_,
info.id info.id
); ))?
.into();
let activity = blocking(context.pool(), move |conn| { let activity = blocking(context.pool(), move |conn| {
Activity::read_from_apub_id(&conn, &activity_id) Activity::read_from_apub_id(&conn, &activity_id)
}) })

View File

@ -45,7 +45,7 @@ pub(crate) async fn is_activity_already_known(
pool: &DbPool, pool: &DbPool,
activity_id: &Url, activity_id: &Url,
) -> Result<bool, LemmyError> { ) -> Result<bool, LemmyError> {
let activity_id = activity_id.to_string(); let activity_id = activity_id.to_owned().into();
let existing = blocking(pool, move |conn| { let existing = blocking(pool, move |conn| {
Activity::read_from_apub_id(&conn, &activity_id) Activity::read_from_apub_id(&conn, &activity_id)
}) })

View File

@ -120,9 +120,9 @@ pub(in crate::inbox) async fn receive_like_for_community(
.as_single_xsd_any_uri() .as_single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? { match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
PostOrComment::Post(post) => receive_like_post(like, post, context, request_counter).await, PostOrComment::Post(post) => receive_like_post(like, *post, context, request_counter).await,
PostOrComment::Comment(comment) => { PostOrComment::Comment(comment) => {
receive_like_comment(like, comment, context, request_counter).await receive_like_comment(like, *comment, context, request_counter).await
} }
} }
} }
@ -152,10 +152,10 @@ pub(in crate::inbox) async fn receive_dislike_for_community(
.context(location_info!())?; .context(location_info!())?;
match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? { match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
PostOrComment::Post(post) => { PostOrComment::Post(post) => {
receive_dislike_post(dislike, post, context, request_counter).await receive_dislike_post(dislike, *post, context, request_counter).await
} }
PostOrComment::Comment(comment) => { PostOrComment::Comment(comment) => {
receive_dislike_comment(dislike, comment, context, request_counter).await receive_dislike_comment(dislike, *comment, context, request_counter).await
} }
} }
} }
@ -177,8 +177,8 @@ pub(in crate::inbox) async fn receive_delete_for_community(
.context(location_info!())?; .context(location_info!())?;
match find_post_or_comment_by_id(context, object).await { match find_post_or_comment_by_id(context, object).await {
Ok(PostOrComment::Post(p)) => receive_delete_post(context, p).await, Ok(PostOrComment::Post(p)) => receive_delete_post(context, *p).await,
Ok(PostOrComment::Comment(c)) => receive_delete_comment(context, c).await, Ok(PostOrComment::Comment(c)) => receive_delete_comment(context, *c).await,
// if we dont have the object, no need to do anything // if we dont have the object, no need to do anything
Err(_) => Ok(()), Err(_) => Ok(()),
} }
@ -215,8 +215,8 @@ pub(in crate::inbox) async fn receive_remove_for_community(
remove.id(community_id.domain().context(location_info!())?)?; remove.id(community_id.domain().context(location_info!())?)?;
match find_post_or_comment_by_id(context, object).await { match find_post_or_comment_by_id(context, object).await {
Ok(PostOrComment::Post(p)) => receive_remove_post(context, remove, p).await, Ok(PostOrComment::Post(p)) => receive_remove_post(context, remove, *p).await,
Ok(PostOrComment::Comment(c)) => receive_remove_comment(context, remove, c).await, Ok(PostOrComment::Comment(c)) => receive_remove_comment(context, remove, *c).await,
// if we dont have the object, no need to do anything // if we dont have the object, no need to do anything
Err(_) => Ok(()), Err(_) => Ok(()),
} }
@ -276,8 +276,8 @@ pub(in crate::inbox) async fn receive_undo_delete_for_community(
.single_xsd_any_uri() .single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
match find_post_or_comment_by_id(context, object).await { match find_post_or_comment_by_id(context, object).await {
Ok(PostOrComment::Post(p)) => receive_undo_delete_post(context, p).await, Ok(PostOrComment::Post(p)) => receive_undo_delete_post(context, *p).await,
Ok(PostOrComment::Comment(c)) => receive_undo_delete_comment(context, c).await, Ok(PostOrComment::Comment(c)) => receive_undo_delete_comment(context, *c).await,
// if we dont have the object, no need to do anything // if we dont have the object, no need to do anything
Err(_) => Ok(()), Err(_) => Ok(()),
} }
@ -300,8 +300,8 @@ pub(in crate::inbox) async fn receive_undo_remove_for_community(
.single_xsd_any_uri() .single_xsd_any_uri()
.context(location_info!())?; .context(location_info!())?;
match find_post_or_comment_by_id(context, object).await { match find_post_or_comment_by_id(context, object).await {
Ok(PostOrComment::Post(p)) => receive_undo_remove_post(context, p).await, Ok(PostOrComment::Post(p)) => receive_undo_remove_post(context, *p).await,
Ok(PostOrComment::Comment(c)) => receive_undo_remove_comment(context, c).await, Ok(PostOrComment::Comment(c)) => receive_undo_remove_comment(context, *c).await,
// if we dont have the object, no need to do anything // if we dont have the object, no need to do anything
Err(_) => Ok(()), Err(_) => Ok(()),
} }
@ -325,10 +325,10 @@ pub(in crate::inbox) async fn receive_undo_like_for_community(
.context(location_info!())?; .context(location_info!())?;
match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? { match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
PostOrComment::Post(post) => { PostOrComment::Post(post) => {
receive_undo_like_post(&like, post, context, request_counter).await receive_undo_like_post(&like, *post, context, request_counter).await
} }
PostOrComment::Comment(comment) => { PostOrComment::Comment(comment) => {
receive_undo_like_comment(&like, comment, context, request_counter).await receive_undo_like_comment(&like, *comment, context, request_counter).await
} }
} }
} }
@ -351,10 +351,10 @@ pub(in crate::inbox) async fn receive_undo_dislike_for_community(
.context(location_info!())?; .context(location_info!())?;
match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? { match fetch_post_or_comment_by_id(&object_id, context, request_counter).await? {
PostOrComment::Post(post) => { PostOrComment::Post(post) => {
receive_undo_dislike_post(&dislike, post, context, request_counter).await receive_undo_dislike_post(&dislike, *post, context, request_counter).await
} }
PostOrComment::Comment(comment) => { PostOrComment::Comment(comment) => {
receive_undo_dislike_comment(&dislike, comment, context, request_counter).await receive_undo_dislike_comment(&dislike, *comment, context, request_counter).await
} }
} }
} }
@ -365,11 +365,11 @@ async fn fetch_post_or_comment_by_id(
request_counter: &mut i32, request_counter: &mut i32,
) -> Result<PostOrComment, LemmyError> { ) -> Result<PostOrComment, LemmyError> {
if let Ok(post) = get_or_fetch_and_insert_post(apub_id, context, request_counter).await { if let Ok(post) = get_or_fetch_and_insert_post(apub_id, context, request_counter).await {
return Ok(PostOrComment::Post(post)); return Ok(PostOrComment::Post(Box::new(post)));
} }
if let Ok(comment) = get_or_fetch_and_insert_comment(apub_id, context, request_counter).await { if let Ok(comment) = get_or_fetch_and_insert_comment(apub_id, context, request_counter).await {
return Ok(PostOrComment::Comment(comment)); return Ok(PostOrComment::Comment(Box::new(comment)));
} }
Err(NotFound.into()) Err(NotFound.into())

View File

@ -26,13 +26,16 @@ use anyhow::{anyhow, Context};
use diesel::NotFound; use diesel::NotFound;
use lemmy_api_structs::blocking; use lemmy_api_structs::blocking;
use lemmy_db_queries::{source::activity::Activity_, ApubObject, DbPool}; use lemmy_db_queries::{source::activity::Activity_, ApubObject, DbPool};
use lemmy_db_schema::source::{ use lemmy_db_schema::{
source::{
activity::Activity, activity::Activity,
comment::Comment, comment::Comment,
community::Community, community::Community,
post::Post, post::Post,
private_message::PrivateMessage, private_message::PrivateMessage,
user::User_, user::User_,
},
DbUrl,
}; };
use lemmy_utils::{location_info, settings::structs::Settings, LemmyError}; use lemmy_utils::{location_info, settings::structs::Settings, LemmyError};
use lemmy_websocket::LemmyContext; use lemmy_websocket::LemmyContext;
@ -216,7 +219,7 @@ pub enum EndpointType {
pub fn generate_apub_endpoint( pub fn generate_apub_endpoint(
endpoint_type: EndpointType, endpoint_type: EndpointType,
name: &str, name: &str,
) -> Result<lemmy_db_schema::Url, ParseError> { ) -> Result<DbUrl, ParseError> {
let point = match endpoint_type { let point = match endpoint_type {
EndpointType::Community => "c", EndpointType::Community => "c",
EndpointType::User => "u", EndpointType::User => "u",
@ -236,21 +239,15 @@ pub fn generate_apub_endpoint(
) )
} }
pub fn generate_followers_url( pub fn generate_followers_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
actor_id: &lemmy_db_schema::Url,
) -> Result<lemmy_db_schema::Url, ParseError> {
Ok(Url::parse(&format!("{}/followers", actor_id))?.into()) Ok(Url::parse(&format!("{}/followers", actor_id))?.into())
} }
pub fn generate_inbox_url( pub fn generate_inbox_url(actor_id: &DbUrl) -> Result<DbUrl, ParseError> {
actor_id: &lemmy_db_schema::Url,
) -> Result<lemmy_db_schema::Url, ParseError> {
Ok(Url::parse(&format!("{}/inbox", actor_id))?.into()) Ok(Url::parse(&format!("{}/inbox", actor_id))?.into())
} }
pub fn generate_shared_inbox_url( pub fn generate_shared_inbox_url(actor_id: &DbUrl) -> Result<DbUrl, LemmyError> {
actor_id: &lemmy_db_schema::Url,
) -> Result<lemmy_db_schema::Url, LemmyError> {
let actor_id = actor_id.clone().into_inner(); let actor_id = actor_id.clone().into_inner();
let url = format!( let url = format!(
"{}://{}{}/inbox", "{}://{}{}/inbox",
@ -277,7 +274,7 @@ pub(crate) async fn insert_activity<T>(
where where
T: Serialize + std::fmt::Debug + Send + 'static, T: Serialize + std::fmt::Debug + Send + 'static,
{ {
let ap_id = ap_id.to_string(); let ap_id = ap_id.to_owned().into();
blocking(pool, move |conn| { blocking(pool, move |conn| {
Activity::insert(conn, ap_id, &activity, local, sensitive) Activity::insert(conn, ap_id, &activity, local, sensitive)
}) })
@ -286,8 +283,8 @@ where
} }
pub(crate) enum PostOrComment { pub(crate) enum PostOrComment {
Comment(Comment), Comment(Box<Comment>),
Post(Post), Post(Box<Post>),
} }
/// Tries to find a post or comment in the local database, without any network requests. /// Tries to find a post or comment in the local database, without any network requests.
@ -303,7 +300,7 @@ pub(crate) async fn find_post_or_comment_by_id(
}) })
.await?; .await?;
if let Ok(p) = post { if let Ok(p) = post {
return Ok(PostOrComment::Post(p)); return Ok(PostOrComment::Post(Box::new(p)));
} }
let ap_id = apub_id.clone(); let ap_id = apub_id.clone();
@ -312,7 +309,7 @@ pub(crate) async fn find_post_or_comment_by_id(
}) })
.await?; .await?;
if let Ok(c) = comment { if let Ok(c) = comment {
return Ok(PostOrComment::Comment(c)); return Ok(PostOrComment::Comment(Box::new(c)));
} }
Err(NotFound.into()) Err(NotFound.into())
@ -333,8 +330,8 @@ pub(crate) async fn find_object_by_id(
let ap_id = apub_id.clone(); let ap_id = apub_id.clone();
if let Ok(pc) = find_post_or_comment_by_id(context, ap_id.to_owned()).await { if let Ok(pc) = find_post_or_comment_by_id(context, ap_id.to_owned()).await {
return Ok(match pc { return Ok(match pc {
PostOrComment::Post(p) => Object::Post(p), PostOrComment::Post(p) => Object::Post(*p),
PostOrComment::Comment(c) => Object::Comment(c), PostOrComment::Comment(c) => Object::Comment(*c),
}); });
} }

View File

@ -73,13 +73,13 @@ impl ToApub for Community {
if let Some(icon_url) = &self.icon { if let Some(icon_url) = &self.icon {
let mut image = Image::new(); let mut image = Image::new();
image.set_url(Url::parse(icon_url)?); image.set_url::<Url>(icon_url.to_owned().into());
group.set_icon(image.into_any_base()?); group.set_icon(image.into_any_base()?);
} }
if let Some(banner_url) = &self.banner { if let Some(banner_url) = &self.banner {
let mut image = Image::new(); let mut image = Image::new();
image.set_url(Url::parse(banner_url)?); image.set_url::<Url>(banner_url.to_owned().into());
group.set_image(image.into_any_base()?); group.set_image(image.into_any_base()?);
} }
@ -173,7 +173,7 @@ impl FromApubToForm<GroupExt> for CommunityForm {
.url() .url()
.context(location_info!())? .context(location_info!())?
.as_single_xsd_any_uri() .as_single_xsd_any_uri()
.map(|u| u.to_string()), .map(|u| u.to_owned().into()),
), ),
None => None, None => None,
}; };
@ -185,7 +185,7 @@ impl FromApubToForm<GroupExt> for CommunityForm {
.url() .url()
.context(location_info!())? .context(location_info!())?
.as_single_xsd_any_uri() .as_single_xsd_any_uri()
.map(|u| u.to_string()), .map(|u| u.to_owned().into()),
), ),
None => None, None => None,
}; };

View File

@ -14,7 +14,7 @@ use chrono::NaiveDateTime;
use diesel::result::Error::NotFound; use diesel::result::Error::NotFound;
use lemmy_api_structs::blocking; use lemmy_api_structs::blocking;
use lemmy_db_queries::{ApubObject, Crud, DbPool}; use lemmy_db_queries::{ApubObject, Crud, DbPool};
use lemmy_db_schema::source::community::Community; use lemmy_db_schema::{source::community::Community, DbUrl};
use lemmy_utils::{ use lemmy_utils::{
location_info, location_info,
settings::structs::Settings, settings::structs::Settings,
@ -96,7 +96,7 @@ where
pub(in crate::objects) fn check_object_domain<T, Kind>( pub(in crate::objects) fn check_object_domain<T, Kind>(
apub: &T, apub: &T,
expected_domain: Url, expected_domain: Url,
) -> Result<lemmy_db_schema::Url, LemmyError> ) -> Result<DbUrl, LemmyError>
where where
T: Base + AsBase<Kind>, T: Base + AsBase<Kind>,
{ {

View File

@ -24,10 +24,13 @@ use activitystreams_ext::Ext1;
use anyhow::Context; use anyhow::Context;
use lemmy_api_structs::blocking; use lemmy_api_structs::blocking;
use lemmy_db_queries::{Crud, DbPool}; use lemmy_db_queries::{Crud, DbPool};
use lemmy_db_schema::source::{ use lemmy_db_schema::{
self,
source::{
community::Community, community::Community,
post::{Post, PostForm}, post::{Post, PostForm},
user::User_, user::User_,
},
}; };
use lemmy_utils::{ use lemmy_utils::{
location_info, location_info,
@ -70,16 +73,13 @@ impl ToApub for Post {
set_content_and_source(&mut page, &body)?; set_content_and_source(&mut page, &body)?;
} }
// TODO: hacky code because we get self.url == Some("") if let Some(url) = &self.url {
// https://github.com/LemmyNet/lemmy/issues/602 page.set_url::<Url>(url.to_owned().into());
let url = self.url.as_ref().filter(|u| !u.is_empty());
if let Some(u) = url {
page.set_url(Url::parse(u)?);
} }
if let Some(thumbnail_url) = &self.thumbnail_url { if let Some(thumbnail_url) = &self.thumbnail_url {
let mut image = Image::new(); let mut image = Image::new();
image.set_url(Url::parse(thumbnail_url)?); image.set_url::<Url>(thumbnail_url.to_owned().into());
page.set_image(image.into_any_base()?); page.set_image(image.into_any_base()?);
} }
@ -146,7 +146,7 @@ impl FromApubToForm<PageExt> for PostForm {
let community = get_to_community(page, context, request_counter).await?; let community = get_to_community(page, context, request_counter).await?;
let thumbnail_url = match &page.inner.image() { let thumbnail_url: Option<Url> = match &page.inner.image() {
Some(any_image) => Image::from_any_base( Some(any_image) => Image::from_any_base(
any_image any_image
.to_owned() .to_owned()
@ -158,7 +158,7 @@ impl FromApubToForm<PageExt> for PostForm {
.url() .url()
.context(location_info!())? .context(location_info!())?
.as_single_xsd_any_uri() .as_single_xsd_any_uri()
.map(|u| u.to_string()), .map(|url| url.to_owned()),
None => None, None => None,
}; };
let url = page let url = page
@ -166,11 +166,11 @@ impl FromApubToForm<PageExt> for PostForm {
.url() .url()
.map(|u| u.as_single_xsd_any_uri()) .map(|u| u.as_single_xsd_any_uri())
.flatten() .flatten()
.map(|s| s.to_string()); .map(|u| u.to_owned());
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) = let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
if let Some(url) = &url { if let Some(url) = &url {
fetch_iframely_and_pictrs_data(context.client(), Some(url.to_owned())).await fetch_iframely_and_pictrs_data(context.client(), Some(url)).await
} else { } else {
(None, None, None, thumbnail_url) (None, None, None, thumbnail_url)
}; };
@ -192,7 +192,7 @@ impl FromApubToForm<PageExt> for PostForm {
let body_slurs_removed = body.map(|b| remove_slurs(&b)); let body_slurs_removed = body.map(|b| remove_slurs(&b));
Ok(PostForm { Ok(PostForm {
name, name,
url, url: url.map(|u| u.into()),
body: body_slurs_removed, body: body_slurs_removed,
creator_id: creator.id, creator_id: creator.id,
community_id: community.id, community_id: community.id,
@ -214,7 +214,7 @@ impl FromApubToForm<PageExt> for PostForm {
embed_title: iframely_title, embed_title: iframely_title,
embed_description: iframely_description, embed_description: iframely_description,
embed_html: iframely_html, embed_html: iframely_html,
thumbnail_url: pictrs_thumbnail, thumbnail_url: pictrs_thumbnail.map(|u| u.into()),
ap_id: Some(check_object_domain(page, expected_domain)?), ap_id: Some(check_object_domain(page, expected_domain)?),
local: false, local: false,
}) })

View File

@ -50,13 +50,13 @@ impl ToApub for User_ {
if let Some(avatar_url) = &self.avatar { if let Some(avatar_url) = &self.avatar {
let mut image = Image::new(); let mut image = Image::new();
image.set_url(Url::parse(avatar_url)?); image.set_url::<Url>(avatar_url.to_owned().into());
person.set_icon(image.into_any_base()?); person.set_icon(image.into_any_base()?);
} }
if let Some(banner_url) = &self.banner { if let Some(banner_url) = &self.banner {
let mut image = Image::new(); let mut image = Image::new();
image.set_url(Url::parse(banner_url)?); image.set_url::<Url>(banner_url.to_owned().into());
person.set_image(image.into_any_base()?); person.set_image(image.into_any_base()?);
} }
@ -126,7 +126,7 @@ impl FromApubToForm<PersonExt> for UserForm {
.url() .url()
.context(location_info!())? .context(location_info!())?
.as_single_xsd_any_uri() .as_single_xsd_any_uri()
.map(|u| u.to_string()), .map(|url| url.to_owned()),
), ),
None => None, None => None,
}; };
@ -139,7 +139,7 @@ impl FromApubToForm<PersonExt> for UserForm {
.url() .url()
.context(location_info!())? .context(location_info!())?
.as_single_xsd_any_uri() .as_single_xsd_any_uri()
.map(|u| u.to_string()), .map(|url| url.to_owned()),
), ),
None => None, None => None,
}; };
@ -174,8 +174,8 @@ impl FromApubToForm<PersonExt> for UserForm {
admin: false, admin: false,
banned: None, banned: None,
email: None, email: None,
avatar, avatar: avatar.map(|o| o.map(|i| i.into())),
banner, banner: banner.map(|o| o.map(|i| i.into())),
published: person.inner.published().map(|u| u.to_owned().naive_local()), published: person.inner.published().map(|u| u.to_owned().naive_local()),
updated: person.updated().map(|u| u.to_owned().naive_local()), updated: person.updated().map(|u| u.to_owned().naive_local()),
show_nsfw: false, show_nsfw: false,

View File

@ -20,7 +20,7 @@ strum = "0.20.0"
strum_macros = "0.20.1" strum_macros = "0.20.1"
log = "0.4.14" log = "0.4.14"
sha2 = "0.9.3" sha2 = "0.9.3"
url = { version = "2.2.0", features = ["serde"] } url = { version = "2.2.1", features = ["serde"] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
regex = "1.4.3" regex = "1.4.3"
bcrypt = "0.9.0" bcrypt = "0.9.0"

View File

@ -13,10 +13,12 @@ extern crate diesel_migrations;
extern crate serial_test; extern crate serial_test;
use diesel::{result::Error, *}; use diesel::{result::Error, *};
use lemmy_db_schema::Url; use lemmy_db_schema::DbUrl;
use lemmy_utils::ApiError;
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{env, env::VarError}; use std::{env, env::VarError};
use url::Url;
pub mod aggregates; pub mod aggregates;
pub mod source; pub mod source;
@ -112,7 +114,7 @@ pub trait Reportable<T> {
} }
pub trait ApubObject<T> { pub trait ApubObject<T> {
fn read_from_apub_id(conn: &PgConnection, object_id: &Url) -> Result<Self, Error> fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
fn upsert(conn: &PgConnection, user_form: &T) -> Result<Self, Error> fn upsert(conn: &PgConnection, user_form: &T) -> Result<Self, Error>
@ -219,6 +221,20 @@ pub fn diesel_option_overwrite(opt: &Option<String>) -> Option<Option<String>> {
} }
} }
pub fn diesel_option_overwrite_to_url(
opt: &Option<String>,
) -> Result<Option<Option<DbUrl>>, ApiError> {
match opt.as_ref().map(|s| s.as_str()) {
// An empty string is an erase
Some("") => Ok(Some(None)),
Some(str_url) => match Url::parse(str_url) {
Ok(url) => Ok(Some(Some(url.into()))),
Err(_) => Err(ApiError::err("invalid_url")),
},
None => Ok(None),
}
}
embed_migrations!(); embed_migrations!();
pub fn establish_unpooled_connection() -> PgConnection { pub fn establish_unpooled_connection() -> PgConnection {
@ -250,7 +266,7 @@ pub mod functions {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::fuzzy_search; use super::{fuzzy_search, *};
use crate::is_email_regex; use crate::is_email_regex;
#[test] #[test]
@ -264,4 +280,32 @@ mod tests {
assert!(is_email_regex("gush@gmail.com")); assert!(is_email_regex("gush@gmail.com"));
assert!(!is_email_regex("nada_neutho")); assert!(!is_email_regex("nada_neutho"));
} }
#[test]
fn test_diesel_option_overwrite() {
assert_eq!(diesel_option_overwrite(&None), None);
assert_eq!(diesel_option_overwrite(&Some("".to_string())), Some(None));
assert_eq!(
diesel_option_overwrite(&Some("test".to_string())),
Some(Some("test".to_string()))
);
}
#[test]
fn test_diesel_option_overwrite_to_url() {
assert!(matches!(diesel_option_overwrite_to_url(&None), Ok(None)));
assert!(matches!(
diesel_option_overwrite_to_url(&Some("".to_string())),
Ok(Some(None))
));
assert!(matches!(
diesel_option_overwrite_to_url(&Some("invalid_url".to_string())),
Err(_)
));
let example_url = "https://example.com";
assert!(matches!(
diesel_option_overwrite_to_url(&Some(example_url.to_string())),
Ok(Some(Some(url))) if url == Url::parse(&example_url).unwrap().into()
));
}
} }

View File

@ -1,6 +1,6 @@
use crate::Crud; use crate::Crud;
use diesel::{dsl::*, result::Error, sql_types::Text, *}; use diesel::{dsl::*, result::Error, sql_types::Text, *};
use lemmy_db_schema::{source::activity::*, Url}; use lemmy_db_schema::{source::activity::*, DbUrl};
use log::debug; use log::debug;
use serde::Serialize; use serde::Serialize;
use serde_json::Value; use serde_json::Value;
@ -41,7 +41,7 @@ impl Crud<ActivityForm> for Activity {
pub trait Activity_ { pub trait Activity_ {
fn insert<T>( fn insert<T>(
conn: &PgConnection, conn: &PgConnection,
ap_id: String, ap_id: DbUrl,
data: &T, data: &T,
local: bool, local: bool,
sensitive: bool, sensitive: bool,
@ -49,20 +49,20 @@ pub trait Activity_ {
where where
T: Serialize + Debug; T: Serialize + Debug;
fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Activity, Error>; fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Activity, Error>;
fn delete_olds(conn: &PgConnection) -> Result<usize, Error>; fn delete_olds(conn: &PgConnection) -> Result<usize, Error>;
/// Returns up to 20 activities of type `Announce/Create/Page` from the community /// Returns up to 20 activities of type `Announce/Create/Page` from the community
fn read_community_outbox( fn read_community_outbox(
conn: &PgConnection, conn: &PgConnection,
community_actor_id: &Url, community_actor_id: &DbUrl,
) -> Result<Vec<Value>, Error>; ) -> Result<Vec<Value>, Error>;
} }
impl Activity_ for Activity { impl Activity_ for Activity {
fn insert<T>( fn insert<T>(
conn: &PgConnection, conn: &PgConnection,
ap_id: String, ap_id: DbUrl,
data: &T, data: &T,
local: bool, local: bool,
sensitive: bool, sensitive: bool,
@ -88,7 +88,7 @@ impl Activity_ for Activity {
} }
} }
fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Activity, Error> { fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Activity, Error> {
use lemmy_db_schema::schema::activity::dsl::*; use lemmy_db_schema::schema::activity::dsl::*;
activity.filter(ap_id.eq(object_id)).first::<Self>(conn) activity.filter(ap_id.eq(object_id)).first::<Self>(conn)
} }
@ -100,7 +100,7 @@ impl Activity_ for Activity {
fn read_community_outbox( fn read_community_outbox(
conn: &PgConnection, conn: &PgConnection,
community_actor_id: &Url, community_actor_id: &DbUrl,
) -> Result<Vec<Value>, Error> { ) -> Result<Vec<Value>, Error> {
use lemmy_db_schema::schema::activity::dsl::*; use lemmy_db_schema::schema::activity::dsl::*;
let res: Vec<Value> = activity let res: Vec<Value> = activity
@ -121,6 +121,7 @@ impl Activity_ for Activity {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*;
use crate::{ use crate::{
establish_unpooled_connection, establish_unpooled_connection,
source::activity::Activity_, source::activity::Activity_,
@ -134,6 +135,7 @@ mod tests {
}; };
use serde_json::Value; use serde_json::Value;
use serial_test::serial; use serial_test::serial;
use url::Url;
#[test] #[test]
#[serial] #[serial]
@ -171,8 +173,11 @@ mod tests {
let inserted_creator = User_::create(&conn, &creator_form).unwrap(); let inserted_creator = User_::create(&conn, &creator_form).unwrap();
let ap_id = let ap_id: DbUrl = Url::parse(
"https://enterprise.lemmy.ml/activities/delete/f1b5d57c-80f8-4e03-a615-688d552e946c"; "https://enterprise.lemmy.ml/activities/delete/f1b5d57c-80f8-4e03-a615-688d552e946c",
)
.unwrap()
.into();
let test_json: Value = serde_json::from_str( let test_json: Value = serde_json::from_str(
r#"{ r#"{
"@context": "https://www.w3.org/ns/activitystreams", "@context": "https://www.w3.org/ns/activitystreams",
@ -188,7 +193,7 @@ mod tests {
) )
.unwrap(); .unwrap();
let activity_form = ActivityForm { let activity_form = ActivityForm {
ap_id: ap_id.to_string(), ap_id: ap_id.clone(),
data: test_json.to_owned(), data: test_json.to_owned(),
local: true, local: true,
sensitive: false, sensitive: false,
@ -198,7 +203,7 @@ mod tests {
let inserted_activity = Activity::create(&conn, &activity_form).unwrap(); let inserted_activity = Activity::create(&conn, &activity_form).unwrap();
let expected_activity = Activity { let expected_activity = Activity {
ap_id: Some(ap_id.to_string()), ap_id: Some(ap_id.clone()),
id: inserted_activity.id, id: inserted_activity.id,
data: test_json, data: test_json,
local: true, local: true,
@ -208,7 +213,7 @@ mod tests {
}; };
let read_activity = Activity::read(&conn, inserted_activity.id).unwrap(); let read_activity = Activity::read(&conn, inserted_activity.id).unwrap();
let read_activity_by_apub_id = Activity::read_from_apub_id(&conn, ap_id).unwrap(); let read_activity_by_apub_id = Activity::read_from_apub_id(&conn, &ap_id).unwrap();
User_::delete(&conn, inserted_creator.id).unwrap(); User_::delete(&conn, inserted_creator.id).unwrap();
Activity::delete(&conn, inserted_activity.id).unwrap(); Activity::delete(&conn, inserted_activity.id).unwrap();

View File

@ -10,11 +10,11 @@ use lemmy_db_schema::{
CommentSaved, CommentSaved,
CommentSavedForm, CommentSavedForm,
}, },
Url, DbUrl,
}; };
pub trait Comment_ { pub trait Comment_ {
fn update_ap_id(conn: &PgConnection, comment_id: i32, apub_id: Url) -> Result<Comment, Error>; fn update_ap_id(conn: &PgConnection, comment_id: i32, apub_id: DbUrl) -> Result<Comment, Error>;
fn permadelete_for_creator( fn permadelete_for_creator(
conn: &PgConnection, conn: &PgConnection,
for_creator_id: i32, for_creator_id: i32,
@ -43,7 +43,7 @@ pub trait Comment_ {
} }
impl Comment_ for Comment { impl Comment_ for Comment {
fn update_ap_id(conn: &PgConnection, comment_id: i32, apub_id: Url) -> Result<Self, Error> { fn update_ap_id(conn: &PgConnection, comment_id: i32, apub_id: DbUrl) -> Result<Self, Error> {
use lemmy_db_schema::schema::comment::dsl::*; use lemmy_db_schema::schema::comment::dsl::*;
diesel::update(comment.find(comment_id)) diesel::update(comment.find(comment_id))
@ -145,7 +145,7 @@ impl Crud<CommentForm> for Comment {
} }
impl ApubObject<CommentForm> for Comment { impl ApubObject<CommentForm> for Comment {
fn read_from_apub_id(conn: &PgConnection, object_id: &Url) -> Result<Self, Error> { fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Self, Error> {
use lemmy_db_schema::schema::comment::dsl::*; use lemmy_db_schema::schema::comment::dsl::*;
comment.filter(ap_id.eq(object_id)).first::<Self>(conn) comment.filter(ap_id.eq(object_id)).first::<Self>(conn)
} }

View File

@ -12,7 +12,7 @@ use lemmy_db_schema::{
CommunityUserBan, CommunityUserBan,
CommunityUserBanForm, CommunityUserBanForm,
}, },
Url, DbUrl,
}; };
mod safe_type { mod safe_type {
@ -90,7 +90,7 @@ impl Crud<CommunityForm> for Community {
} }
impl ApubObject<CommunityForm> for Community { impl ApubObject<CommunityForm> for Community {
fn read_from_apub_id(conn: &PgConnection, for_actor_id: &Url) -> Result<Self, Error> { fn read_from_apub_id(conn: &PgConnection, for_actor_id: &DbUrl) -> Result<Self, Error> {
use lemmy_db_schema::schema::community::dsl::*; use lemmy_db_schema::schema::community::dsl::*;
community community
.filter(actor_id.eq(for_actor_id)) .filter(actor_id.eq(for_actor_id))
@ -131,7 +131,10 @@ pub trait Community_ {
new_creator_id: i32, new_creator_id: i32,
) -> Result<Community, Error>; ) -> Result<Community, Error>;
fn distinct_federated_communities(conn: &PgConnection) -> Result<Vec<String>, Error>; fn distinct_federated_communities(conn: &PgConnection) -> Result<Vec<String>, Error>;
fn read_from_followers_url(conn: &PgConnection, followers_url: &Url) -> Result<Community, Error>; fn read_from_followers_url(
conn: &PgConnection,
followers_url: &DbUrl,
) -> Result<Community, Error>;
} }
impl Community_ for Community { impl Community_ for Community {
@ -194,7 +197,7 @@ impl Community_ for Community {
fn read_from_followers_url( fn read_from_followers_url(
conn: &PgConnection, conn: &PgConnection,
followers_url_: &Url, followers_url_: &DbUrl,
) -> Result<Community, Error> { ) -> Result<Community, Error> {
use lemmy_db_schema::schema::community::dsl::*; use lemmy_db_schema::schema::community::dsl::*;
community community

View File

@ -12,7 +12,7 @@ use lemmy_db_schema::{
PostSaved, PostSaved,
PostSavedForm, PostSavedForm,
}, },
Url, DbUrl,
}; };
impl Crud<PostForm> for Post { impl Crud<PostForm> for Post {
@ -42,7 +42,7 @@ impl Crud<PostForm> for Post {
pub trait Post_ { pub trait Post_ {
//fn read(conn: &PgConnection, post_id: i32) -> Result<Post, Error>; //fn read(conn: &PgConnection, post_id: i32) -> Result<Post, Error>;
fn list_for_community(conn: &PgConnection, the_community_id: i32) -> Result<Vec<Post>, Error>; fn list_for_community(conn: &PgConnection, the_community_id: i32) -> Result<Vec<Post>, Error>;
fn update_ap_id(conn: &PgConnection, post_id: i32, apub_id: Url) -> Result<Post, Error>; fn update_ap_id(conn: &PgConnection, post_id: i32, apub_id: DbUrl) -> Result<Post, Error>;
fn permadelete_for_creator(conn: &PgConnection, for_creator_id: i32) -> Result<Vec<Post>, Error>; fn permadelete_for_creator(conn: &PgConnection, for_creator_id: i32) -> Result<Vec<Post>, Error>;
fn update_deleted(conn: &PgConnection, post_id: i32, new_deleted: bool) -> Result<Post, Error>; fn update_deleted(conn: &PgConnection, post_id: i32, new_deleted: bool) -> Result<Post, Error>;
fn update_removed(conn: &PgConnection, post_id: i32, new_removed: bool) -> Result<Post, Error>; fn update_removed(conn: &PgConnection, post_id: i32, new_removed: bool) -> Result<Post, Error>;
@ -68,7 +68,7 @@ impl Post_ for Post {
.load::<Self>(conn) .load::<Self>(conn)
} }
fn update_ap_id(conn: &PgConnection, post_id: i32, apub_id: Url) -> Result<Self, Error> { fn update_ap_id(conn: &PgConnection, post_id: i32, apub_id: DbUrl) -> Result<Self, Error> {
use lemmy_db_schema::schema::post::dsl::*; use lemmy_db_schema::schema::post::dsl::*;
diesel::update(post.find(post_id)) diesel::update(post.find(post_id))
@ -147,7 +147,7 @@ impl Post_ for Post {
} }
impl ApubObject<PostForm> for Post { impl ApubObject<PostForm> for Post {
fn read_from_apub_id(conn: &PgConnection, object_id: &Url) -> Result<Self, Error> { fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Self, Error> {
use lemmy_db_schema::schema::post::dsl::*; use lemmy_db_schema::schema::post::dsl::*;
post.filter(ap_id.eq(object_id)).first::<Self>(conn) post.filter(ap_id.eq(object_id)).first::<Self>(conn)
} }

View File

@ -1,6 +1,6 @@
use crate::{ApubObject, Crud}; use crate::{ApubObject, Crud};
use diesel::{dsl::*, result::Error, *}; use diesel::{dsl::*, result::Error, *};
use lemmy_db_schema::{naive_now, source::private_message::*, Url}; use lemmy_db_schema::{naive_now, source::private_message::*, DbUrl};
impl Crud<PrivateMessageForm> for PrivateMessage { impl Crud<PrivateMessageForm> for PrivateMessage {
fn read(conn: &PgConnection, private_message_id: i32) -> Result<Self, Error> { fn read(conn: &PgConnection, private_message_id: i32) -> Result<Self, Error> {
@ -28,7 +28,7 @@ impl Crud<PrivateMessageForm> for PrivateMessage {
} }
impl ApubObject<PrivateMessageForm> for PrivateMessage { impl ApubObject<PrivateMessageForm> for PrivateMessage {
fn read_from_apub_id(conn: &PgConnection, object_id: &Url) -> Result<Self, Error> fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Self, Error>
where where
Self: Sized, Self: Sized,
{ {
@ -53,7 +53,7 @@ pub trait PrivateMessage_ {
fn update_ap_id( fn update_ap_id(
conn: &PgConnection, conn: &PgConnection,
private_message_id: i32, private_message_id: i32,
apub_id: Url, apub_id: DbUrl,
) -> Result<PrivateMessage, Error>; ) -> Result<PrivateMessage, Error>;
fn update_content( fn update_content(
conn: &PgConnection, conn: &PgConnection,
@ -80,7 +80,7 @@ impl PrivateMessage_ for PrivateMessage {
fn update_ap_id( fn update_ap_id(
conn: &PgConnection, conn: &PgConnection,
private_message_id: i32, private_message_id: i32,
apub_id: Url, apub_id: DbUrl,
) -> Result<PrivateMessage, Error> { ) -> Result<PrivateMessage, Error> {
use lemmy_db_schema::schema::private_message::dsl::*; use lemmy_db_schema::schema::private_message::dsl::*;

View File

@ -5,7 +5,7 @@ use lemmy_db_schema::{
naive_now, naive_now,
schema::user_::dsl::*, schema::user_::dsl::*,
source::user::{UserForm, UserSafeSettings, User_}, source::user::{UserForm, UserSafeSettings, User_},
Url, DbUrl,
}; };
use lemmy_utils::settings::structs::Settings; use lemmy_utils::settings::structs::Settings;
@ -242,7 +242,7 @@ impl Crud<UserForm> for User_ {
} }
impl ApubObject<UserForm> for User_ { impl ApubObject<UserForm> for User_ {
fn read_from_apub_id(conn: &PgConnection, object_id: &Url) -> Result<Self, Error> { fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Self, Error> {
use lemmy_db_schema::schema::user_::dsl::*; use lemmy_db_schema::schema::user_::dsl::*;
user_ user_
.filter(deleted.eq(false)) .filter(deleted.eq(false))

View File

@ -12,4 +12,4 @@ chrono = { version = "0.4.19", features = ["serde"] }
serde = { version = "1.0.123", features = ["derive"] } serde = { version = "1.0.123", features = ["derive"] }
serde_json = { version = "1.0.61", features = ["preserve_order"] } serde_json = { version = "1.0.61", features = ["preserve_order"] }
log = "0.4.14" log = "0.4.14"
url = { version = "2.2.0", features = ["serde"] } url = { version = "2.2.1", features = ["serde"] }

View File

@ -8,21 +8,22 @@ use diesel::{
serialize::{Output, ToSql}, serialize::{Output, ToSql},
sql_types::Text, sql_types::Text,
}; };
use serde::Serialize; use serde::{Deserialize, Serialize};
use std::{ use std::{
fmt::{Display, Formatter}, fmt::{Display, Formatter},
io::Write, io::Write,
}; };
use url::Url;
pub mod schema; pub mod schema;
pub mod source; pub mod source;
#[repr(transparent)] #[repr(transparent)]
#[derive(Clone, PartialEq, Serialize, Debug, AsExpression, FromSqlRow)] #[derive(Clone, PartialEq, Serialize, Deserialize, Debug, AsExpression, FromSqlRow)]
#[sql_type = "Text"] #[sql_type = "Text"]
pub struct Url(url::Url); pub struct DbUrl(Url);
impl<DB: Backend> ToSql<Text, DB> for Url impl<DB: Backend> ToSql<Text, DB> for DbUrl
where where
String: ToSql<Text, DB>, String: ToSql<Text, DB>,
{ {
@ -31,37 +32,37 @@ where
} }
} }
impl<DB: Backend> FromSql<Text, DB> for Url impl<DB: Backend> FromSql<Text, DB> for DbUrl
where where
String: FromSql<Text, DB>, String: FromSql<Text, DB>,
{ {
fn from_sql(bytes: Option<&DB::RawValue>) -> diesel::deserialize::Result<Self> { fn from_sql(bytes: Option<&DB::RawValue>) -> diesel::deserialize::Result<Self> {
let str = String::from_sql(bytes)?; let str = String::from_sql(bytes)?;
Ok(Url(url::Url::parse(&str)?)) Ok(DbUrl(Url::parse(&str)?))
} }
} }
impl Url { impl DbUrl {
pub fn into_inner(self) -> url::Url { pub fn into_inner(self) -> Url {
self.0 self.0
} }
} }
impl Display for Url { impl Display for DbUrl {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.to_owned().into_inner().fmt(f) self.to_owned().into_inner().fmt(f)
} }
} }
impl From<Url> for url::Url { impl From<DbUrl> for Url {
fn from(url: Url) -> Self { fn from(url: DbUrl) -> Self {
url.0 url.0
} }
} }
impl From<url::Url> for Url { impl From<Url> for DbUrl {
fn from(url: url::Url) -> Self { fn from(url: Url) -> Self {
Url(url) DbUrl(url)
} }
} }

View File

@ -1,4 +1,4 @@
use crate::schema::activity; use crate::{schema::activity, DbUrl};
use serde_json::Value; use serde_json::Value;
use std::fmt::Debug; use std::fmt::Debug;
@ -10,7 +10,7 @@ pub struct Activity {
pub local: bool, pub local: bool,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub ap_id: Option<String>, pub ap_id: Option<DbUrl>,
pub sensitive: Option<bool>, pub sensitive: Option<bool>,
} }
@ -20,6 +20,6 @@ pub struct ActivityForm {
pub data: Value, pub data: Value,
pub local: bool, pub local: bool,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub ap_id: String, pub ap_id: DbUrl,
pub sensitive: bool, pub sensitive: bool,
} }

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
schema::{comment, comment_alias_1, comment_like, comment_saved}, schema::{comment, comment_alias_1, comment_like, comment_saved},
source::post::Post, source::post::Post,
Url, DbUrl,
}; };
use serde::Serialize; use serde::Serialize;
@ -26,7 +26,7 @@ pub struct Comment {
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool, pub deleted: bool,
pub ap_id: Url, pub ap_id: DbUrl,
pub local: bool, pub local: bool,
} }
@ -44,7 +44,7 @@ pub struct CommentAlias1 {
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool, pub deleted: bool,
pub ap_id: Url, pub ap_id: DbUrl,
pub local: bool, pub local: bool,
} }
@ -60,7 +60,7 @@ pub struct CommentForm {
pub published: Option<chrono::NaiveDateTime>, pub published: Option<chrono::NaiveDateTime>,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub deleted: Option<bool>, pub deleted: Option<bool>,
pub ap_id: Option<Url>, pub ap_id: Option<DbUrl>,
pub local: bool, pub local: bool,
} }

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
schema::{community, community_follower, community_moderator, community_user_ban}, schema::{community, community_follower, community_moderator, community_user_ban},
Url, DbUrl,
}; };
use serde::Serialize; use serde::Serialize;
@ -17,16 +17,16 @@ pub struct Community {
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool, pub deleted: bool,
pub nsfw: bool, pub nsfw: bool,
pub actor_id: Url, pub actor_id: DbUrl,
pub local: bool, pub local: bool,
pub private_key: Option<String>, pub private_key: Option<String>,
pub public_key: Option<String>, pub public_key: Option<String>,
pub last_refreshed_at: chrono::NaiveDateTime, pub last_refreshed_at: chrono::NaiveDateTime,
pub icon: Option<String>, pub icon: Option<DbUrl>,
pub banner: Option<String>, pub banner: Option<DbUrl>,
pub followers_url: Url, pub followers_url: DbUrl,
pub inbox_url: Url, pub inbox_url: DbUrl,
pub shared_inbox_url: Option<Url>, pub shared_inbox_url: Option<DbUrl>,
} }
/// A safe representation of community, without the sensitive info /// A safe representation of community, without the sensitive info
@ -43,10 +43,10 @@ pub struct CommunitySafe {
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool, pub deleted: bool,
pub nsfw: bool, pub nsfw: bool,
pub actor_id: Url, pub actor_id: DbUrl,
pub local: bool, pub local: bool,
pub icon: Option<String>, pub icon: Option<DbUrl>,
pub banner: Option<String>, pub banner: Option<DbUrl>,
} }
#[derive(Insertable, AsChangeset, Debug)] #[derive(Insertable, AsChangeset, Debug)]
@ -61,16 +61,16 @@ pub struct CommunityForm {
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub deleted: Option<bool>, pub deleted: Option<bool>,
pub nsfw: bool, pub nsfw: bool,
pub actor_id: Option<Url>, pub actor_id: Option<DbUrl>,
pub local: bool, pub local: bool,
pub private_key: Option<String>, pub private_key: Option<String>,
pub public_key: Option<String>, pub public_key: Option<String>,
pub last_refreshed_at: Option<chrono::NaiveDateTime>, pub last_refreshed_at: Option<chrono::NaiveDateTime>,
pub icon: Option<Option<String>>, pub icon: Option<Option<DbUrl>>,
pub banner: Option<Option<String>>, pub banner: Option<Option<DbUrl>>,
pub followers_url: Option<Url>, pub followers_url: Option<DbUrl>,
pub inbox_url: Option<Url>, pub inbox_url: Option<DbUrl>,
pub shared_inbox_url: Option<Option<Url>>, pub shared_inbox_url: Option<Option<DbUrl>>,
} }
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
schema::{post, post_like, post_read, post_saved}, schema::{post, post_like, post_read, post_saved},
Url, DbUrl,
}; };
use serde::Serialize; use serde::Serialize;
@ -9,7 +9,7 @@ use serde::Serialize;
pub struct Post { pub struct Post {
pub id: i32, pub id: i32,
pub name: String, pub name: String,
pub url: Option<String>, pub url: Option<DbUrl>,
pub body: Option<String>, pub body: Option<String>,
pub creator_id: i32, pub creator_id: i32,
pub community_id: i32, pub community_id: i32,
@ -23,8 +23,8 @@ pub struct Post {
pub embed_title: Option<String>, pub embed_title: Option<String>,
pub embed_description: Option<String>, pub embed_description: Option<String>,
pub embed_html: Option<String>, pub embed_html: Option<String>,
pub thumbnail_url: Option<String>, pub thumbnail_url: Option<DbUrl>,
pub ap_id: Url, pub ap_id: DbUrl,
pub local: bool, pub local: bool,
} }
@ -32,7 +32,7 @@ pub struct Post {
#[table_name = "post"] #[table_name = "post"]
pub struct PostForm { pub struct PostForm {
pub name: String, pub name: String,
pub url: Option<String>, pub url: Option<DbUrl>,
pub body: Option<String>, pub body: Option<String>,
pub creator_id: i32, pub creator_id: i32,
pub community_id: i32, pub community_id: i32,
@ -46,8 +46,8 @@ pub struct PostForm {
pub embed_title: Option<String>, pub embed_title: Option<String>,
pub embed_description: Option<String>, pub embed_description: Option<String>,
pub embed_html: Option<String>, pub embed_html: Option<String>,
pub thumbnail_url: Option<String>, pub thumbnail_url: Option<DbUrl>,
pub ap_id: Option<Url>, pub ap_id: Option<DbUrl>,
pub local: bool, pub local: bool,
} }

View File

@ -1,4 +1,4 @@
use crate::{schema::post_report, source::post::Post}; use crate::{schema::post_report, source::post::Post, DbUrl};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive( #[derive(
@ -11,7 +11,7 @@ pub struct PostReport {
pub creator_id: i32, pub creator_id: i32,
pub post_id: i32, pub post_id: i32,
pub original_post_name: String, pub original_post_name: String,
pub original_post_url: Option<String>, pub original_post_url: Option<DbUrl>,
pub original_post_body: Option<String>, pub original_post_body: Option<String>,
pub reason: String, pub reason: String,
pub resolved: bool, pub resolved: bool,
@ -26,7 +26,7 @@ pub struct PostReportForm {
pub creator_id: i32, pub creator_id: i32,
pub post_id: i32, pub post_id: i32,
pub original_post_name: String, pub original_post_name: String,
pub original_post_url: Option<String>, pub original_post_url: Option<DbUrl>,
pub original_post_body: Option<String>, pub original_post_body: Option<String>,
pub reason: String, pub reason: String,
} }

View File

@ -1,4 +1,4 @@
use crate::{schema::private_message, Url}; use crate::{schema::private_message, DbUrl};
use serde::Serialize; use serde::Serialize;
#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)] #[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
@ -12,7 +12,7 @@ pub struct PrivateMessage {
pub read: bool, pub read: bool,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub ap_id: Url, pub ap_id: DbUrl,
pub local: bool, pub local: bool,
} }
@ -26,6 +26,6 @@ pub struct PrivateMessageForm {
pub read: Option<bool>, pub read: Option<bool>,
pub published: Option<chrono::NaiveDateTime>, pub published: Option<chrono::NaiveDateTime>,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub ap_id: Option<Url>, pub ap_id: Option<DbUrl>,
pub local: bool, pub local: bool,
} }

View File

@ -1,4 +1,4 @@
use crate::schema::site; use crate::{schema::site, DbUrl};
use serde::Serialize; use serde::Serialize;
#[derive(Queryable, Identifiable, PartialEq, Debug, Clone, Serialize)] #[derive(Queryable, Identifiable, PartialEq, Debug, Clone, Serialize)]
@ -13,8 +13,8 @@ pub struct Site {
pub enable_downvotes: bool, pub enable_downvotes: bool,
pub open_registration: bool, pub open_registration: bool,
pub enable_nsfw: bool, pub enable_nsfw: bool,
pub icon: Option<String>, pub icon: Option<DbUrl>,
pub banner: Option<String>, pub banner: Option<DbUrl>,
} }
#[derive(Insertable, AsChangeset)] #[derive(Insertable, AsChangeset)]
@ -28,6 +28,6 @@ pub struct SiteForm {
pub open_registration: bool, pub open_registration: bool,
pub enable_nsfw: bool, pub enable_nsfw: bool,
// when you want to null out a column, you have to send Some(None)), since sending None means you just don't want to update that column. // when you want to null out a column, you have to send Some(None)), since sending None means you just don't want to update that column.
pub icon: Option<Option<String>>, pub icon: Option<Option<DbUrl>>,
pub banner: Option<Option<String>>, pub banner: Option<Option<DbUrl>>,
} }

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
schema::{user_, user_alias_1, user_alias_2}, schema::{user_, user_alias_1, user_alias_2},
Url, DbUrl,
}; };
use serde::Serialize; use serde::Serialize;
@ -12,7 +12,7 @@ pub struct User_ {
pub preferred_username: Option<String>, pub preferred_username: Option<String>,
pub password_encrypted: String, pub password_encrypted: String,
pub email: Option<String>, pub email: Option<String>,
pub avatar: Option<String>, pub avatar: Option<DbUrl>,
pub admin: bool, pub admin: bool,
pub banned: bool, pub banned: bool,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
@ -25,16 +25,16 @@ pub struct User_ {
pub show_avatars: bool, pub show_avatars: bool,
pub send_notifications_to_email: bool, pub send_notifications_to_email: bool,
pub matrix_user_id: Option<String>, pub matrix_user_id: Option<String>,
pub actor_id: Url, pub actor_id: DbUrl,
pub bio: Option<String>, pub bio: Option<String>,
pub local: bool, pub local: bool,
pub private_key: Option<String>, pub private_key: Option<String>,
pub public_key: Option<String>, pub public_key: Option<String>,
pub last_refreshed_at: chrono::NaiveDateTime, pub last_refreshed_at: chrono::NaiveDateTime,
pub banner: Option<String>, pub banner: Option<DbUrl>,
pub deleted: bool, pub deleted: bool,
pub inbox_url: Url, pub inbox_url: DbUrl,
pub shared_inbox_url: Option<Url>, pub shared_inbox_url: Option<DbUrl>,
} }
/// A safe representation of user, without the sensitive info /// A safe representation of user, without the sensitive info
@ -44,19 +44,19 @@ pub struct UserSafe {
pub id: i32, pub id: i32,
pub name: String, pub name: String,
pub preferred_username: Option<String>, pub preferred_username: Option<String>,
pub avatar: Option<String>, pub avatar: Option<DbUrl>,
pub admin: bool, pub admin: bool,
pub banned: bool, pub banned: bool,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub matrix_user_id: Option<String>, pub matrix_user_id: Option<String>,
pub actor_id: Url, pub actor_id: DbUrl,
pub bio: Option<String>, pub bio: Option<String>,
pub local: bool, pub local: bool,
pub banner: Option<String>, pub banner: Option<DbUrl>,
pub deleted: bool, pub deleted: bool,
pub inbox_url: Url, pub inbox_url: DbUrl,
pub shared_inbox_url: Option<Url>, pub shared_inbox_url: Option<DbUrl>,
} }
/// A safe user view with only settings /// A safe user view with only settings
@ -67,7 +67,7 @@ pub struct UserSafeSettings {
pub name: String, pub name: String,
pub preferred_username: Option<String>, pub preferred_username: Option<String>,
pub email: Option<String>, pub email: Option<String>,
pub avatar: Option<String>, pub avatar: Option<DbUrl>,
pub admin: bool, pub admin: bool,
pub banned: bool, pub banned: bool,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
@ -80,11 +80,11 @@ pub struct UserSafeSettings {
pub show_avatars: bool, pub show_avatars: bool,
pub send_notifications_to_email: bool, pub send_notifications_to_email: bool,
pub matrix_user_id: Option<String>, pub matrix_user_id: Option<String>,
pub actor_id: Url, pub actor_id: DbUrl,
pub bio: Option<String>, pub bio: Option<String>,
pub local: bool, pub local: bool,
pub last_refreshed_at: chrono::NaiveDateTime, pub last_refreshed_at: chrono::NaiveDateTime,
pub banner: Option<String>, pub banner: Option<DbUrl>,
pub deleted: bool, pub deleted: bool,
} }
@ -96,7 +96,7 @@ pub struct UserAlias1 {
pub preferred_username: Option<String>, pub preferred_username: Option<String>,
pub password_encrypted: String, pub password_encrypted: String,
pub email: Option<String>, pub email: Option<String>,
pub avatar: Option<String>, pub avatar: Option<DbUrl>,
pub admin: bool, pub admin: bool,
pub banned: bool, pub banned: bool,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
@ -109,13 +109,13 @@ pub struct UserAlias1 {
pub show_avatars: bool, pub show_avatars: bool,
pub send_notifications_to_email: bool, pub send_notifications_to_email: bool,
pub matrix_user_id: Option<String>, pub matrix_user_id: Option<String>,
pub actor_id: Url, pub actor_id: DbUrl,
pub bio: Option<String>, pub bio: Option<String>,
pub local: bool, pub local: bool,
pub private_key: Option<String>, pub private_key: Option<String>,
pub public_key: Option<String>, pub public_key: Option<String>,
pub last_refreshed_at: chrono::NaiveDateTime, pub last_refreshed_at: chrono::NaiveDateTime,
pub banner: Option<String>, pub banner: Option<DbUrl>,
pub deleted: bool, pub deleted: bool,
} }
@ -125,16 +125,16 @@ pub struct UserSafeAlias1 {
pub id: i32, pub id: i32,
pub name: String, pub name: String,
pub preferred_username: Option<String>, pub preferred_username: Option<String>,
pub avatar: Option<String>, pub avatar: Option<DbUrl>,
pub admin: bool, pub admin: bool,
pub banned: bool, pub banned: bool,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub matrix_user_id: Option<String>, pub matrix_user_id: Option<String>,
pub actor_id: Url, pub actor_id: DbUrl,
pub bio: Option<String>, pub bio: Option<String>,
pub local: bool, pub local: bool,
pub banner: Option<String>, pub banner: Option<DbUrl>,
pub deleted: bool, pub deleted: bool,
} }
@ -146,7 +146,7 @@ pub struct UserAlias2 {
pub preferred_username: Option<String>, pub preferred_username: Option<String>,
pub password_encrypted: String, pub password_encrypted: String,
pub email: Option<String>, pub email: Option<String>,
pub avatar: Option<String>, pub avatar: Option<DbUrl>,
pub admin: bool, pub admin: bool,
pub banned: bool, pub banned: bool,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
@ -159,13 +159,13 @@ pub struct UserAlias2 {
pub show_avatars: bool, pub show_avatars: bool,
pub send_notifications_to_email: bool, pub send_notifications_to_email: bool,
pub matrix_user_id: Option<String>, pub matrix_user_id: Option<String>,
pub actor_id: Url, pub actor_id: DbUrl,
pub bio: Option<String>, pub bio: Option<String>,
pub local: bool, pub local: bool,
pub private_key: Option<String>, pub private_key: Option<String>,
pub public_key: Option<String>, pub public_key: Option<String>,
pub last_refreshed_at: chrono::NaiveDateTime, pub last_refreshed_at: chrono::NaiveDateTime,
pub banner: Option<String>, pub banner: Option<DbUrl>,
pub deleted: bool, pub deleted: bool,
} }
@ -175,16 +175,16 @@ pub struct UserSafeAlias2 {
pub id: i32, pub id: i32,
pub name: String, pub name: String,
pub preferred_username: Option<String>, pub preferred_username: Option<String>,
pub avatar: Option<String>, pub avatar: Option<DbUrl>,
pub admin: bool, pub admin: bool,
pub banned: bool, pub banned: bool,
pub published: chrono::NaiveDateTime, pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub matrix_user_id: Option<String>, pub matrix_user_id: Option<String>,
pub actor_id: Url, pub actor_id: DbUrl,
pub bio: Option<String>, pub bio: Option<String>,
pub local: bool, pub local: bool,
pub banner: Option<String>, pub banner: Option<DbUrl>,
pub deleted: bool, pub deleted: bool,
} }
@ -197,7 +197,7 @@ pub struct UserForm {
pub admin: bool, pub admin: bool,
pub banned: Option<bool>, pub banned: Option<bool>,
pub email: Option<Option<String>>, pub email: Option<Option<String>>,
pub avatar: Option<Option<String>>, pub avatar: Option<Option<DbUrl>>,
pub published: Option<chrono::NaiveDateTime>, pub published: Option<chrono::NaiveDateTime>,
pub updated: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>,
pub show_nsfw: bool, pub show_nsfw: bool,
@ -208,13 +208,13 @@ pub struct UserForm {
pub show_avatars: bool, pub show_avatars: bool,
pub send_notifications_to_email: bool, pub send_notifications_to_email: bool,
pub matrix_user_id: Option<Option<String>>, pub matrix_user_id: Option<Option<String>>,
pub actor_id: Option<Url>, pub actor_id: Option<DbUrl>,
pub bio: Option<Option<String>>, pub bio: Option<Option<String>>,
pub local: bool, pub local: bool,
pub private_key: Option<String>, pub private_key: Option<String>,
pub public_key: Option<String>, pub public_key: Option<String>,
pub last_refreshed_at: Option<chrono::NaiveDateTime>, pub last_refreshed_at: Option<chrono::NaiveDateTime>,
pub banner: Option<Option<String>>, pub banner: Option<Option<DbUrl>>,
pub inbox_url: Option<Url>, pub inbox_url: Option<DbUrl>,
pub shared_inbox_url: Option<Option<Url>>, pub shared_inbox_url: Option<Option<DbUrl>>,
} }

View File

@ -12,7 +12,7 @@ lemmy_db_schema = { path = "../db_schema" }
diesel = { version = "1.4.5", features = ["postgres","chrono","r2d2","serde_json"] } diesel = { version = "1.4.5", features = ["postgres","chrono","r2d2","serde_json"] }
serde = { version = "1.0.123", features = ["derive"] } serde = { version = "1.0.123", features = ["derive"] }
log = "0.4.14" log = "0.4.14"
url = "2.2.0" url = "2.2.1"
[dev-dependencies] [dev-dependencies]
serial_test = "0.5.1" serial_test = "0.5.1"

View File

@ -25,6 +25,6 @@ chrono = { version = "0.4.19", features = ["serde"] }
rss = "1.10.0" rss = "1.10.0"
serde = { version = "1.0.123", features = ["derive"] } serde = { version = "1.0.123", features = ["derive"] }
awc = { version = "2.0.3", default-features = false } awc = { version = "2.0.3", default-features = false }
url = { version = "2.2.0", features = ["serde"] } url = { version = "2.2.1", features = ["serde"] }
strum = "0.20.0" strum = "0.20.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"

View File

@ -22,7 +22,7 @@ thiserror = "1.0.23"
comrak = { version = "0.9.0", default-features = false } comrak = { version = "0.9.0", default-features = false }
lazy_static = "1.4.0" lazy_static = "1.4.0"
openssl = "0.10.32" openssl = "0.10.32"
url = { version = "2.2.0", features = ["serde"] } url = { version = "2.2.1", features = ["serde"] }
actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] } actix-web = { version = "3.3.2", default-features = false, features = ["rustls"] }
actix-rt = { version = "1.1.1", default-features = false } actix-rt = { version = "1.1.1", default-features = false }
anyhow = "1.0.38" anyhow = "1.0.38"

View File

@ -6,6 +6,7 @@ use reqwest::Client;
use serde::Deserialize; use serde::Deserialize;
use std::future::Future; use std::future::Future;
use thiserror::Error; use thiserror::Error;
use url::Url;
#[derive(Clone, Debug, Error)] #[derive(Clone, Debug, Error)]
#[error("Error sending request, {0}")] #[error("Error sending request, {0}")]
@ -50,13 +51,13 @@ where
pub(crate) struct IframelyResponse { pub(crate) struct IframelyResponse {
title: Option<String>, title: Option<String>,
description: Option<String>, description: Option<String>,
thumbnail_url: Option<String>, thumbnail_url: Option<Url>,
html: Option<String>, html: Option<String>,
} }
pub(crate) async fn fetch_iframely( pub(crate) async fn fetch_iframely(
client: &Client, client: &Client,
url: &str, url: &Url,
) -> Result<IframelyResponse, LemmyError> { ) -> Result<IframelyResponse, LemmyError> {
let fetch_url = format!("{}/oembed?url={}", Settings::get().iframely_url(), url); let fetch_url = format!("{}/oembed?url={}", Settings::get().iframely_url(), url);
@ -83,14 +84,14 @@ pub(crate) struct PictrsFile {
pub(crate) async fn fetch_pictrs( pub(crate) async fn fetch_pictrs(
client: &Client, client: &Client,
image_url: &str, image_url: &Url,
) -> Result<PictrsResponse, LemmyError> { ) -> Result<PictrsResponse, LemmyError> {
is_image_content_type(client, image_url).await?; is_image_content_type(client, image_url).await?;
let fetch_url = format!( let fetch_url = format!(
"{}/image/download?url={}", "{}/image/download?url={}",
Settings::get().pictrs_url(), Settings::get().pictrs_url(),
utf8_percent_encode(image_url, NON_ALPHANUMERIC) // TODO this might not be needed utf8_percent_encode(image_url.as_str(), NON_ALPHANUMERIC) // TODO this might not be needed
); );
let response = retry(|| client.get(&fetch_url).send()).await?; let response = retry(|| client.get(&fetch_url).send()).await?;
@ -109,13 +110,8 @@ pub(crate) async fn fetch_pictrs(
pub async fn fetch_iframely_and_pictrs_data( pub async fn fetch_iframely_and_pictrs_data(
client: &Client, client: &Client,
url: Option<String>, url: Option<&Url>,
) -> ( ) -> (Option<String>, Option<String>, Option<String>, Option<Url>) {
Option<String>,
Option<String>,
Option<String>,
Option<String>,
) {
match &url { match &url {
Some(url) => { Some(url) => {
// Fetch iframely data // Fetch iframely data
@ -149,11 +145,19 @@ pub async fn fetch_iframely_and_pictrs_data(
// The full urls are necessary for federation // The full urls are necessary for federation
let pictrs_thumbnail = if let Some(pictrs_hash) = pictrs_hash { let pictrs_thumbnail = if let Some(pictrs_hash) = pictrs_hash {
Some(format!( let url = Url::parse(&format!(
"{}/pictrs/image/{}", "{}/pictrs/image/{}",
Settings::get().get_protocol_and_hostname(), Settings::get().get_protocol_and_hostname(),
pictrs_hash pictrs_hash
)) ));
match url {
Ok(parsed_url) => Some(parsed_url),
Err(e) => {
// This really shouldn't happen unless the settings or hash are malformed
error!("Unexpected error constructing pictrs thumbnail URL: {}", e);
None
}
}
} else { } else {
None None
}; };
@ -169,9 +173,8 @@ pub async fn fetch_iframely_and_pictrs_data(
} }
} }
async fn is_image_content_type(client: &Client, test: &str) -> Result<(), LemmyError> { async fn is_image_content_type(client: &Client, test: &Url) -> Result<(), LemmyError> {
let response = retry(|| client.get(test).send()).await?; let response = retry(|| client.get(test.to_owned()).send()).await?;
if response if response
.headers() .headers()
.get("Content-Type") .get("Content-Type")

View File

@ -0,0 +1,4 @@
-- This is a clean-up migration that cannot be undone,
-- but Diesel requires a non-empty script so run a no-op.
SELECT 1;

View File

@ -0,0 +1 @@
UPDATE post SET url = NULL where url = '';