]> Untitled Git - lemmy.git/commitdiff
Merge branch 'main' into move_views_to_diesel
authorDessalines <tyhou13@gmx.com>
Tue, 8 Dec 2020 18:17:55 +0000 (13:17 -0500)
committerDessalines <tyhou13@gmx.com>
Tue, 8 Dec 2020 18:17:55 +0000 (13:17 -0500)
15 files changed:
1  2 
docs/src/about/goals.md
lemmy_api/src/community.rs
lemmy_apub/src/activities/receive/community.rs
lemmy_apub/src/activities/receive/private_message.rs
lemmy_apub/src/fetcher.rs
lemmy_apub/src/http/community.rs
lemmy_apub/src/inbox/community_inbox.rs
lemmy_apub/src/inbox/mod.rs
lemmy_apub/src/inbox/shared_inbox.rs
lemmy_apub/src/inbox/user_inbox.rs
lemmy_apub/src/objects/community.rs
lemmy_db/src/comment.rs
lemmy_db/src/community.rs
lemmy_db/src/lib.rs
lemmy_db/src/user.rs

diff --combined docs/src/about/goals.md
index ea86db0756496b8d57577c2590cbe7591b80ce7e,e7d8e8dc6b2a72f51a8c737481c9dfe9a3305e91..0ce019fcfd69938032207012a06eba766cca0b74
  - [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)
index dcd9be0585d9f46835477e953e71fce249648b11,d7de0e6bd717a3bab6ad626599c846c08e5dfe26..fe7748f945f9fcdafe750d51cc517dd9bb21daa9
@@@ -13,17 -13,14 +13,18 @@@ use lemmy_db::
    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,
@@@ -99,7 -96,7 +100,7 @@@ impl Perform for GetCommunity 
        .unwrap_or(1);
  
      let res = GetCommunityResponse {
 -      community: community_view,
 +      community_view,
        moderators,
        online,
      };
@@@ -133,7 -130,7 +134,7 @@@ impl Perform for CreateCommunity 
      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 })
    }
  }
  
@@@ -229,7 -228,7 +230,7 @@@ impl Perform for EditCommunity 
      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);
  
@@@ -340,7 -341,9 +341,7 @@@ impl Perform for DeleteCommunity 
      })
      .await??;
  
 -    let res = CommunityResponse {
 -      community: community_view,
 -    };
 +    let res = CommunityResponse { community_view };
  
      send_community_websocket(&res, context, websocket_id, UserOperation::DeleteCommunity);
  
@@@ -406,7 -409,9 +407,7 @@@ impl Perform for RemoveCommunity 
      })
      .await??;
  
 -    let res = CommunityResponse {
 -      community: community_view,
 -    };
 +    let res = CommunityResponse { community_view };
  
      send_community_websocket(&res, context, websocket_id, UserOperation::RemoveCommunity);
  
@@@ -441,8 -446,9 +442,8 @@@ impl Perform for ListCommunities 
      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)
@@@ -514,10 -520,12 +515,10 @@@ impl Perform for FollowCommunity 
      // 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 })
    }
  }
  
@@@ -633,12 -641,12 +634,12 @@@ impl Perform for BanFromCommunity 
  
      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,
      };
  
@@@ -741,19 -749,17 +742,19 @@@ impl Perform for TransferCommunity 
      })
      .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,
      })
@@@ -844,16 -850,15 +845,16 @@@ fn send_community_websocket
    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,
    });
  }
index 80c911b142eeacb26d6b419d1d7795b5b3572c25,b1866283b64a538558f51ab83deb08479378740f..16b0c4e3cfb0fecdb525bbe645d9d2444d08eba4
@@@ -4,7 -4,7 +4,7 @@@ use activitystreams::
    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};
@@@ -21,13 -21,13 +21,13 @@@ pub(crate) async fn receive_delete_comm
  
    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,
@@@ -53,7 -53,7 +53,7 @@@ pub(crate) async fn receive_remove_comm
      .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,
@@@ -100,13 -100,13 +100,13 @@@ pub(crate) async fn receive_undo_delete
  
    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,
@@@ -135,7 -135,7 +135,7 @@@ pub(crate) async fn receive_undo_remove
      .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,
index 07913226cfb6512d0a7b206ede53178598dac637,25ccc520d1eecfe674d7cf34d141b48630d822eb..f05b523799904073650702a1fe7e7029341d43e0
@@@ -3,7 -3,7 +3,7 @@@ use crate::
    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};
