- [Rust docker build](https://shaneutt.com/blog/rust-fast-small-docker-image-builds/)
- [Zurb mentions](https://github.com/zurb/tribute)
- [TippyJS](https://github.com/atomiks/tippyjs)
-
- ## Activitypub guides
-
- - https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/
- - https://raw.githubusercontent.com/w3c/activitypub/gh-pages/activitypub-tutorial.txt
- - https://github.com/tOkeshu/activitypub-example
- - https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/
- - Use the [activitypub crate.](https://docs.rs/activitypub/0.1.4/activitypub/)
- - https://docs.rs/activitypub/0.1.4/activitypub/
- - [Activitypub vocab.](https://www.w3.org/TR/activitystreams-vocabulary/)
- - [Activitypub main](https://www.w3.org/TR/activitypub/)
- - [Federation.md](https://github.com/dariusk/gathio/blob/7fc93dbe9d4d99457a0e85c6c532112f415b7af2/FEDERATION.md)
- - [Activitypub implementers guide](https://socialhub.activitypub.rocks/t/draft-guide-for-new-activitypub-implementers/479)
- - [Data storage questions](https://socialhub.activitypub.rocks/t/data-storage-questions/579/3)
- - [Activitypub as it has been understood](https://flak.tedunangst.com/post/ActivityPub-as-it-has-been-understood)
- - [Asonix http signatures in rust](https://git.asonix.dog/Aardwolf/http-signature-normalization)
+- [SQL function indexes](https://sorentwo.com/2013/12/30/let-postgres-do-the-work.html)
comment::Comment,
comment_view::CommentQueryBuilder,
community::*,
- community_view::*,
diesel_option_overwrite,
moderator::*,
naive_now,
post::Post,
site::*,
- user_view::*,
+ views::{
+ community_follower_view::CommunityFollowerView,
+ community_moderator_view::CommunityModeratorView,
+ community_view::{CommunityQueryBuilder, CommunityView},
+ user_view::UserViewSafe,
+ },
+ ApubObject,
Bannable,
Crud,
Followable,
.unwrap_or(1);
let res = GetCommunityResponse {
- community: community_view,
+ community_view,
moderators,
online,
};
let actor_id = make_apub_endpoint(EndpointType::Community, &data.name).to_string();
let actor_id_cloned = actor_id.to_owned();
let community_dupe = blocking(context.pool(), move |conn| {
- Community::read_from_actor_id(conn, &actor_id_cloned)
+ Community::read_from_apub_id(conn, &actor_id_cloned)
})
.await?;
if community_dupe.is_ok() {
})
.await??;
- Ok(CommunityResponse {
- community: community_view,
- })
+ Ok(CommunityResponse { community_view })
}
}
let edit_id = data.edit_id;
let mods: Vec<i32> = blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, edit_id)
- .map(|v| v.into_iter().map(|m| m.user_id).collect())
+ .map(|v| v.into_iter().map(|m| m.moderator.id).collect())
})
.await??;
if !mods.contains(&user.id) {
})
.await??;
- let res = CommunityResponse {
- community: community_view,
- };
+ let res = CommunityResponse { community_view };
send_community_websocket(&res, context, websocket_id, UserOperation::EditCommunity);
})
.await??;
- let res = CommunityResponse {
- community: community_view,
- };
+ let res = CommunityResponse { community_view };
send_community_websocket(&res, context, websocket_id, UserOperation::DeleteCommunity);
})
.await??;
- let res = CommunityResponse {
- community: community_view,
- };
+ let res = CommunityResponse { community_view };
send_community_websocket(&res, context, websocket_id, UserOperation::RemoveCommunity);
let page = data.page;
let limit = data.limit;
let communities = blocking(context.pool(), move |conn| {
- CommunityQueryBuilder::create(conn)
+ CommunityQueryBuilder::create(conn, user_id)
.sort(&sort)
- .for_user(user_id)
.show_nsfw(show_nsfw)
.page(page)
.limit(limit)
// For now, just assume that remote follows are accepted.
// Otherwise, the subscribed will be null
if !community.local {
- community_view.subscribed = Some(data.follow);
+ community_view.subscribed = data.follow;
}
- Ok(CommunityResponse {
- community: community_view,
- })
+ Ok(CommunityResponse { community_view })
}
}
let user_id = data.user_id;
let user_view = blocking(context.pool(), move |conn| {
- UserView::get_user_secure(conn, user_id)
+ UserViewSafe::read(conn, user_id)
})
.await??;
let res = BanFromCommunityResponse {
- user: user_view,
+ user_view,
banned: data.ban,
};
})
.await??;
- let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
+ let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
let creator_index = admins
.iter()
- .position(|r| r.id == site_creator_id)
+ .position(|r| r.user.id == site_creator_id)
.context(location_info!())?;
let creator_user = admins.remove(creator_index);
admins.insert(0, creator_user);
// Make sure user is the creator, or an admin
- if user.id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user.id) {
+ if user.id != read_community.creator_id
+ && !admins.iter().map(|a| a.user.id).any(|x| x == user.id)
+ {
return Err(APIError::err("not_an_admin").into());
}
.await??;
let creator_index = community_mods
.iter()
- .position(|r| r.user_id == data.user_id)
+ .position(|r| r.moderator.id == data.user_id)
.context(location_info!())?;
let creator_user = community_mods.remove(creator_index);
community_mods.insert(0, creator_user);
// TODO: this should probably be a bulk operation
for cmod in &community_mods {
let community_moderator_form = CommunityModeratorForm {
- community_id: cmod.community_id,
- user_id: cmod.user_id,
+ community_id: cmod.community.id,
+ user_id: cmod.moderator.id,
};
let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
// Return the jwt
Ok(GetCommunityResponse {
- community: community_view,
+ community_view,
moderators,
online: 0,
})
websocket_id: Option<ConnectionId>,
op: UserOperation,
) {
+ // TODO is there any way around this?
// Strip out the user id and subscribed when sending to others
- let mut res_sent = res.clone();
- res_sent.community.user_id = None;
- res_sent.community.subscribed = None;
+ // let mut res_sent = res.clone();
+ // res_sent.community_view.user_id = None;
+ // res_sent.community.subscribed = None;
context.chat_server().do_send(SendCommunityRoomMessage {
op,
- response: res_sent,
- community_id: res.community.id,
+ response: res.to_owned(),
+ community_id: res.community_view.community.id,
websocket_id,
});
}
base::{AnyBase, ExtendsExt},
};
use anyhow::Context;
- use lemmy_db::{community::Community, views::community_view::CommunityView};
-use lemmy_db::{community::Community, community_view::CommunityView, ApubObject};
++use lemmy_db::{community::Community, views::community_view::CommunityView, ApubObject};
use lemmy_structs::{blocking, community::CommunityResponse};
use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::{messages::SendCommunityRoomMessage, LemmyContext, UserOperation};
let community_id = deleted_community.id;
let res = CommunityResponse {
- community: blocking(context.pool(), move |conn| {
+ community_view: blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, None)
})
.await??,
};
- let community_id = res.community.id;
+ let community_id = res.community_view.community.id;
context.chat_server().do_send(SendCommunityRoomMessage {
op: UserOperation::EditCommunity,
response: res,
.single_xsd_any_uri()
.context(location_info!())?;
let community = blocking(context.pool(), move |conn| {
- Community::read_from_actor_id(conn, community_uri.as_str())
+ Community::read_from_apub_id(conn, community_uri.as_str())
})
.await??;
let community_id = removed_community.id;
let res = CommunityResponse {
- community: blocking(context.pool(), move |conn| {
+ community_view: blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, None)
})
.await??,
};
- let community_id = res.community.id;
+ let community_id = res.community_view.community.id;
context.chat_server().do_send(SendCommunityRoomMessage {
op: UserOperation::EditCommunity,
response: res,
let community_id = deleted_community.id;
let res = CommunityResponse {
- community: blocking(context.pool(), move |conn| {
+ community_view: blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, None)
})
.await??,
};
- let community_id = res.community.id;
+ let community_id = res.community_view.community.id;
context.chat_server().do_send(SendCommunityRoomMessage {
op: UserOperation::EditCommunity,
response: res,
.single_xsd_any_uri()
.context(location_info!())?;
let community = blocking(context.pool(), move |conn| {
- Community::read_from_actor_id(conn, community_uri.as_str())
+ Community::read_from_apub_id(conn, community_uri.as_str())
})
.await??;
let community_id = removed_community.id;
let res = CommunityResponse {
- community: blocking(context.pool(), move |conn| {
+ community_view: blocking(context.pool(), move |conn| {
CommunityView::read(conn, community_id, None)
})
.await??,
};
- let community_id = res.community.id;
+ let community_id = res.community_view.community.id;
context.chat_server().do_send(SendCommunityRoomMessage {
op: UserOperation::EditCommunity,
check_is_apub_id_valid,
fetcher::get_or_fetch_and_upsert_user,
inbox::get_activity_to_and_cc,
- FromApub,
+ objects::FromApub,
NoteExt,
};
use activitystreams::{
public,
};
use anyhow::{anyhow, Context};
- use lemmy_db::{
- private_message::{PrivateMessage, PrivateMessageForm},
- private_message_view::PrivateMessageView,
- Crud,
- };
+ use lemmy_db::{private_message::PrivateMessage, private_message_view::PrivateMessageView};
use lemmy_structs::{blocking, user::PrivateMessageResponse};
use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::{messages::SendUserRoomMessage, LemmyContext, UserOperation};
.context(location_info!())?;
let private_message =
- PrivateMessageForm::from_apub(¬e, context, Some(expected_domain), request_counter).await?;
-
- let inserted_private_message = blocking(&context.pool(), move |conn| {
- PrivateMessage::create(conn, &private_message)
- })
- .await??;
+ PrivateMessage::from_apub(¬e, context, expected_domain, request_counter).await?;
let message = blocking(&context.pool(), move |conn| {
- PrivateMessageView::read(conn, inserted_private_message.id)
+ PrivateMessageView::read(conn, private_message.id)
})
.await??;
.to_owned();
let note = NoteExt::from_any_base(object)?.context(location_info!())?;
- let private_message_form =
- PrivateMessageForm::from_apub(¬e, context, Some(expected_domain), request_counter).await?;
-
- let private_message_ap_id = private_message_form
- .ap_id
- .as_ref()
- .context(location_info!())?
- .clone();
- let private_message = blocking(&context.pool(), move |conn| {
- PrivateMessage::read_from_apub_id(conn, &private_message_ap_id)
- })
- .await??;
-
- let private_message_id = private_message.id;
- blocking(&context.pool(), move |conn| {
- PrivateMessage::update(conn, private_message_id, &private_message_form)
- })
- .await??;
+ let private_message =
+ PrivateMessage::from_apub(¬e, context, expected_domain, request_counter).await?;
let private_message_id = private_message.id;
let message = blocking(&context.pool(), move |conn| {
where
T: AsBase<Kind> + AsObject<Kind> + ActorAndObjectRefExt,
{
- let to_and_cc = get_activity_to_and_cc(activity)?;
+ let to_and_cc = get_activity_to_and_cc(activity);
if to_and_cc.len() != 1 {
return Err(anyhow!("Private message can only be addressed to one user").into());
}
use crate::{
check_is_apub_id_valid,
+ objects::FromApub,
ActorType,
- FromApub,
GroupExt,
NoteExt,
PageExt,
use chrono::NaiveDateTime;
use diesel::result::Error::NotFound;
use lemmy_db::{
- comment::{Comment, CommentForm},
+ comment::Comment,
comment_view::CommentView,
- community::{Community, CommunityForm, CommunityModerator, CommunityModeratorForm},
+ community::{Community, CommunityModerator, CommunityModeratorForm},
- community_view::CommunityView,
naive_now,
- post::{Post, PostForm},
+ post::Post,
post_view::PostView,
- user::{UserForm, User_},
+ user::User_,
- user_view::UserView,
+ views::{community_view::CommunityView, user_view::UserViewSafe},
- Crud,
+ ApubObject,
Joinable,
SearchType,
};
response.users = vec![
blocking(context.pool(), move |conn| {
- UserView::get_user_secure(conn, user.id)
+ UserViewSafe::read(conn, user.id)
})
.await??,
];
response
}
SearchAcceptedObjects::Page(p) => {
- let post_form = PostForm::from_apub(&p, context, Some(query_url), recursion_counter).await?;
+ let p = Post::from_apub(&p, context, query_url, recursion_counter).await?;
- let p = blocking(context.pool(), move |conn| Post::upsert(conn, &post_form)).await??;
response.posts =
vec![blocking(context.pool(), move |conn| PostView::read(conn, p.id, None)).await??];
response
}
SearchAcceptedObjects::Comment(c) => {
- let comment_form =
- CommentForm::from_apub(&c, context, Some(query_url), recursion_counter).await?;
+ let c = Comment::from_apub(&c, context, query_url, recursion_counter).await?;
- let c = blocking(context.pool(), move |conn| {
- Comment::upsert(conn, &comment_form)
- })
- .await??;
response.comments = vec![
blocking(context.pool(), move |conn| {
CommentView::read(conn, c.id, None)
) -> Result<User_, LemmyError> {
let apub_id_owned = apub_id.to_owned();
let user = blocking(context.pool(), move |conn| {
- User_::read_from_actor_id(conn, apub_id_owned.as_ref())
+ User_::read_from_apub_id(conn, apub_id_owned.as_ref())
})
.await?;
return Ok(u);
}
- let mut uf = UserForm::from_apub(
- &person?,
- context,
- Some(apub_id.to_owned()),
- recursion_counter,
- )
- .await?;
- uf.last_refreshed_at = Some(naive_now());
- let user = blocking(context.pool(), move |conn| User_::update(conn, u.id, &uf)).await??;
+ let user = User_::from_apub(&person?, context, apub_id.to_owned(), recursion_counter).await?;
+
+ let user_id = user.id;
+ blocking(context.pool(), move |conn| {
+ User_::mark_as_updated(conn, user_id)
+ })
+ .await??;
Ok(user)
}
let person =
fetch_remote_object::<PersonExt>(context.client(), apub_id, recursion_counter).await?;
- let uf = UserForm::from_apub(
- &person,
- context,
- Some(apub_id.to_owned()),
- recursion_counter,
- )
- .await?;
- let user = blocking(context.pool(), move |conn| User_::upsert(conn, &uf)).await??;
+ let user = User_::from_apub(&person, context, apub_id.to_owned(), recursion_counter).await?;
Ok(user)
}
) -> Result<Community, LemmyError> {
let apub_id_owned = apub_id.to_owned();
let community = blocking(context.pool(), move |conn| {
- Community::read_from_actor_id(conn, apub_id_owned.as_str())
+ Community::read_from_apub_id(conn, apub_id_owned.as_str())
})
.await?;
}
let group = group?;
- let cf =
- CommunityForm::from_apub(&group, context, Some(apub_id.to_owned()), recursion_counter).await?;
- let community = blocking(context.pool(), move |conn| Community::upsert(conn, &cf)).await??;
+ let community =
+ Community::from_apub(&group, context, apub_id.to_owned(), recursion_counter).await?;
// Also add the community moderators too
let attributed_to = group.inner.attributed_to().context(location_info!())?;
}
for o in outbox_items {
let page = PageExt::from_any_base(o)?.context(location_info!())?;
+ let page_id = page.id_unchecked().context(location_info!())?;
- // The post creator may be from a blocked instance,
- // if it errors, then continue
- let post = match PostForm::from_apub(&page, context, None, recursion_counter).await {
- Ok(post) => post,
- Err(_) => continue,
- };
- let post_ap_id = post.ap_id.as_ref().context(location_info!())?.clone();
- // Check whether the post already exists in the local db
- let existing = blocking(context.pool(), move |conn| {
- Post::read_from_apub_id(conn, &post_ap_id)
- })
- .await?;
- match existing {
- Ok(e) => blocking(context.pool(), move |conn| Post::update(conn, e.id, &post)).await??,
- Err(_) => blocking(context.pool(), move |conn| Post::upsert(conn, &post)).await??,
- };
+ // The post creator may be from a blocked instance, if it errors, then skip it
+ if check_is_apub_id_valid(page_id).is_err() {
+ continue;
+ }
+ Post::from_apub(&page, context, page_id.to_owned(), recursion_counter).await?;
// TODO: we need to send a websocket update here
}
Ok(p) => Ok(p),
Err(NotFound {}) => {
debug!("Fetching and creating remote post: {}", post_ap_id);
- let post =
+ let page =
fetch_remote_object::<PageExt>(context.client(), post_ap_id, recursion_counter).await?;
- let post_form = PostForm::from_apub(
- &post,
- context,
- Some(post_ap_id.to_owned()),
- recursion_counter,
- )
- .await?;
-
- let post = blocking(context.pool(), move |conn| Post::upsert(conn, &post_form)).await??;
+ let post = Post::from_apub(&page, context, post_ap_id.to_owned(), recursion_counter).await?;
Ok(post)
}
);
let comment =
fetch_remote_object::<NoteExt>(context.client(), comment_ap_id, recursion_counter).await?;
- let comment_form = CommentForm::from_apub(
+ let comment = Comment::from_apub(
&comment,
context,
- Some(comment_ap_id.to_owned()),
+ comment_ap_id.to_owned(),
recursion_counter,
)
.await?;
- let post_id = comment_form.post_id;
+ let post_id = comment.post_id;
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
if post.locked {
return Err(anyhow!("Post is locked").into());
}
- let comment = blocking(context.pool(), move |conn| {
- Comment::upsert(conn, &comment_form)
- })
- .await??;
-
Ok(comment)
}
Err(e) => Err(e.into()),
use crate::{
extensions::context::lemmy_context,
http::{create_apub_response, create_apub_tombstone_response},
+ objects::ToApub,
ActorType,
- ToApub,
};
use activitystreams::{
base::{AnyBase, BaseExt, ExtendsExt},
collection::{CollectionExt, OrderedCollection, UnorderedCollection},
};
use actix_web::{body::Body, web, HttpResponse};
-use lemmy_db::{community::Community, community_view::CommunityFollowerView, post::Post};
+use lemmy_db::{
+ community::Community,
+ post::Post,
+ views::community_follower_view::CommunityFollowerView,
+};
use lemmy_structs::blocking;
use lemmy_utils::LemmyError;
use lemmy_websocket::LemmyContext;
use anyhow::{anyhow, Context};
use lemmy_db::{
community::{Community, CommunityFollower, CommunityFollowerForm},
- community_view::CommunityUserBanView,
user::User_,
+ views::community_user_ban_view::CommunityUserBanView,
+ ApubObject,
DbPool,
Followable,
};
Community::read_from_name(&conn, &path)
})
.await??;
- let to_and_cc = get_activity_to_and_cc(&activity)?;
+ let to_and_cc = get_activity_to_and_cc(&activity);
if !to_and_cc.contains(&&community.actor_id()?) {
return Err(anyhow!("Activity delivered to wrong community").into());
}
// unconditionally.
let actor_id = actor.actor_id_str();
let user = blocking(&context.pool(), move |conn| {
- User_::read_from_actor_id(&conn, &actor_id)
+ User_::read_from_apub_id(&conn, &actor_id)
})
.await??;
check_community_or_site_ban(&user, &to_community, context.pool()).await?;
verify_activity_domains_valid(&follow, &user_url, false)?;
let user = blocking(&context.pool(), move |conn| {
- User_::read_from_actor_id(&conn, user_url.as_str())
+ User_::read_from_apub_id(&conn, user_url.as_str())
})
.await??;
let community_follower_form = CommunityFollowerForm {
Ok(())
}
- async fn check_community_or_site_ban(
+ pub(crate) async fn check_community_or_site_ban(
user: &User_,
community: &Community,
pool: &DbPool,
};
use actix_web::HttpRequest;
use anyhow::{anyhow, Context};
- use lemmy_db::{activity::Activity, community::Community, user::User_, DbPool};
+ use lemmy_db::{activity::Activity, community::Community, user::User_, ApubObject, DbPool};
use lemmy_structs::blocking;
use lemmy_utils::{location_info, settings::Settings, LemmyError};
use lemmy_websocket::LemmyContext;
}
}
-pub(crate) fn get_activity_to_and_cc<T, Kind>(activity: &T) -> Result<Vec<Url>, LemmyError>
+pub(crate) fn get_activity_to_and_cc<T, Kind>(activity: &T) -> Vec<Url>
where
T: AsBase<Kind> + AsObject<Kind> + ActorAndObjectRefExt,
{
.collect();
to_and_cc.append(&mut cc);
}
- Ok(to_and_cc)
+ to_and_cc
}
pub(crate) fn is_addressed_to_public<T, Kind>(activity: &T) -> Result<(), LemmyError>
where
T: AsBase<Kind> + AsObject<Kind> + ActorAndObjectRefExt,
{
- let to_and_cc = get_activity_to_and_cc(activity)?;
+ let to_and_cc = get_activity_to_and_cc(activity);
if to_and_cc.contains(&public()) {
Ok(())
} else {
) -> Result<bool, LemmyError> {
for url in to_and_cc {
let url = url.to_string();
- let user = blocking(&pool, move |conn| User_::read_from_actor_id(&conn, &url)).await?;
+ let user = blocking(&pool, move |conn| User_::read_from_apub_id(&conn, &url)).await?;
if let Ok(u) = user {
if u.local {
return Ok(true);
if url.ends_with("/followers") {
let community_url = url.replace("/followers", "");
let community = blocking(&pool, move |conn| {
- Community::read_from_actor_id(&conn, &community_url)
+ Community::read_from_apub_id(&conn, &community_url)
})
.await??;
if !community.local {
use activitystreams::{activity::ActorAndObject, prelude::*};
use actix_web::{web, HttpRequest, HttpResponse};
use anyhow::Context;
- use lemmy_db::{community::Community, DbPool};
+ use lemmy_db::{community::Community, ApubObject, DbPool};
use lemmy_structs::blocking;
use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext;
let activity_any_base = activity.clone().into_any_base()?;
let mut res: Option<HttpResponse> = None;
- let to_and_cc = get_activity_to_and_cc(&activity)?;
+ let to_and_cc = get_activity_to_and_cc(&activity);
// Handle community first, so in case the sender is banned by the community, it will error out.
// If we handled the user receive first, the activity would be inserted to the database before the
// community could check for bans.
) -> Result<Option<Community>, LemmyError> {
for url in to_and_cc {
let url = url.to_string();
- let community = blocking(&pool, move |conn| {
- Community::read_from_actor_id(&conn, &url)
- })
- .await?;
+ let community = blocking(&pool, move |conn| Community::read_from_apub_id(&conn, &url)).await?;
if let Ok(c) = community {
if c.local {
return Ok(Some(c));
community::{Community, CommunityFollower},
private_message::PrivateMessage,
user::User_,
+ ApubObject,
Followable,
};
use lemmy_structs::blocking;
User_::read_from_name(&conn, &username)
})
.await??;
- let to_and_cc = get_activity_to_and_cc(&activity)?;
+ let to_and_cc = get_activity_to_and_cc(&activity);
// TODO: we should also accept activities that are sent to community followers
if !to_and_cc.contains(&&user.actor_id()?) {
return Err(anyhow!("Activity delivered to wrong user").into());
context: &LemmyContext,
activity: &UserAcceptedActivities,
) -> Result<(), LemmyError> {
- let to_and_cc = get_activity_to_and_cc(activity)?;
+ let to_and_cc = get_activity_to_and_cc(activity);
// Check if it is addressed directly to any local user
if is_addressed_to_local_user(&to_and_cc, context.pool()).await? {
return Ok(());
) -> Result<CommunityOrPrivateMessage, LemmyError> {
let ap_id = apub_id.to_string();
let community = blocking(context.pool(), move |conn| {
- Community::read_from_actor_id(conn, &ap_id)
+ Community::read_from_apub_id(conn, &ap_id)
})
.await?;
if let Ok(c) = community {
objects::{
check_object_domain,
create_tombstone,
+ get_object_from_apub,
get_source_markdown_value,
set_content_and_source,
+ FromApub,
+ FromApubToForm,
+ ToApub,
},
ActorType,
- FromApub,
GroupExt,
- ToApub,
};
use activitystreams::{
actor::{kind::GroupType, ApActor, Endpoints, Group},
use anyhow::Context;
use lemmy_db::{
community::{Community, CommunityForm},
- community_view::CommunityModeratorView,
naive_now,
+ views::community_moderator_view::CommunityModeratorView,
DbPool,
};
use lemmy_structs::blocking;
CommunityModeratorView::for_community(&conn, id)
})
.await??;
- let moderators: Vec<String> = moderators.into_iter().map(|m| m.user_actor_id).collect();
+ let moderators: Vec<String> = moderators
+ .into_iter()
+ .map(|m| m.moderator.actor_id)
+ .collect();
let mut group = ApObject::new(Group::new());
group
create_tombstone(self.deleted, &self.actor_id, self.updated, GroupType::Group)
}
}
+
#[async_trait::async_trait(?Send)]
- impl FromApub for CommunityForm {
+ impl FromApub for Community {
type ApubType = GroupExt;
+ /// Converts a `Group` to `Community`.
+ async fn from_apub(
+ group: &GroupExt,
+ context: &LemmyContext,
+ expected_domain: Url,
+ request_counter: &mut i32,
+ ) -> Result<Community, LemmyError> {
+ get_object_from_apub(group, context, expected_domain, request_counter).await
+ }
+ }
+
+ #[async_trait::async_trait(?Send)]
+ impl FromApubToForm<GroupExt> for CommunityForm {
async fn from_apub(
group: &GroupExt,
context: &LemmyContext,
- expected_domain: Option<Url>,
+ expected_domain: Url,
request_counter: &mut i32,
) -> Result<Self, LemmyError> {
let creator_and_moderator_uris = group.inner.attributed_to().context(location_info!())?;
use crate::{
naive_now,
schema::{comment, comment_like, comment_saved},
+ ApubObject,
Crud,
Likeable,
Saveable,
}
}
+ impl ApubObject<CommentForm> for Comment {
+ fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
+ use crate::schema::comment::dsl::*;
+ comment.filter(ap_id.eq(object_id)).first::<Self>(conn)
+ }
+
+ fn upsert(conn: &PgConnection, comment_form: &CommentForm) -> Result<Self, Error> {
+ use crate::schema::comment::dsl::*;
+ insert_into(comment)
+ .values(comment_form)
+ .on_conflict(ap_id)
+ .do_update()
+ .set(comment_form)
+ .get_result::<Self>(conn)
+ }
+ }
+
impl Comment {
pub fn update_ap_id(
conn: &PgConnection,
.get_result::<Self>(conn)
}
- pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
- use crate::schema::comment::dsl::*;
- comment.filter(ap_id.eq(object_id)).first::<Self>(conn)
- }
-
pub fn permadelete_for_creator(
conn: &PgConnection,
for_creator_id: i32,
.set((content.eq(new_content), updated.eq(naive_now())))
.get_result::<Self>(conn)
}
-
- pub fn upsert(conn: &PgConnection, comment_form: &CommentForm) -> Result<Self, Error> {
- use crate::schema::comment::dsl::*;
- insert_into(comment)
- .values(comment_form)
- .on_conflict(ap_id)
- .do_update()
- .set(comment_form)
- .get_result::<Self>(conn)
- }
}
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug, Clone)]
pub id: i32,
pub user_id: i32,
pub comment_id: i32,
- pub post_id: i32,
+ pub post_id: i32, // TODO this is redundant
pub score: i16,
pub published: chrono::NaiveDateTime,
}
pub struct CommentLikeForm {
pub user_id: i32,
pub comment_id: i32,
- pub post_id: i32,
+ pub post_id: i32, // TODO this is redundant
pub score: i16,
}
use crate::{
naive_now,
schema::{community, community_follower, community_moderator, community_user_ban},
+ ApubObject,
Bannable,
Crud,
Followable,
Joinable,
};
use diesel::{dsl::*, result::Error, *};
+use serde::Serialize;
-#[derive(Clone, Queryable, Identifiable, PartialEq, Debug)]
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
#[table_name = "community"]
pub struct Community {
pub id: i32,
pub banner: Option<String>,
}
+/// A safe representation of community, without the sensitive info
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
+#[table_name = "community"]
+pub struct CommunitySafe {
+ pub id: i32,
+ pub name: String,
+ pub title: String,
+ pub description: Option<String>,
+ pub category_id: i32,
+ pub creator_id: i32,
+ pub removed: bool,
+ pub published: chrono::NaiveDateTime,
+ pub updated: Option<chrono::NaiveDateTime>,
+ pub deleted: bool,
+ pub nsfw: bool,
+ pub actor_id: String,
+ pub local: bool,
+ pub icon: Option<String>,
+ pub banner: Option<String>,
+}
+
+mod safe_type {
+ use crate::{community::Community, schema::community::columns::*, ToSafe};
+ type Columns = (
+ id,
+ name,
+ title,
+ description,
+ category_id,
+ creator_id,
+ removed,
+ published,
+ updated,
+ deleted,
+ nsfw,
+ actor_id,
+ local,
+ icon,
+ banner,
+ );
+
+ impl ToSafe for Community {
+ type SafeColumns = Columns;
+ fn safe_columns_tuple() -> Self::SafeColumns {
+ (
+ id,
+ name,
+ title,
+ description,
+ category_id,
+ creator_id,
+ removed,
+ published,
+ updated,
+ deleted,
+ nsfw,
+ actor_id,
+ local,
+ icon,
+ banner,
+ )
+ }
+ }
+}
+
#[derive(Insertable, AsChangeset, Debug)]
#[table_name = "community"]
pub struct CommunityForm {
}
}
- impl Community {
- pub fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Self, Error> {
+ impl ApubObject<CommunityForm> for Community {
+ fn read_from_apub_id(conn: &PgConnection, for_actor_id: &str) -> Result<Self, Error> {
use crate::schema::community::dsl::*;
community
- .filter(local.eq(true))
- .filter(name.eq(community_name))
+ .filter(actor_id.eq(for_actor_id))
.first::<Self>(conn)
}
- pub fn read_from_actor_id(conn: &PgConnection, for_actor_id: &str) -> Result<Self, Error> {
+ fn upsert(conn: &PgConnection, community_form: &CommunityForm) -> Result<Community, Error> {
+ use crate::schema::community::dsl::*;
+ insert_into(community)
+ .values(community_form)
+ .on_conflict(actor_id)
+ .do_update()
+ .set(community_form)
+ .get_result::<Self>(conn)
+ }
+ }
+
+ impl Community {
+ pub fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Self, Error> {
use crate::schema::community::dsl::*;
community
- .filter(actor_id.eq(for_actor_id))
+ .filter(local.eq(true))
+ .filter(name.eq(community_name))
.first::<Self>(conn)
}
}
fn community_mods_and_admins(conn: &PgConnection, community_id: i32) -> Result<Vec<i32>, Error> {
- use crate::{community_view::CommunityModeratorView, user_view::UserView};
+ use crate::views::{community_moderator_view::CommunityModeratorView, user_view::UserViewSafe};
let mut mods_and_admins: Vec<i32> = Vec::new();
mods_and_admins.append(
&mut CommunityModeratorView::for_community(conn, community_id)
- .map(|v| v.into_iter().map(|m| m.user_id).collect())?,
+ .map(|v| v.into_iter().map(|m| m.moderator.id).collect())?,
);
mods_and_admins
- .append(&mut UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect())?);
+ .append(&mut UserViewSafe::admins(conn).map(|v| v.into_iter().map(|a| a.user.id).collect())?);
Ok(mods_and_admins)
}
.unwrap_or_default()
.contains(&user_id)
}
-
- pub fn upsert(conn: &PgConnection, community_form: &CommunityForm) -> Result<Community, Error> {
- use crate::schema::community::dsl::*;
- insert_into(community)
- .values(community_form)
- .on_conflict(actor_id)
- .do_update()
- .set(community_form)
- .get_result::<Self>(conn)
- }
}
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
use std::{env, env::VarError};
pub mod activity;
+pub mod aggregates;
pub mod category;
pub mod comment;
pub mod comment_report;
pub mod comment_view;
pub mod community;
-pub mod community_view;
pub mod moderator;
pub mod moderator_views;
pub mod password_reset_request;
pub mod private_message_view;
pub mod schema;
pub mod site;
-pub mod site_view;
pub mod user;
pub mod user_mention;
pub mod user_mention_view;
-pub mod user_view;
+pub mod views;
pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
Self: Sized;
}
+ pub trait ApubObject<T> {
+ fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error>
+ where
+ Self: Sized;
+ fn upsert(conn: &PgConnection, user_form: &T) -> Result<Self, Error>
+ where
+ Self: Sized;
+ }
+
pub trait MaybeOptional<T> {
fn get_optional(self) -> Option<T>;
}
}
}
+pub(crate) trait ToSafe {
+ type SafeColumns;
+ fn safe_columns_tuple() -> Self::SafeColumns;
+}
+
pub fn get_database_url_from_env() -> Result<String, VarError> {
env::var("LEMMY_DATABASE_URL")
}
Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
}
+pub(crate) mod functions {
+ use diesel::sql_types::*;
+
+ sql_function! {
+ fn hot_rank(score: BigInt, time: Timestamp) -> Integer;
+ }
+}
+
#[cfg(test)]
mod tests {
use super::fuzzy_search;
is_email_regex,
naive_now,
schema::{user_, user_::dsl::*},
+ ApubObject,
Crud,
};
use bcrypt::{hash, DEFAULT_COST};
pub deleted: bool,
}
+/// A safe representation of user, without the sensitive info
+#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
+#[table_name = "user_"]
+pub struct UserSafe {
+ pub id: i32,
+ pub name: String,
+ pub preferred_username: Option<String>,
+ pub avatar: Option<String>,
+ pub admin: bool,
+ pub banned: bool,
+ pub published: chrono::NaiveDateTime,
+ pub updated: Option<chrono::NaiveDateTime>,
+ pub matrix_user_id: Option<String>,
+ pub actor_id: String,
+ pub bio: Option<String>,
+ pub local: bool,
+ pub banner: Option<String>,
+ pub deleted: bool,
+}
+
+mod safe_type {
+ use crate::{schema::user_::columns::*, user::User_, ToSafe};
+ type Columns = (
+ id,
+ name,
+ preferred_username,
+ avatar,
+ admin,
+ banned,
+ published,
+ updated,
+ matrix_user_id,
+ actor_id,
+ bio,
+ local,
+ banner,
+ deleted,
+ );
+
+ impl ToSafe for User_ {
+ type SafeColumns = Columns;
+ fn safe_columns_tuple() -> Self::SafeColumns {
+ (
+ id,
+ name,
+ preferred_username,
+ avatar,
+ admin,
+ banned,
+ published,
+ updated,
+ matrix_user_id,
+ actor_id,
+ bio,
+ local,
+ banner,
+ deleted,
+ )
+ }
+ }
+}
+
#[derive(Insertable, AsChangeset, Clone)]
#[table_name = "user_"]
pub struct UserForm {
}
}
+ impl ApubObject<UserForm> for User_ {
+ fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
+ use crate::schema::user_::dsl::*;
+ user_
+ .filter(deleted.eq(false))
+ .filter(actor_id.eq(object_id))
+ .first::<Self>(conn)
+ }
+
+ fn upsert(conn: &PgConnection, user_form: &UserForm) -> Result<User_, Error> {
+ insert_into(user_)
+ .values(user_form)
+ .on_conflict(actor_id)
+ .do_update()
+ .set(user_form)
+ .get_result::<Self>(conn)
+ }
+ }
+
impl User_ {
pub fn register(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
let mut edited_user = form.clone();
.get_result::<Self>(conn)
}
- pub fn read_from_actor_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
- use crate::schema::user_::dsl::*;
- user_
- .filter(deleted.eq(false))
- .filter(actor_id.eq(object_id))
- .first::<Self>(conn)
- }
-
pub fn find_by_email_or_username(
conn: &PgConnection,
username_or_email: &str,
)
}
- pub fn upsert(conn: &PgConnection, user_form: &UserForm) -> Result<User_, Error> {
- insert_into(user_)
- .values(user_form)
- .on_conflict(actor_id)
- .do_update()
- .set(user_form)
+ pub fn mark_as_updated(conn: &PgConnection, user_id: i32) -> Result<User_, Error> {
+ diesel::update(user_.find(user_id))
+ .set((last_refreshed_at.eq(naive_now()),))
.get_result::<Self>(conn)
}
private_key: None,
public_key: None,
last_refreshed_at: inserted_user.published,
+ deleted: false,
};
let read_user = User_::read(&conn, inserted_user.id).unwrap();