@@@ -41,15 -37,10 +37,10 @@@ pub(crate) async fn receive_create_priv
    .context(location_info!())?;
  
    let private_message =
-     PrivateMessageForm::from_apub(&note, 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(&note, 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??;
  
@@@ -82,24 -73,8 +73,8 @@@ pub(crate) async fn receive_update_priv
      .to_owned();
    let note = NoteExt::from_any_base(object)?.context(location_info!())?;
  
-   let private_message_form =
-     PrivateMessageForm::from_apub(&note, 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(&note, context, expected_domain, request_counter).await?;
  
    let private_message_id = private_message.id;
    let message = blocking(&context.pool(), move |conn| {
@@@ -194,7 -169,7 +169,7 @@@ async fn check_private_message_activity
  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());
    }
index 0eb33cb753daab8630fb033905619e21bb164866,ad977f5b1b3a371ed9fb1d6aa6bffcf3eead9360..7556580e60d09b5a19522f619802d900e8e71ec2
@@@ -1,7 -1,7 +1,7 @@@
  use crate::{
    check_is_apub_id_valid,
+   objects::FromApub,
    ActorType,
-   FromApub,
    GroupExt,
    NoteExt,
    PageExt,
@@@ -13,15 -13,16 +13,15 @@@ use anyhow::{anyhow, Context}
  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,
  };
@@@ -160,7 -161,7 +160,7 @@@ pub async fn search_by_apub_id
  
        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)
@@@ -242,7 -237,7 +236,7 @@@ pub(crate) async fn get_or_fetch_and_up
  ) -> 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)
      }
@@@ -317,7 -303,7 +302,7 @@@ pub(crate) async fn get_or_fetch_and_up
  ) -> 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?;
  
@@@ -353,9 -339,8 +338,8 @@@ async fn fetch_remote_community
    }
  
    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
    }
  
@@@ -447,17 -422,9 +421,9 @@@ pub(crate) async fn get_or_fetch_and_in
      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)
      }
@@@ -489,25 -456,20 +455,20 @@@ pub(crate) async fn get_or_fetch_and_in
        );
        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()),
index 113228595a54e77c7973081fe60c99e74625fd0d,0a90571ff0287bfbb00b8eceb5cd2e60875b2f15..d2a18ee0b3c2a070e9e85cdbb84bcb4baf461e73
@@@ -1,19 -1,15 +1,19 @@@
  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;
index 37ae444e912658851011e20fb19e8cd1c110fa66,4bdad2fadcc433dd273459be7b63e69cfc404e7f..1c7b32e908e6c7aa2026afbf4d887b9816287e01
@@@ -28,8 -28,9 +28,9 @@@ use actix_web::{web, HttpRequest, HttpR
  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,
  };
@@@ -81,7 -82,7 +82,7 @@@ pub async fn community_inbox
      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());
    }
@@@ -118,7 -119,7 +119,7 @@@ pub(crate) async fn community_receive_m
    // 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?;
@@@ -242,7 -243,7 +243,7 @@@ async fn handle_undo_follow
    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,
index f8dd8bfebe7057b2be01f2d9a26a6ec6da337e2b,e04fdd0ffd032e3ffa429e6392b101a7ad3587a7..d36a34c9b0f718ba4a8584d863880da9aefa47c3
@@@ -12,7 -12,7 +12,7 @@@ use activitystreams::
  };
  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;
@@@ -50,7 -50,7 +50,7 @@@ pub(crate) async fn is_activity_already
    }
  }
  
 -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 {
@@@ -119,7 -119,7 +119,7 @@@ pub(crate) async fn is_addressed_to_loc
  ) -> 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);
@@@ -141,7 -141,7 +141,7 @@@ pub(crate) async fn is_addressed_to_com
      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 {
index e9a81ab363932931d5e7af588539b0ce35f226ad,826038bf09ccbd37ee5f406dd1c0acb20acd225c..0108622957136f711b9854d6c8f6f80a5f2bea23
@@@ -15,7 -15,7 +15,7 @@@ use crate::
  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;
@@@ -66,7 -66,7 +66,7 @@@ pub async fn shared_inbox
  
    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.
@@@ -137,10 -137,7 +137,7 @@@ async fn extract_local_community_from_d
  ) -> 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));
index cc1c0661bf511178b5373de0a54d18c21e0e6132,81b9f185378266af85c5fa0c6f4b1027a4c0af28..28f2de60f557953d05c4a71b21b3be28e907e2f1
@@@ -52,6 -52,7 +52,7 @@@ use lemmy_db::
    community::{Community, CommunityFollower},
    private_message::PrivateMessage,
    user::User_,
+   ApubObject,
    Followable,
  };
  use lemmy_structs::blocking;
@@@ -101,7 -102,7 +102,7 @@@ pub async fn user_inbox
      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());
@@@ -172,7 -173,7 +173,7 @@@ async fn is_for_user_inbox
    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(());
@@@ -377,7 -378,7 +378,7 @@@ async fn find_community_or_private_mess
  ) -> 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 {
index 91638ef02e34275e1b21e81f2ac1b12c67b584e6,594a5b5ee03956a1a574be545e8c8f1e88f89c12..8cc5b9eb5cb231574d4e1ca40d13d5e329c8aaeb
@@@ -4,13 -4,15 +4,15 @@@ use crate::
    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},
@@@ -22,8 -24,8 +24,8 @@@ use activitystreams_ext::Ext2
  use anyhow::Context;
  use lemmy_db::{
    community::{Community, CommunityForm},
 -  community_view::CommunityModeratorView,
    naive_now,
 +  views::community_moderator_view::CommunityModeratorView,
    DbPool,
  };
  use lemmy_structs::blocking;
@@@ -49,10 -51,7 +51,10 @@@ impl ToApub for Community 
        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!())?;
diff --combined lemmy_db/src/comment.rs
index c88eb9adb8ff7684b8a1f6a608b04bada30c2630,f54ddd10f203def7fc3820ec4887a1196a379edb..fb327a308dfbe8d63eee316648fa779141f0ddd8
@@@ -2,6 -2,7 +2,7 @@@ use super::post::Post
  use crate::{
    naive_now,
    schema::{comment, comment_like, comment_saved},
+   ApubObject,
    Crud,
    Likeable,
    Saveable,
@@@ -86,6 -87,23 +87,23 @@@ impl Crud<CommentForm> for Comment 
    }
  }
  
+ 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)]
@@@ -187,7 -190,7 +190,7 @@@ pub struct CommentLike 
    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,
  }
  
index 40f046804dac8d36eb0a8b9ca5fb1278617037c8,be40da349246692b0e56714294e2fb9fa7e6e361..eda643ad5ca77c5a58a3162c654610a7b51c1da6
@@@ -1,15 -1,15 +1,16 @@@
  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 {
@@@ -149,19 -84,31 +150,31 @@@ impl Crud<CommunityForm> for Community 
    }
  }
  
- 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)]
diff --combined lemmy_db/src/lib.rs
index b5348a6ca29c9d3aaed30146fc45f4cac4eee3ff,0ca4826a19486ebbb11cd3b9f5be4dfbab632f25..0cf1cd61c6f3dfadd3847287b8f30446f6cb4771
@@@ -12,12 -12,12 +12,12 @@@ use serde::{Deserialize, Serialize}
  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;
@@@ -28,10 -28,11 +28,10 @@@ pub mod private_message
  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>>;
  
@@@ -123,6 -124,15 +123,15 @@@ pub trait Reportable<T> 
      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>;
  }
@@@ -139,11 -149,6 +148,11 @@@ impl<T> MaybeOptional<T> for 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")
  }
@@@ -217,14 -222,6 +226,14 @@@ lazy_static! 
      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;
diff --combined lemmy_db/src/user.rs
index b2cb0e17e7cac2cd760c7aa9d702e5e443381c41,d8e833e6e5bdb2540e7d5d3777c821446edaa10e..41d3ed18b74a01d4f10a2a1013c917848695ed2c
@@@ -2,6 -2,7 +2,7 @@@ use crate::
    is_email_regex,
    naive_now,
    schema::{user_, user_::dsl::*},
+   ApubObject,
    Crud,
  };
  use bcrypt::{hash, DEFAULT_COST};
@@@ -40,68 -41,6 +41,68 @@@ pub struct User_ 
    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 {
@@@ -151,6 -90,25 +152,25 @@@ impl Crud<UserForm> for User_ 
    }
  }
  
+ 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)
    }
  
@@@ -327,7 -274,6 +336,7 @@@ mod tests 
        private_key: None,
        public_key: None,
        last_refreshed_at: inserted_user.published,
 +      deleted: false,
      };
  
      let read_user = User_::read(&conn, inserted_user.id).unwrap();