diff --git a/CHANGELOG.md b/CHANGELOG.md
index 19cd1e539..51e5424c6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -51,6 +51,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Admin API: `POST/DELETE /api/pleroma/admin/users/:nickname/permission_group/:permission_group` are deprecated in favor of: `POST/DELETE /api/pleroma/admin/users/permission_group/:permission_group` (both accept `nicknames` array), `DELETE /api/pleroma/admin/users` (`nickname` query param or `nickname` sent in JSON body) is deprecated in favor of: `DELETE /api/pleroma/admin/users` (`nicknames` query array param or `nicknames` sent in JSON body).
 - Admin API: Add `GET /api/pleroma/admin/relay` endpoint - lists all followed relays
 - Pleroma API: `POST /api/v1/pleroma/conversations/read` to mark all conversations as read
+- Mastodon API: Add `/api/v1/markers` for managing timeline read markers
 
 ### Changed
 - **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
@@ -65,6 +66,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mastodon API: Add `pleroma.direct_conversation_id` to the status endpoint (`GET /api/v1/statuses/:id`)
 - Mastodon API: Mark the direct conversation as read for the author when they send a new direct message
 </details>
+- Deprecated `User.Info` embedded schema (fields moved to `User`)
 
 ### Fixed
 - Report emails now include functional links to profiles of remote user accounts
diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md
index 6adeda07e..e64ae6429 100644
--- a/docs/API/admin_api.md
+++ b/docs/API/admin_api.md
@@ -170,7 +170,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
 - Params: none
 - Response:
   - On failure: `{"error": "…"}`
-  - On success: JSON of the `user.info`
+  - On success: JSON of the user
 
 ## `POST /api/pleroma/admin/users/permission_group/:permission_group`
 
@@ -180,7 +180,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
   - `nicknames`: nicknames array
 - Response:
   - On failure: `{"error": "…"}`
-  - On success: JSON of the `user.info`
+  - On success: JSON of the user
 
 ## DEPRECATED `DELETE /api/pleroma/admin/users/:nickname/permission_group/:permission_group`
 
@@ -189,7 +189,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
 - Params: none
 - Response:
   - On failure: `{"error": "…"}`
-  - On success: JSON of the `user.info`
+  - On success: JSON of the user
 - Note: An admin cannot revoke their own admin status.
 
 ## `DELETE /api/pleroma/admin/users/permission_group/:permission_group`
@@ -200,7 +200,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
   - `nicknames`: nicknames array
 - Response:
   - On failure: `{"error": "…"}`
-  - On success: JSON of the `user.info`
+  - On success: JSON of the user
 - Note: An admin cannot revoke their own admin status.
 
 ## `PATCH /api/pleroma/admin/users/activate`
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index 8866afdf6..4e3b80db3 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -5,6 +5,7 @@
 defmodule Mix.Tasks.Pleroma.User do
   use Mix.Task
   import Mix.Pleroma
+  alias Ecto.Changeset
   alias Pleroma.User
   alias Pleroma.UserInviteToken
   alias Pleroma.Web.OAuth
@@ -109,10 +110,10 @@ defmodule Mix.Tasks.Pleroma.User do
     start_pleroma()
 
     with %User{} = user <- User.get_cached_by_nickname(nickname) do
-      {:ok, user} = User.deactivate(user, !user.info.deactivated)
+      {:ok, user} = User.deactivate(user, !user.deactivated)
 
       shell_info(
-        "Activation status of #{nickname}: #{if(user.info.deactivated, do: "de", else: "")}activated"
+        "Activation status of #{nickname}: #{if(user.deactivated, do: "de", else: "")}activated"
       )
     else
       _ ->
@@ -340,7 +341,7 @@ defmodule Mix.Tasks.Pleroma.User do
     with %User{} = user <- User.get_cached_by_nickname(nickname) do
       {:ok, user} = User.toggle_confirmation(user)
 
-      message = if user.info.confirmation_pending, do: "needs", else: "doesn't need"
+      message = if user.confirmation_pending, do: "needs", else: "doesn't need"
 
       shell_info("#{nickname} #{message} confirmation.")
     else
@@ -364,23 +365,32 @@ defmodule Mix.Tasks.Pleroma.User do
   end
 
   defp set_moderator(user, value) do
-    {:ok, user} = User.update_info(user, &User.Info.admin_api_update(&1, %{is_moderator: value}))
+    {:ok, user} =
+      user
+      |> Changeset.change(%{is_moderator: value})
+      |> User.update_and_set_cache()
 
-    shell_info("Moderator status of #{user.nickname}: #{user.info.is_moderator}")
+    shell_info("Moderator status of #{user.nickname}: #{user.is_moderator}")
     user
   end
 
   defp set_admin(user, value) do
-    {:ok, user} = User.update_info(user, &User.Info.admin_api_update(&1, %{is_admin: value}))
+    {:ok, user} =
+      user
+      |> Changeset.change(%{is_admin: value})
+      |> User.update_and_set_cache()
 
-    shell_info("Admin status of #{user.nickname}: #{user.info.is_admin}")
+    shell_info("Admin status of #{user.nickname}: #{user.is_admin}")
     user
   end
 
   defp set_locked(user, value) do
-    {:ok, user} = User.update_info(user, &User.Info.user_upgrade(&1, %{locked: value}))
+    {:ok, user} =
+      user
+      |> Changeset.change(%{locked: value})
+      |> User.update_and_set_cache()
 
-    shell_info("Locked status of #{user.nickname}: #{user.info.locked}")
+    shell_info("Locked status of #{user.nickname}: #{user.locked}")
     user
   end
 end
diff --git a/lib/pleroma/daemons/digest_email_daemon.ex b/lib/pleroma/daemons/digest_email_daemon.ex
index 462ad2c55..b4c8eaad9 100644
--- a/lib/pleroma/daemons/digest_email_daemon.ex
+++ b/lib/pleroma/daemons/digest_email_daemon.ex
@@ -17,7 +17,7 @@ defmodule Pleroma.Daemons.DigestEmailDaemon do
     now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
 
     from(u in inactive_users_query,
-      where: fragment(~s(? #> '{"email_notifications","digest"}' @> 'true'), u.info),
+      where: fragment(~s(? ->'digest' @> 'true'), u.email_notifications),
       where: u.last_digest_emailed_at < datetime_add(^now, ^negative_interval, "day"),
       select: u
     )
diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex
index 40b67ff56..a10f88f93 100644
--- a/lib/pleroma/emails/user_email.ex
+++ b/lib/pleroma/emails/user_email.ex
@@ -72,7 +72,7 @@ defmodule Pleroma.Emails.UserEmail do
         Endpoint,
         :confirm_email,
         user.id,
-        to_string(user.info.confirmation_token)
+        to_string(user.confirmation_token)
       )
 
     html_body = """
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index 931b9af2b..19b9af46c 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -127,7 +127,7 @@ defmodule Pleroma.Formatter do
     end
   end
 
-  defp get_ap_id(%User{info: %{source_data: %{"url" => url}}}) when is_binary(url), do: url
+  defp get_ap_id(%User{source_data: %{"url" => url}}) when is_binary(url), do: url
   defp get_ap_id(%User{ap_id: ap_id}), do: ap_id
 
   defp get_nickname_text(nickname, %{mentions_format: :full}), do: User.full_nickname(nickname)
diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex
new file mode 100644
index 000000000..7f87c86c3
--- /dev/null
+++ b/lib/pleroma/marker.ex
@@ -0,0 +1,74 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Marker do
+  use Ecto.Schema
+
+  import Ecto.Changeset
+  import Ecto.Query
+
+  alias Ecto.Multi
+  alias Pleroma.Repo
+  alias Pleroma.User
+
+  @timelines ["notifications"]
+
+  schema "markers" do
+    field(:last_read_id, :string, default: "")
+    field(:timeline, :string, default: "")
+    field(:lock_version, :integer, default: 0)
+
+    belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
+    timestamps()
+  end
+
+  def get_markers(user, timelines \\ []) do
+    Repo.all(get_query(user, timelines))
+  end
+
+  def upsert(%User{} = user, attrs) do
+    attrs
+    |> Map.take(@timelines)
+    |> Enum.reduce(Multi.new(), fn {timeline, timeline_attrs}, multi ->
+      marker =
+        user
+        |> get_marker(timeline)
+        |> changeset(timeline_attrs)
+
+      Multi.insert(multi, timeline, marker,
+        returning: true,
+        on_conflict: {:replace, [:last_read_id]},
+        conflict_target: [:user_id, :timeline]
+      )
+    end)
+    |> Repo.transaction()
+  end
+
+  defp get_marker(user, timeline) do
+    case Repo.find_resource(get_query(user, timeline)) do
+      {:ok, marker} -> %__MODULE__{marker | user: user}
+      _ -> %__MODULE__{timeline: timeline, user_id: user.id}
+    end
+  end
+
+  @doc false
+  defp changeset(marker, attrs) do
+    marker
+    |> cast(attrs, [:last_read_id])
+    |> validate_required([:user_id, :timeline, :last_read_id])
+    |> validate_inclusion(:timeline, @timelines)
+  end
+
+  defp by_timeline(query, timeline) do
+    from(m in query, where: m.timeline in ^List.wrap(timeline))
+  end
+
+  defp by_user_id(query, id), do: from(m in query, where: m.user_id == ^id)
+
+  defp get_query(user, timelines) do
+    __MODULE__
+    |> by_user_id(user.id)
+    |> by_timeline(timelines)
+  end
+end
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index d145f8d5b..b7ecf51e4 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -40,7 +40,7 @@ defmodule Pleroma.Notification do
     |> where(
       [n, a],
       fragment(
-        "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
+        "? not in (SELECT ap_id FROM users WHERE deactivated = 'true')",
         a.actor
       )
     )
@@ -55,21 +55,26 @@ defmodule Pleroma.Notification do
     )
     |> preload([n, a, o], activity: {a, object: o})
     |> exclude_muted(user, opts)
+    |> exclude_blocked(user)
     |> exclude_visibility(opts)
   end
 
+  defp exclude_blocked(query, user) do
+    query
+    |> where([n, a], a.actor not in ^user.blocks)
+    |> where(
+      [n, a],
+      fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.domain_blocks
+    )
+  end
+
   defp exclude_muted(query, _, %{with_muted: true}) do
     query
   end
 
   defp exclude_muted(query, user, _opts) do
     query
-    |> where([n, a], a.actor not in ^user.info.muted_notifications)
-    |> where([n, a], a.actor not in ^user.info.blocks)
-    |> where(
-      [n, a],
-      fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks
-    )
+    |> where([n, a], a.actor not in ^user.muted_notifications)
     |> join(:left, [n, a], tm in Pleroma.ThreadMute,
       on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
     )
@@ -309,7 +314,7 @@ defmodule Pleroma.Notification do
   def skip?(
         :followers,
         activity,
-        %{info: %{notification_settings: %{"followers" => false}}} = user
+        %{notification_settings: %{"followers" => false}} = user
       ) do
     actor = activity.data["actor"]
     follower = User.get_cached_by_ap_id(actor)
@@ -319,14 +324,14 @@ defmodule Pleroma.Notification do
   def skip?(
         :non_followers,
         activity,
-        %{info: %{notification_settings: %{"non_followers" => false}}} = user
+        %{notification_settings: %{"non_followers" => false}} = user
       ) do
     actor = activity.data["actor"]
     follower = User.get_cached_by_ap_id(actor)
     !User.following?(follower, user)
   end
 
-  def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
+  def skip?(:follows, activity, %{notification_settings: %{"follows" => false}} = user) do
     actor = activity.data["actor"]
     followed = User.get_cached_by_ap_id(actor)
     User.following?(user, followed)
@@ -335,7 +340,7 @@ defmodule Pleroma.Notification do
   def skip?(
         :non_follows,
         activity,
-        %{info: %{notification_settings: %{"non_follows" => false}}} = user
+        %{notification_settings: %{"non_follows" => false}} = user
       ) do
     actor = activity.data["actor"]
     followed = User.get_cached_by_ap_id(actor)
diff --git a/lib/pleroma/plugs/admin_secret_authentication_plug.ex b/lib/pleroma/plugs/admin_secret_authentication_plug.ex
index 5baf8a691..fdadd476e 100644
--- a/lib/pleroma/plugs/admin_secret_authentication_plug.ex
+++ b/lib/pleroma/plugs/admin_secret_authentication_plug.ex
@@ -19,7 +19,7 @@ defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do
   def call(%{params: %{"admin_token" => admin_token}} = conn, _) do
     if secret_token() && admin_token == secret_token() do
       conn
-      |> assign(:user, %User{info: %{is_admin: true}})
+      |> assign(:user, %User{is_admin: true})
     else
       conn
     end
diff --git a/lib/pleroma/plugs/oauth_plug.ex b/lib/pleroma/plugs/oauth_plug.ex
index 86bc4aa3a..fd004fcd2 100644
--- a/lib/pleroma/plugs/oauth_plug.ex
+++ b/lib/pleroma/plugs/oauth_plug.ex
@@ -71,7 +71,7 @@ defmodule Pleroma.Plugs.OAuthPlug do
       )
 
     # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
-    with %Token{user: %{info: %{deactivated: false} = _} = user} = token_record <- Repo.one(query) do
+    with %Token{user: %{deactivated: false} = user} = token_record <- Repo.one(query) do
       {:ok, user, token_record}
     end
   end
diff --git a/lib/pleroma/plugs/user_enabled_plug.ex b/lib/pleroma/plugs/user_enabled_plug.ex
index da892c28b..fbb4bf115 100644
--- a/lib/pleroma/plugs/user_enabled_plug.ex
+++ b/lib/pleroma/plugs/user_enabled_plug.ex
@@ -10,7 +10,7 @@ defmodule Pleroma.Plugs.UserEnabledPlug do
     options
   end
 
-  def call(%{assigns: %{user: %User{info: %{deactivated: true}}}} = conn, _) do
+  def call(%{assigns: %{user: %User{deactivated: true}}} = conn, _) do
     conn
     |> assign(:user, nil)
   end
diff --git a/lib/pleroma/plugs/user_is_admin_plug.ex b/lib/pleroma/plugs/user_is_admin_plug.ex
index 4c4b3d610..ee808f31f 100644
--- a/lib/pleroma/plugs/user_is_admin_plug.ex
+++ b/lib/pleroma/plugs/user_is_admin_plug.ex
@@ -11,7 +11,7 @@ defmodule Pleroma.Plugs.UserIsAdminPlug do
     options
   end
 
-  def call(%{assigns: %{user: %User{info: %{is_admin: true}}}} = conn, _) do
+  def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _) do
     conn
   end
 
diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex
index df80fbaa4..8154a09b7 100644
--- a/lib/pleroma/stats.ex
+++ b/lib/pleroma/stats.ex
@@ -68,12 +68,7 @@ defmodule Pleroma.Stats do
 
     domain_count = Enum.count(peers)
 
-    status_query =
-      from(u in User.Query.build(%{local: true}),
-        select: fragment("sum((?->>'note_count')::int)", u.info)
-      )
-
-    status_count = Repo.one(status_query)
+    status_count = Repo.aggregate(User.Query.build(%{local: true}), :sum, :note_count)
 
     user_count = Repo.aggregate(User.Query.build(%{local: true, active: true}), :count, :id)
 
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index d3c767f33..03c02a47f 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -61,15 +61,70 @@ defmodule Pleroma.User do
     field(:tags, {:array, :string}, default: [])
     field(:last_refreshed_at, :naive_datetime_usec)
     field(:last_digest_emailed_at, :naive_datetime)
+
+    field(:banner, :map, default: %{})
+    field(:background, :map, default: %{})
+    field(:source_data, :map, default: %{})
+    field(:note_count, :integer, default: 0)
+    field(:follower_count, :integer, default: 0)
+    # Should be filled in only for remote users
+    field(:following_count, :integer, default: nil)
+    field(:locked, :boolean, default: false)
+    field(:confirmation_pending, :boolean, default: false)
+    field(:password_reset_pending, :boolean, default: false)
+    field(:confirmation_token, :string, default: nil)
+    field(:default_scope, :string, default: "public")
+    field(:blocks, {:array, :string}, default: [])
+    field(:domain_blocks, {:array, :string}, default: [])
+    field(:mutes, {:array, :string}, default: [])
+    field(:muted_reblogs, {:array, :string}, default: [])
+    field(:muted_notifications, {:array, :string}, default: [])
+    field(:subscribers, {:array, :string}, default: [])
+    field(:deactivated, :boolean, default: false)
+    field(:no_rich_text, :boolean, default: false)
+    field(:ap_enabled, :boolean, default: false)
+    field(:is_moderator, :boolean, default: false)
+    field(:is_admin, :boolean, default: false)
+    field(:show_role, :boolean, default: true)
+    field(:settings, :map, default: nil)
+    field(:magic_key, :string, default: nil)
+    field(:uri, :string, default: nil)
+    field(:hide_followers_count, :boolean, default: false)
+    field(:hide_follows_count, :boolean, default: false)
+    field(:hide_followers, :boolean, default: false)
+    field(:hide_follows, :boolean, default: false)
+    field(:hide_favorites, :boolean, default: true)
+    field(:unread_conversation_count, :integer, default: 0)
+    field(:pinned_activities, {:array, :string}, default: [])
+    field(:email_notifications, :map, default: %{"digest" => false})
+    field(:mascot, :map, default: nil)
+    field(:emoji, {:array, :map}, default: [])
+    field(:pleroma_settings_store, :map, default: %{})
+    field(:fields, {:array, :map}, default: [])
+    field(:raw_fields, {:array, :map}, default: [])
+    field(:discoverable, :boolean, default: false)
+    field(:invisible, :boolean, default: false)
+    field(:skip_thread_containment, :boolean, default: false)
+
+    field(:notification_settings, :map,
+      default: %{
+        "followers" => true,
+        "follows" => true,
+        "non_follows" => true,
+        "non_followers" => true
+      }
+    )
+
     has_many(:notifications, Notification)
     has_many(:registrations, Registration)
     has_many(:deliveries, Delivery)
-    embeds_one(:info, User.Info)
+
+    field(:info, :map, default: %{})
 
     timestamps()
   end
 
-  def auth_active?(%User{info: %User.Info{confirmation_pending: true}}),
+  def auth_active?(%User{confirmation_pending: true}),
     do: !Pleroma.Config.get([:instance, :account_activation_required])
 
   def auth_active?(%User{}), do: true
@@ -84,10 +139,13 @@ defmodule Pleroma.User do
 
   def visible_for?(_, _), do: false
 
-  def superuser?(%User{local: true, info: %User.Info{is_admin: true}}), do: true
-  def superuser?(%User{local: true, info: %User.Info{is_moderator: true}}), do: true
+  def superuser?(%User{local: true, is_admin: true}), do: true
+  def superuser?(%User{local: true, is_moderator: true}), do: true
   def superuser?(_), do: false
 
+  def invisible?(%User{invisible: true}), do: true
+  def invisible?(_), do: false
+
   def avatar_url(user, options \\ []) do
     case user.avatar do
       %{"url" => [%{"href" => href} | _]} -> href
@@ -96,13 +154,13 @@ defmodule Pleroma.User do
   end
 
   def banner_url(user, options \\ []) do
-    case user.info.banner do
+    case user.banner do
       %{"url" => [%{"href" => href} | _]} -> href
       _ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
     end
   end
 
-  def profile_url(%User{info: %{source_data: %{"url" => url}}}), do: url
+  def profile_url(%User{source_data: %{"url" => url}}), do: url
   def profile_url(%User{ap_id: ap_id}), do: ap_id
   def profile_url(_), do: nil
 
@@ -117,15 +175,15 @@ defmodule Pleroma.User do
 
   def user_info(%User{} = user, args \\ %{}) do
     following_count =
-      Map.get(args, :following_count, user.info.following_count || following_count(user))
+      Map.get(args, :following_count, user.following_count || following_count(user))
 
-    follower_count = Map.get(args, :follower_count, user.info.follower_count)
+    follower_count = Map.get(args, :follower_count, user.follower_count)
 
     %{
-      note_count: user.info.note_count,
-      locked: user.info.locked,
-      confirmation_pending: user.info.confirmation_pending,
-      default_scope: user.info.default_scope
+      note_count: user.note_count,
+      locked: user.locked,
+      confirmation_pending: user.confirmation_pending,
+      default_scope: user.default_scope
     }
     |> Map.put(:following_count, following_count)
     |> Map.put(:follower_count, follower_count)
@@ -155,13 +213,67 @@ defmodule Pleroma.User do
 
   @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
   def restrict_deactivated(query) do
-    from(u in query,
-      where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info)
-    )
+    from(u in query, where: u.deactivated != ^true)
   end
 
   defdelegate following_count(user), to: FollowingRelationship
 
+  @info_fields [
+    :banner,
+    :background,
+    :source_data,
+    :note_count,
+    :follower_count,
+    :following_count,
+    :locked,
+    :confirmation_pending,
+    :password_reset_pending,
+    :confirmation_token,
+    :default_scope,
+    :blocks,
+    :domain_blocks,
+    :mutes,
+    :muted_reblogs,
+    :muted_notifications,
+    :subscribers,
+    :deactivated,
+    :no_rich_text,
+    :ap_enabled,
+    :is_moderator,
+    :is_admin,
+    :show_role,
+    :settings,
+    :magic_key,
+    :uri,
+    :hide_followers_count,
+    :hide_follows_count,
+    :hide_followers,
+    :hide_follows,
+    :hide_favorites,
+    :unread_conversation_count,
+    :pinned_activities,
+    :email_notifications,
+    :mascot,
+    :emoji,
+    :pleroma_settings_store,
+    :fields,
+    :raw_fields,
+    :discoverable,
+    :invisible,
+    :skip_thread_containment,
+    :notification_settings
+  ]
+
+  def info_fields, do: @info_fields
+
+  defp truncate_fields_param(params) do
+    if Map.has_key?(params, :fields) do
+      Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
+    else
+      params
+    end
+  end
+
   defp truncate_if_exists(params, key, max_length) do
     if Map.has_key?(params, key) and is_binary(params[key]) do
       {value, _chopped} = String.split_at(params[key], max_length)
@@ -180,18 +292,43 @@ defmodule Pleroma.User do
       |> Map.put(:info, params[:info] || %{})
       |> truncate_if_exists(:name, name_limit)
       |> truncate_if_exists(:bio, bio_limit)
+      |> truncate_fields_param()
 
     changeset =
       %User{local: false}
-      |> cast(params, [:bio, :name, :ap_id, :nickname, :avatar])
+      |> cast(
+        params,
+        [
+          :bio,
+          :name,
+          :ap_id,
+          :nickname,
+          :avatar,
+          :ap_enabled,
+          :source_data,
+          :banner,
+          :locked,
+          :magic_key,
+          :uri,
+          :hide_followers,
+          :hide_follows,
+          :hide_followers_count,
+          :hide_follows_count,
+          :follower_count,
+          :fields,
+          :following_count,
+          :discoverable,
+          :invisible
+        ]
+      )
       |> validate_required([:name, :ap_id])
       |> unique_constraint(:nickname)
       |> validate_format(:nickname, @email_regex)
       |> validate_length(:bio, max: bio_limit)
       |> validate_length(:name, max: name_limit)
-      |> change_info(&User.Info.remote_user_creation(&1, params[:info]))
+      |> validate_fields(true)
 
-    case params[:info][:source_data] do
+    case params[:source_data] do
       %{"followers" => followers, "following" => following} ->
         changeset
         |> put_change(:follower_address, followers)
@@ -208,11 +345,35 @@ defmodule Pleroma.User do
     name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
 
     struct
-    |> cast(params, [:bio, :name, :avatar])
+    |> cast(
+      params,
+      [
+        :bio,
+        :name,
+        :avatar,
+        :locked,
+        :no_rich_text,
+        :default_scope,
+        :banner,
+        :hide_follows,
+        :hide_followers,
+        :hide_followers_count,
+        :hide_follows_count,
+        :hide_favorites,
+        :background,
+        :show_role,
+        :skip_thread_containment,
+        :fields,
+        :raw_fields,
+        :pleroma_settings_store,
+        :discoverable
+      ]
+    )
     |> unique_constraint(:nickname)
     |> validate_format(:nickname, local_nickname_regex())
     |> validate_length(:bio, max: bio_limit)
     |> validate_length(:name, min: 1, max: name_limit)
+    |> validate_fields(false)
   end
 
   def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
@@ -221,20 +382,38 @@ defmodule Pleroma.User do
 
     params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
 
+    params = if remote?, do: truncate_fields_param(params), else: params
+
     struct
-    |> cast(params, [
-      :bio,
-      :name,
-      :follower_address,
-      :following_address,
-      :avatar,
-      :last_refreshed_at
-    ])
+    |> cast(
+      params,
+      [
+        :bio,
+        :name,
+        :follower_address,
+        :following_address,
+        :avatar,
+        :last_refreshed_at,
+        :ap_enabled,
+        :source_data,
+        :banner,
+        :locked,
+        :magic_key,
+        :follower_count,
+        :following_count,
+        :hide_follows,
+        :fields,
+        :hide_followers,
+        :discoverable,
+        :hide_followers_count,
+        :hide_follows_count
+      ]
+    )
     |> unique_constraint(:nickname)
     |> validate_format(:nickname, local_nickname_regex())
     |> validate_length(:bio, max: bio_limit)
     |> validate_length(:name, max: name_limit)
-    |> change_info(&User.Info.user_upgrade(&1, params[:info], remote?))
+    |> validate_fields(remote?)
   end
 
   def password_update_changeset(struct, params) do
@@ -242,8 +421,8 @@ defmodule Pleroma.User do
     |> cast(params, [:password, :password_confirmation])
     |> validate_required([:password, :password_confirmation])
     |> validate_confirmation(:password)
-    |> put_password_hash
-    |> put_embed(:info, User.Info.set_password_reset_pending(struct.info, false))
+    |> put_password_hash()
+    |> put_change(:password_reset_pending, false)
   end
 
   @spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
@@ -260,19 +439,19 @@ defmodule Pleroma.User do
     end
   end
 
+  def update_password_reset_pending(user, value) do
+    user
+    |> change()
+    |> put_change(:password_reset_pending, value)
+    |> update_and_set_cache()
+  end
+
   def force_password_reset_async(user) do
     BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
   end
 
   @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
-  def force_password_reset(user) do
-    info_cng = User.Info.set_password_reset_pending(user.info, true)
-
-    user
-    |> change()
-    |> put_embed(:info, info_cng)
-    |> update_and_set_cache()
-  end
+  def force_password_reset(user), do: update_password_reset_pending(user, true)
 
   def register_changeset(struct, params \\ %{}, opts \\ []) do
     bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
@@ -286,6 +465,7 @@ defmodule Pleroma.User do
       end
 
     struct
+    |> confirmation_changeset(need_confirmation: need_confirmation?)
     |> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
     |> validate_required([:name, :nickname, :password, :password_confirmation])
     |> validate_confirmation(:password)
@@ -296,7 +476,6 @@ defmodule Pleroma.User do
     |> validate_format(:email, @email_regex)
     |> validate_length(:bio, max: bio_limit)
     |> validate_length(:name, min: 1, max: name_limit)
-    |> change_info(&User.Info.confirmation_changeset(&1, need_confirmation: need_confirmation?))
     |> maybe_validate_required_email(opts[:external])
     |> put_password_hash
     |> put_ap_id()
@@ -346,7 +525,7 @@ defmodule Pleroma.User do
   end
 
   def try_send_confirmation_email(%User{} = user) do
-    if user.info.confirmation_pending &&
+    if user.confirmation_pending &&
          Pleroma.Config.get([:instance, :account_activation_required]) do
       user
       |> Pleroma.Emails.UserEmail.account_confirmation_email()
@@ -369,10 +548,7 @@ defmodule Pleroma.User do
   def needs_update?(_), do: true
 
   @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
-  def maybe_direct_follow(
-        %User{} = follower,
-        %User{local: true, info: %{locked: true}} = followed
-      ) do
+  def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
     follow(follower, followed, "pending")
   end
 
@@ -405,12 +581,12 @@ defmodule Pleroma.User do
 
   defdelegate following(user), to: FollowingRelationship
 
-  def follow(%User{} = follower, %User{info: info} = followed, state \\ "accept") do
+  def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
     deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
 
     cond do
-      info.deactivated ->
-        {:error, "Could not follow user: You are deactivated."}
+      followed.deactivated ->
+        {:error, "Could not follow user: #{followed.nickname} is deactivated."}
 
       deny_follow_blocked and blocks?(followed, follower) ->
         {:error, "Could not follow user: #{followed.nickname} blocked you."}
@@ -445,7 +621,7 @@ defmodule Pleroma.User do
   defdelegate following?(follower, followed), to: FollowingRelationship
 
   def locked?(%User{} = user) do
-    user.info.locked || false
+    user.locked || false
   end
 
   def get_by_id(id) do
@@ -488,6 +664,12 @@ defmodule Pleroma.User do
     {:ok, user}
   end
 
+  def update_and_set_cache(struct, params) do
+    struct
+    |> update_changeset(params)
+    |> update_and_set_cache()
+  end
+
   def update_and_set_cache(changeset) do
     with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
       set_cache(user)
@@ -663,16 +845,7 @@ defmodule Pleroma.User do
   def increase_note_count(%User{} = user) do
     User
     |> where(id: ^user.id)
-    |> update([u],
-      set: [
-        info:
-          fragment(
-            "safe_jsonb_set(?, '{note_count}', ((?->>'note_count')::int + 1)::varchar::jsonb, true)",
-            u.info,
-            u.info
-          )
-      ]
-    )
+    |> update([u], inc: [note_count: 1])
     |> select([u], u)
     |> Repo.update_all([])
     |> case do
@@ -686,12 +859,7 @@ defmodule Pleroma.User do
     |> where(id: ^user.id)
     |> update([u],
       set: [
-        info:
-          fragment(
-            "safe_jsonb_set(?, '{note_count}', (greatest(0, (?->>'note_count')::int - 1))::varchar::jsonb, true)",
-            u.info,
-            u.info
-          )
+        note_count: fragment("greatest(0, note_count - 1)")
       ]
     )
     |> select([u], u)
@@ -702,28 +870,18 @@ defmodule Pleroma.User do
     end
   end
 
-  def update_note_count(%User{} = user) do
+  def update_note_count(%User{} = user, note_count \\ nil) do
     note_count =
-      from(
-        a in Object,
-        where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
-        select: count(a.id)
-      )
-      |> Repo.one()
-
-    update_info(user, &User.Info.set_note_count(&1, note_count))
-  end
-
-  def update_mascot(user, url) do
-    info_changeset =
-      User.Info.mascot_update(
-        user.info,
-        url
-      )
+      note_count ||
+        from(
+          a in Object,
+          where: fragment("?->>'actor' = ? and ?->>'type' = 'Note'", a.data, ^user.ap_id, a.data),
+          select: count(a.id)
+        )
+        |> Repo.one()
 
     user
-    |> change()
-    |> put_embed(:info, info_changeset)
+    |> cast(%{note_count: note_count}, [:note_count])
     |> update_and_set_cache()
   end
 
@@ -741,10 +899,24 @@ defmodule Pleroma.User do
 
   def fetch_follow_information(user) do
     with {:ok, info} <- ActivityPub.fetch_follow_information_for_user(user) do
-      update_info(user, &User.Info.follow_information_update(&1, info))
+      user
+      |> follow_information_changeset(info)
+      |> update_and_set_cache()
     end
   end
 
+  defp follow_information_changeset(user, params) do
+    user
+    |> cast(params, [
+      :hide_followers,
+      :hide_follows,
+      :follower_count,
+      :following_count,
+      :hide_followers_count,
+      :hide_follows_count
+    ])
+  end
+
   def update_follower_count(%User{} = user) do
     if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
       follower_count_query =
@@ -755,14 +927,7 @@ defmodule Pleroma.User do
       |> where(id: ^user.id)
       |> join(:inner, [u], s in subquery(follower_count_query))
       |> update([u, s],
-        set: [
-          info:
-            fragment(
-              "safe_jsonb_set(?, '{follower_count}', ?::varchar::jsonb, true)",
-              u.info,
-              s.count
-            )
-        ]
+        set: [follower_count: s.count]
       )
       |> select([u], u)
       |> Repo.update_all([])
@@ -792,14 +957,7 @@ defmodule Pleroma.User do
     User
     |> join(:inner, [u], p in subquery(unread_query))
     |> update([u, p],
-      set: [
-        info:
-          fragment(
-            "jsonb_set(?, '{unread_conversation_count}', ?::varchar::jsonb, true)",
-            u.info,
-            p.count
-          )
-      ]
+      set: [unread_conversation_count: p.count]
     )
     |> where([u], u.id == ^user.id)
     |> select([u], u)
@@ -820,14 +978,7 @@ defmodule Pleroma.User do
     User
     |> join(:inner, [u], p in subquery(unread_query))
     |> update([u, p],
-      set: [
-        info:
-          fragment(
-            "jsonb_set(?, '{unread_conversation_count}', (coalesce((?->>'unread_conversation_count')::int, 0) + 1)::varchar::jsonb, true)",
-            u.info,
-            u.info
-          )
-      ]
+      inc: [unread_conversation_count: 1]
     )
     |> where([u], u.id == ^user.id)
     |> where([u, p], p.count == 0)
@@ -858,11 +1009,11 @@ defmodule Pleroma.User do
 
   @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
   def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
-    update_info(muter, &User.Info.add_to_mutes(&1, ap_id, notifications?))
+    add_to_mutes(muter, ap_id, notifications?)
   end
 
   def unmute(muter, %{ap_id: ap_id}) do
-    update_info(muter, &User.Info.remove_from_mutes(&1, ap_id))
+    remove_from_mutes(muter, ap_id)
   end
 
   def subscribe(subscriber, %{ap_id: ap_id}) do
@@ -872,14 +1023,14 @@ defmodule Pleroma.User do
       if blocks?(subscribed, subscriber) and deny_follow_blocked do
         {:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
       else
-        update_info(subscribed, &User.Info.add_to_subscribers(&1, subscriber.ap_id))
+        User.add_to_subscribers(subscribed, subscriber.ap_id)
       end
     end
   end
 
   def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
     with %User{} = user <- get_cached_by_ap_id(ap_id) do
-      update_info(user, &User.Info.remove_from_subscribers(&1, unsubscriber.ap_id))
+      User.remove_from_subscribers(user, unsubscriber.ap_id)
     end
   end
 
@@ -912,7 +1063,7 @@ defmodule Pleroma.User do
 
     {:ok, blocker} = update_follower_count(blocker)
 
-    update_info(blocker, &User.Info.add_to_block(&1, ap_id))
+    add_to_block(blocker, ap_id)
   end
 
   # helper to handle the block given only an actor's AP id
@@ -921,17 +1072,17 @@ defmodule Pleroma.User do
   end
 
   def unblock(blocker, %{ap_id: ap_id}) do
-    update_info(blocker, &User.Info.remove_from_block(&1, ap_id))
+    remove_from_block(blocker, ap_id)
   end
 
   def mutes?(nil, _), do: false
-  def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
+  def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id)
 
   @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
   def muted_notifications?(nil, _), do: false
 
   def muted_notifications?(user, %{ap_id: ap_id}),
-    do: Enum.member?(user.info.muted_notifications, ap_id)
+    do: Enum.member?(user.muted_notifications, ap_id)
 
   def blocks?(%User{} = user, %User{} = target) do
     blocks_ap_id?(user, target) || blocks_domain?(user, target)
@@ -940,13 +1091,13 @@ defmodule Pleroma.User do
   def blocks?(nil, _), do: false
 
   def blocks_ap_id?(%User{} = user, %User{} = target) do
-    Enum.member?(user.info.blocks, target.ap_id)
+    Enum.member?(user.blocks, target.ap_id)
   end
 
   def blocks_ap_id?(_, _), do: false
 
   def blocks_domain?(%User{} = user, %User{} = target) do
-    domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks)
+    domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
     %{host: host} = URI.parse(target.ap_id)
     Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
   end
@@ -955,36 +1106,28 @@ defmodule Pleroma.User do
 
   def subscribed_to?(user, %{ap_id: ap_id}) do
     with %User{} = target <- get_cached_by_ap_id(ap_id) do
-      Enum.member?(target.info.subscribers, user.ap_id)
+      Enum.member?(target.subscribers, user.ap_id)
     end
   end
 
   @spec muted_users(User.t()) :: [User.t()]
   def muted_users(user) do
-    User.Query.build(%{ap_id: user.info.mutes, deactivated: false})
+    User.Query.build(%{ap_id: user.mutes, deactivated: false})
     |> Repo.all()
   end
 
   @spec blocked_users(User.t()) :: [User.t()]
   def blocked_users(user) do
-    User.Query.build(%{ap_id: user.info.blocks, deactivated: false})
+    User.Query.build(%{ap_id: user.blocks, deactivated: false})
     |> Repo.all()
   end
 
   @spec subscribers(User.t()) :: [User.t()]
   def subscribers(user) do
-    User.Query.build(%{ap_id: user.info.subscribers, deactivated: false})
+    User.Query.build(%{ap_id: user.subscribers, deactivated: false})
     |> Repo.all()
   end
 
-  def block_domain(user, domain) do
-    update_info(user, &User.Info.add_to_domain_block(&1, domain))
-  end
-
-  def unblock_domain(user, domain) do
-    update_info(user, &User.Info.remove_from_domain_block(&1, domain))
-  end
-
   def deactivate_async(user, status \\ true) do
     BackgroundWorker.enqueue("deactivate_user", %{"user_id" => user.id, "status" => status})
   end
@@ -998,7 +1141,7 @@ defmodule Pleroma.User do
   end
 
   def deactivate(%User{} = user, status) do
-    with {:ok, user} <- update_info(user, &User.Info.set_activation_status(&1, status)) do
+    with {:ok, user} <- set_activation_status(user, status) do
       Enum.each(get_followers(user), &invalidate_cache/1)
       Enum.each(get_friends(user), &update_follower_count/1)
 
@@ -1006,8 +1149,23 @@ defmodule Pleroma.User do
     end
   end
 
-  def update_notification_settings(%User{} = user, settings \\ %{}) do
-    update_info(user, &User.Info.update_notification_settings(&1, settings))
+  def update_notification_settings(%User{} = user, settings) do
+    settings =
+      settings
+      |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
+      |> Map.new()
+
+    notification_settings =
+      user.notification_settings
+      |> Map.merge(settings)
+      |> Map.take(["followers", "follows", "non_follows", "non_followers"])
+
+    params = %{notification_settings: notification_settings}
+
+    user
+    |> cast(params, [:notification_settings])
+    |> validate_required([:notification_settings])
+    |> update_and_set_cache()
   end
 
   def delete(users) when is_list(users) do
@@ -1049,7 +1207,7 @@ defmodule Pleroma.User do
     pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
 
     # Insert all the posts in reverse order, so they're in the right order on the timeline
-    user.info.source_data["outbox"]
+    user.source_data["outbox"]
     |> Utils.fetch_ordered_collection(pages)
     |> Enum.reverse()
     |> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
@@ -1170,7 +1328,7 @@ defmodule Pleroma.User do
 
   defp delete_activity(_activity), do: "Doing nothing"
 
-  def html_filter_policy(%User{info: %{no_rich_text: true}}) do
+  def html_filter_policy(%User{no_rich_text: true}) do
     Pleroma.HTML.Scrubber.TwitterText
   end
 
@@ -1206,7 +1364,7 @@ defmodule Pleroma.User do
     else
       _ ->
         {:ok, user} =
-          %User{info: %User.Info{}}
+          %User{}
           |> cast(%{}, [:ap_id, :nickname, :local])
           |> put_change(:ap_id, uri)
           |> put_change(:nickname, nickname)
@@ -1219,9 +1377,7 @@ defmodule Pleroma.User do
   end
 
   # AP style
-  def public_key_from_info(%{
-        source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}
-      }) do
+  def public_key(%{source_data: %{"publicKey" => %{"publicKeyPem" => public_key_pem}}}) do
     key =
       public_key_pem
       |> :public_key.pem_decode()
@@ -1231,11 +1387,11 @@ defmodule Pleroma.User do
     {:ok, key}
   end
 
-  def public_key_from_info(_), do: {:error, "not found key"}
+  def public_key(_), do: {:error, "not found key"}
 
   def get_public_key_for_ap_id(ap_id) do
     with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
-         {:ok, public_key} <- public_key_from_info(user.info) do
+         {:ok, public_key} <- public_key(user) do
       {:ok, public_key}
     else
       _ -> :error
@@ -1254,7 +1410,7 @@ defmodule Pleroma.User do
   end
 
   def ap_enabled?(%User{local: true}), do: true
-  def ap_enabled?(%User{info: info}), do: info.ap_enabled
+  def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
   def ap_enabled?(_), do: false
 
   @doc "Gets or fetch a user by uri or nickname."
@@ -1367,7 +1523,6 @@ defmodule Pleroma.User do
     %User{
       name: ap_id,
       ap_id: ap_id,
-      info: %User.Info{},
       nickname: "erroruser@example.com",
       inserted_at: NaiveDateTime.utc_now()
     }
@@ -1380,7 +1535,7 @@ defmodule Pleroma.User do
   end
 
   def showing_reblogs?(%User{} = user, %User{} = target) do
-    target.ap_id not in user.info.muted_reblogs
+    target.ap_id not in user.muted_reblogs
   end
 
   @doc """
@@ -1412,7 +1567,7 @@ defmodule Pleroma.User do
       left_join: a in Pleroma.Activity,
       on: u.ap_id == a.actor,
       where: not is_nil(u.nickname),
-      where: fragment("not (?->'deactivated' @> 'true')", u.info),
+      where: u.deactivated != ^true,
       where: u.id not in ^has_read_notifications,
       group_by: u.id,
       having:
@@ -1426,16 +1581,16 @@ defmodule Pleroma.User do
 
   ## Examples
 
-      iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => false}}}, "digest", true)
-      Pleroma.User{info: %{email_notifications: %{"digest" => true}}}
+      iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => false}}, "digest", true)
+      Pleroma.User{email_notifications: %{"digest" => true}}
 
-      iex> Pleroma.User.switch_email_notifications(Pleroma.User{info: %{email_notifications: %{"digest" => true}}}, "digest", false)
-      Pleroma.User{info: %{email_notifications: %{"digest" => false}}}
+      iex> Pleroma.User.switch_email_notifications(Pleroma.User{email_notifications: %{"digest" => true}}, "digest", false)
+      Pleroma.User{email_notifications: %{"digest" => false}}
   """
   @spec switch_email_notifications(t(), String.t(), boolean()) ::
           {:ok, t()} | {:error, Ecto.Changeset.t()}
   def switch_email_notifications(user, type, status) do
-    update_info(user, &User.Info.update_email_notifications(&1, %{type => status}))
+    User.update_email_notifications(user, %{type => status})
   end
 
   @doc """
@@ -1455,17 +1610,16 @@ defmodule Pleroma.User do
 
   @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
   def toggle_confirmation(%User{} = user) do
-    need_confirmation? = !user.info.confirmation_pending
-
     user
-    |> update_info(&User.Info.confirmation_changeset(&1, need_confirmation: need_confirmation?))
+    |> confirmation_changeset(need_confirmation: !user.confirmation_pending)
+    |> update_and_set_cache()
   end
 
-  def get_mascot(%{info: %{mascot: %{} = mascot}}) when not is_nil(mascot) do
+  def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
     mascot
   end
 
-  def get_mascot(%{info: %{mascot: mascot}}) when is_nil(mascot) do
+  def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
     # use instance-default
     config = Pleroma.Config.get([:assets, :mascots])
     default_mascot = Pleroma.Config.get([:assets, :default_mascot])
@@ -1535,31 +1689,285 @@ defmodule Pleroma.User do
     |> update_and_set_cache()
   end
 
-  @doc """
-  Changes `user.info` and returns the user changeset.
-
-  `fun` is called with the `user.info`.
-  """
-  def change_info(user, fun) do
-    changeset = change(user)
-    info = get_field(changeset, :info) || %User.Info{}
-    put_embed(changeset, :info, fun.(info))
+  # Internal function; public one is `deactivate/2`
+  defp set_activation_status(user, deactivated) do
+    user
+    |> cast(%{deactivated: deactivated}, [:deactivated])
+    |> update_and_set_cache()
   end
 
-  @doc """
-  Updates `user.info` and sets cache.
+  def update_banner(user, banner) do
+    user
+    |> cast(%{banner: banner}, [:banner])
+    |> update_and_set_cache()
+  end
 
-  `fun` is called with the `user.info`.
-  """
-  def update_info(users, fun) when is_list(users) do
-    Repo.transaction(fn ->
-      for user <- users, do: update_info(user, fun)
+  def update_background(user, background) do
+    user
+    |> cast(%{background: background}, [:background])
+    |> update_and_set_cache()
+  end
+
+  def update_source_data(user, source_data) do
+    user
+    |> cast(%{source_data: source_data}, [:source_data])
+    |> update_and_set_cache()
+  end
+
+  def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
+    %{
+      admin: is_admin,
+      moderator: is_moderator
+    }
+  end
+
+  # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
+  # For example: [{"name": "Pronoun", "value": "she/her"}, …]
+  def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
+    limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
+
+    attachment
+    |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
+    |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
+    |> Enum.take(limit)
+  end
+
+  def fields(%{fields: nil}), do: []
+
+  def fields(%{fields: fields}), do: fields
+
+  def validate_fields(changeset, remote? \\ false) do
+    limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
+    limit = Pleroma.Config.get([:instance, limit_name], 0)
+
+    changeset
+    |> validate_length(:fields, max: limit)
+    |> validate_change(:fields, fn :fields, fields ->
+      if Enum.all?(fields, &valid_field?/1) do
+        []
+      else
+        [fields: "invalid"]
+      end
     end)
   end
 
-  def update_info(user, fun) do
+  defp valid_field?(%{"name" => name, "value" => value}) do
+    name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
+    value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
+
+    is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
+      String.length(value) <= value_limit
+  end
+
+  defp valid_field?(_), do: false
+
+  defp truncate_field(%{"name" => name, "value" => value}) do
+    {name, _chopped} =
+      String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
+
+    {value, _chopped} =
+      String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
+
+    %{"name" => name, "value" => value}
+  end
+
+  def admin_api_update(user, params) do
     user
-    |> change_info(fun)
+    |> cast(params, [
+      :is_moderator,
+      :is_admin,
+      :show_role
+    ])
+    |> update_and_set_cache()
+  end
+
+  def mascot_update(user, url) do
+    user
+    |> cast(%{mascot: url}, [:mascot])
+    |> validate_required([:mascot])
+    |> update_and_set_cache()
+  end
+
+  def mastodon_settings_update(user, settings) do
+    user
+    |> cast(%{settings: settings}, [:settings])
+    |> validate_required([:settings])
+    |> update_and_set_cache()
+  end
+
+  @spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
+  def confirmation_changeset(user, need_confirmation: need_confirmation?) do
+    params =
+      if need_confirmation? do
+        %{
+          confirmation_pending: true,
+          confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
+        }
+      else
+        %{
+          confirmation_pending: false,
+          confirmation_token: nil
+        }
+      end
+
+    cast(user, params, [:confirmation_pending, :confirmation_token])
+  end
+
+  def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
+    if id not in user.pinned_activities do
+      max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
+      params = %{pinned_activities: user.pinned_activities ++ [id]}
+
+      user
+      |> cast(params, [:pinned_activities])
+      |> validate_length(:pinned_activities,
+        max: max_pinned_statuses,
+        message: "You have already pinned the maximum number of statuses"
+      )
+    else
+      change(user)
+    end
+    |> update_and_set_cache()
+  end
+
+  def remove_pinnned_activity(user, %Pleroma.Activity{id: id}) do
+    params = %{pinned_activities: List.delete(user.pinned_activities, id)}
+
+    user
+    |> cast(params, [:pinned_activities])
+    |> update_and_set_cache()
+  end
+
+  def update_email_notifications(user, settings) do
+    email_notifications =
+      user.email_notifications
+      |> Map.merge(settings)
+      |> Map.take(["digest"])
+
+    params = %{email_notifications: email_notifications}
+    fields = [:email_notifications]
+
+    user
+    |> cast(params, fields)
+    |> validate_required(fields)
+    |> update_and_set_cache()
+  end
+
+  defp set_subscribers(user, subscribers) do
+    params = %{subscribers: subscribers}
+
+    user
+    |> cast(params, [:subscribers])
+    |> validate_required([:subscribers])
+    |> update_and_set_cache()
+  end
+
+  def add_to_subscribers(user, subscribed) do
+    set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
+  end
+
+  def remove_from_subscribers(user, subscribed) do
+    set_subscribers(user, List.delete(user.subscribers, subscribed))
+  end
+
+  defp set_domain_blocks(user, domain_blocks) do
+    params = %{domain_blocks: domain_blocks}
+
+    user
+    |> cast(params, [:domain_blocks])
+    |> validate_required([:domain_blocks])
+    |> update_and_set_cache()
+  end
+
+  def block_domain(user, domain_blocked) do
+    set_domain_blocks(user, Enum.uniq([domain_blocked | user.domain_blocks]))
+  end
+
+  def unblock_domain(user, domain_blocked) do
+    set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
+  end
+
+  defp set_blocks(user, blocks) do
+    params = %{blocks: blocks}
+
+    user
+    |> cast(params, [:blocks])
+    |> validate_required([:blocks])
+    |> update_and_set_cache()
+  end
+
+  def add_to_block(user, blocked) do
+    set_blocks(user, Enum.uniq([blocked | user.blocks]))
+  end
+
+  def remove_from_block(user, blocked) do
+    set_blocks(user, List.delete(user.blocks, blocked))
+  end
+
+  defp set_mutes(user, mutes) do
+    params = %{mutes: mutes}
+
+    user
+    |> cast(params, [:mutes])
+    |> validate_required([:mutes])
+    |> update_and_set_cache()
+  end
+
+  def add_to_mutes(user, muted, notifications?) do
+    with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
+      set_notification_mutes(
+        user,
+        Enum.uniq([muted | user.muted_notifications]),
+        notifications?
+      )
+    end
+  end
+
+  def remove_from_mutes(user, muted) do
+    with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do
+      set_notification_mutes(
+        user,
+        List.delete(user.muted_notifications, muted),
+        true
+      )
+    end
+  end
+
+  defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
+    {:ok, user}
+  end
+
+  defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
+    params = %{muted_notifications: muted_notifications}
+
+    user
+    |> cast(params, [:muted_notifications])
+    |> validate_required([:muted_notifications])
+    |> update_and_set_cache()
+  end
+
+  def add_reblog_mute(user, ap_id) do
+    params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
+
+    user
+    |> cast(params, [:muted_reblogs])
+    |> update_and_set_cache()
+  end
+
+  def remove_reblog_mute(user, ap_id) do
+    params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
+
+    user
+    |> cast(params, [:muted_reblogs])
+    |> update_and_set_cache()
+  end
+
+  def set_invisible(user, invisible) do
+    params = %{invisible: invisible}
+
+    user
+    |> cast(params, [:invisible])
+    |> validate_required([:invisible])
     |> update_and_set_cache()
   end
 end
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
deleted file mode 100644
index 2d39abcb3..000000000
--- a/lib/pleroma/user/info.ex
+++ /dev/null
@@ -1,472 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.User.Info do
-  use Ecto.Schema
-  import Ecto.Changeset
-
-  alias Pleroma.User.Info
-
-  @type t :: %__MODULE__{}
-
-  embedded_schema do
-    field(:banner, :map, default: %{})
-    field(:background, :map, default: %{})
-    field(:source_data, :map, default: %{})
-    field(:note_count, :integer, default: 0)
-    field(:follower_count, :integer, default: 0)
-    # Should be filled in only for remote users
-    field(:following_count, :integer, default: nil)
-    field(:locked, :boolean, default: false)
-    field(:confirmation_pending, :boolean, default: false)
-    field(:password_reset_pending, :boolean, default: false)
-    field(:confirmation_token, :string, default: nil)
-    field(:default_scope, :string, default: "public")
-    field(:blocks, {:array, :string}, default: [])
-    field(:domain_blocks, {:array, :string}, default: [])
-    field(:mutes, {:array, :string}, default: [])
-    field(:muted_reblogs, {:array, :string}, default: [])
-    field(:muted_notifications, {:array, :string}, default: [])
-    field(:subscribers, {:array, :string}, default: [])
-    field(:deactivated, :boolean, default: false)
-    field(:no_rich_text, :boolean, default: false)
-    field(:ap_enabled, :boolean, default: false)
-    field(:is_moderator, :boolean, default: false)
-    field(:is_admin, :boolean, default: false)
-    field(:show_role, :boolean, default: true)
-    field(:keys, :string, default: nil)
-    field(:settings, :map, default: nil)
-    field(:magic_key, :string, default: nil)
-    field(:uri, :string, default: nil)
-    field(:hide_followers_count, :boolean, default: false)
-    field(:hide_follows_count, :boolean, default: false)
-    field(:hide_followers, :boolean, default: false)
-    field(:hide_follows, :boolean, default: false)
-    field(:hide_favorites, :boolean, default: true)
-    field(:unread_conversation_count, :integer, default: 0)
-    field(:pinned_activities, {:array, :string}, default: [])
-    field(:email_notifications, :map, default: %{"digest" => false})
-    field(:mascot, :map, default: nil)
-    field(:emoji, {:array, :map}, default: [])
-    field(:pleroma_settings_store, :map, default: %{})
-    field(:fields, {:array, :map}, default: nil)
-    field(:raw_fields, {:array, :map}, default: [])
-    field(:discoverable, :boolean, default: false)
-
-    field(:notification_settings, :map,
-      default: %{
-        "followers" => true,
-        "follows" => true,
-        "non_follows" => true,
-        "non_followers" => true
-      }
-    )
-
-    field(:skip_thread_containment, :boolean, default: false)
-
-    # Found in the wild
-    # ap_id -> Where is this used?
-    # bio -> Where is this used?
-    # avatar -> Where is this used?
-    # fqn -> Where is this used?
-    # host -> Where is this used?
-    # subject _> Where is this used?
-  end
-
-  def set_activation_status(info, deactivated) do
-    params = %{deactivated: deactivated}
-
-    info
-    |> cast(params, [:deactivated])
-    |> validate_required([:deactivated])
-  end
-
-  def set_password_reset_pending(info, pending) do
-    params = %{password_reset_pending: pending}
-
-    info
-    |> cast(params, [:password_reset_pending])
-    |> validate_required([:password_reset_pending])
-  end
-
-  def update_notification_settings(info, settings) do
-    settings =
-      settings
-      |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end)
-      |> Map.new()
-
-    notification_settings =
-      info.notification_settings
-      |> Map.merge(settings)
-      |> Map.take(["followers", "follows", "non_follows", "non_followers"])
-
-    params = %{notification_settings: notification_settings}
-
-    info
-    |> cast(params, [:notification_settings])
-    |> validate_required([:notification_settings])
-  end
-
-  @doc """
-  Update email notifications in the given User.Info struct.
-
-  Examples:
-
-      iex> update_email_notifications(%Pleroma.User.Info{email_notifications: %{"digest" => false}}, %{"digest" => true})
-      %Pleroma.User.Info{email_notifications: %{"digest" => true}}
-
-  """
-  @spec update_email_notifications(t(), map()) :: Ecto.Changeset.t()
-  def update_email_notifications(info, settings) do
-    email_notifications =
-      info.email_notifications
-      |> Map.merge(settings)
-      |> Map.take(["digest"])
-
-    params = %{email_notifications: email_notifications}
-    fields = [:email_notifications]
-
-    info
-    |> cast(params, fields)
-    |> validate_required(fields)
-  end
-
-  def add_to_note_count(info, number) do
-    set_note_count(info, info.note_count + number)
-  end
-
-  def set_note_count(info, number) do
-    params = %{note_count: Enum.max([0, number])}
-
-    info
-    |> cast(params, [:note_count])
-    |> validate_required([:note_count])
-  end
-
-  def set_follower_count(info, number) do
-    params = %{follower_count: Enum.max([0, number])}
-
-    info
-    |> cast(params, [:follower_count])
-    |> validate_required([:follower_count])
-  end
-
-  def set_mutes(info, mutes) do
-    params = %{mutes: mutes}
-
-    info
-    |> cast(params, [:mutes])
-    |> validate_required([:mutes])
-  end
-
-  @spec set_notification_mutes(Changeset.t(), [String.t()], boolean()) :: Changeset.t()
-  def set_notification_mutes(changeset, muted_notifications, notifications?) do
-    if notifications? do
-      put_change(changeset, :muted_notifications, muted_notifications)
-      |> validate_required([:muted_notifications])
-    else
-      changeset
-    end
-  end
-
-  def set_blocks(info, blocks) do
-    params = %{blocks: blocks}
-
-    info
-    |> cast(params, [:blocks])
-    |> validate_required([:blocks])
-  end
-
-  def set_subscribers(info, subscribers) do
-    params = %{subscribers: subscribers}
-
-    info
-    |> cast(params, [:subscribers])
-    |> validate_required([:subscribers])
-  end
-
-  @spec add_to_mutes(Info.t(), String.t(), boolean()) :: Changeset.t()
-  def add_to_mutes(info, muted, notifications?) do
-    info
-    |> set_mutes(Enum.uniq([muted | info.mutes]))
-    |> set_notification_mutes(
-      Enum.uniq([muted | info.muted_notifications]),
-      notifications?
-    )
-  end
-
-  @spec remove_from_mutes(Info.t(), String.t()) :: Changeset.t()
-  def remove_from_mutes(info, muted) do
-    info
-    |> set_mutes(List.delete(info.mutes, muted))
-    |> set_notification_mutes(List.delete(info.muted_notifications, muted), true)
-  end
-
-  def add_to_block(info, blocked) do
-    set_blocks(info, Enum.uniq([blocked | info.blocks]))
-  end
-
-  def remove_from_block(info, blocked) do
-    set_blocks(info, List.delete(info.blocks, blocked))
-  end
-
-  def add_to_subscribers(info, subscribed) do
-    set_subscribers(info, Enum.uniq([subscribed | info.subscribers]))
-  end
-
-  def remove_from_subscribers(info, subscribed) do
-    set_subscribers(info, List.delete(info.subscribers, subscribed))
-  end
-
-  def set_domain_blocks(info, domain_blocks) do
-    params = %{domain_blocks: domain_blocks}
-
-    info
-    |> cast(params, [:domain_blocks])
-    |> validate_required([:domain_blocks])
-  end
-
-  def add_to_domain_block(info, domain_blocked) do
-    set_domain_blocks(info, Enum.uniq([domain_blocked | info.domain_blocks]))
-  end
-
-  def remove_from_domain_block(info, domain_blocked) do
-    set_domain_blocks(info, List.delete(info.domain_blocks, domain_blocked))
-  end
-
-  def set_keys(info, keys) do
-    params = %{keys: keys}
-
-    info
-    |> cast(params, [:keys])
-    |> validate_required([:keys])
-  end
-
-  def remote_user_creation(info, params) do
-    params =
-      if Map.has_key?(params, :fields) do
-        Map.put(params, :fields, Enum.map(params[:fields], &truncate_field/1))
-      else
-        params
-      end
-
-    info
-    |> cast(params, [
-      :ap_enabled,
-      :source_data,
-      :banner,
-      :locked,
-      :magic_key,
-      :uri,
-      :hide_followers,
-      :hide_follows,
-      :hide_followers_count,
-      :hide_follows_count,
-      :follower_count,
-      :fields,
-      :following_count,
-      :discoverable
-    ])
-    |> validate_fields(true)
-  end
-
-  def user_upgrade(info, params, remote? \\ false) do
-    info
-    |> cast(params, [
-      :ap_enabled,
-      :source_data,
-      :banner,
-      :locked,
-      :magic_key,
-      :follower_count,
-      :following_count,
-      :hide_follows,
-      :fields,
-      :hide_followers,
-      :discoverable,
-      :hide_followers_count,
-      :hide_follows_count
-    ])
-    |> validate_fields(remote?)
-  end
-
-  def profile_update(info, params) do
-    info
-    |> cast(params, [
-      :locked,
-      :no_rich_text,
-      :default_scope,
-      :banner,
-      :hide_follows,
-      :hide_followers,
-      :hide_followers_count,
-      :hide_follows_count,
-      :hide_favorites,
-      :background,
-      :show_role,
-      :skip_thread_containment,
-      :fields,
-      :raw_fields,
-      :pleroma_settings_store,
-      :discoverable
-    ])
-    |> validate_fields()
-  end
-
-  def validate_fields(changeset, remote? \\ false) do
-    limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
-    limit = Pleroma.Config.get([:instance, limit_name], 0)
-
-    changeset
-    |> validate_length(:fields, max: limit)
-    |> validate_change(:fields, fn :fields, fields ->
-      if Enum.all?(fields, &valid_field?/1) do
-        []
-      else
-        [fields: "invalid"]
-      end
-    end)
-  end
-
-  defp valid_field?(%{"name" => name, "value" => value}) do
-    name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
-    value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
-
-    is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
-      String.length(value) <= value_limit
-  end
-
-  defp valid_field?(_), do: false
-
-  defp truncate_field(%{"name" => name, "value" => value}) do
-    {name, _chopped} =
-      String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
-
-    {value, _chopped} =
-      String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
-
-    %{"name" => name, "value" => value}
-  end
-
-  @spec confirmation_changeset(Info.t(), keyword()) :: Changeset.t()
-  def confirmation_changeset(info, opts) do
-    need_confirmation? = Keyword.get(opts, :need_confirmation)
-
-    params =
-      if need_confirmation? do
-        %{
-          confirmation_pending: true,
-          confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
-        }
-      else
-        %{
-          confirmation_pending: false,
-          confirmation_token: nil
-        }
-      end
-
-    cast(info, params, [:confirmation_pending, :confirmation_token])
-  end
-
-  def mastodon_settings_update(info, settings) do
-    params = %{settings: settings}
-
-    info
-    |> cast(params, [:settings])
-    |> validate_required([:settings])
-  end
-
-  def mascot_update(info, url) do
-    params = %{mascot: url}
-
-    info
-    |> cast(params, [:mascot])
-    |> validate_required([:mascot])
-  end
-
-  def set_source_data(info, source_data) do
-    params = %{source_data: source_data}
-
-    info
-    |> cast(params, [:source_data])
-    |> validate_required([:source_data])
-  end
-
-  def admin_api_update(info, params) do
-    info
-    |> cast(params, [
-      :is_moderator,
-      :is_admin,
-      :show_role
-    ])
-  end
-
-  def add_pinnned_activity(info, %Pleroma.Activity{id: id}) do
-    if id not in info.pinned_activities do
-      max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
-      params = %{pinned_activities: info.pinned_activities ++ [id]}
-
-      info
-      |> cast(params, [:pinned_activities])
-      |> validate_length(:pinned_activities,
-        max: max_pinned_statuses,
-        message: "You have already pinned the maximum number of statuses"
-      )
-    else
-      change(info)
-    end
-  end
-
-  def remove_pinnned_activity(info, %Pleroma.Activity{id: id}) do
-    params = %{pinned_activities: List.delete(info.pinned_activities, id)}
-
-    cast(info, params, [:pinned_activities])
-  end
-
-  def roles(%Info{is_moderator: is_moderator, is_admin: is_admin}) do
-    %{
-      admin: is_admin,
-      moderator: is_moderator
-    }
-  end
-
-  def add_reblog_mute(info, ap_id) do
-    params = %{muted_reblogs: info.muted_reblogs ++ [ap_id]}
-
-    cast(info, params, [:muted_reblogs])
-  end
-
-  def remove_reblog_mute(info, ap_id) do
-    params = %{muted_reblogs: List.delete(info.muted_reblogs, ap_id)}
-
-    cast(info, params, [:muted_reblogs])
-  end
-
-  # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
-  # For example: [{"name": "Pronoun", "value": "she/her"}, …]
-  def fields(%{fields: nil, source_data: %{"attachment" => attachment}}) do
-    limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
-
-    attachment
-    |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
-    |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
-    |> Enum.take(limit)
-  end
-
-  def fields(%{fields: nil}), do: []
-
-  def fields(%{fields: fields}), do: fields
-
-  def follow_information_update(info, params) do
-    info
-    |> cast(params, [
-      :hide_followers,
-      :hide_follows,
-      :follower_count,
-      :following_count,
-      :hide_followers_count,
-      :hide_follows_count
-    ])
-  end
-end
diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex
index f32def4f5..2eda454bc 100644
--- a/lib/pleroma/user/query.ex
+++ b/lib/pleroma/user/query.ex
@@ -58,7 +58,6 @@ defmodule Pleroma.User.Query do
 
   @ilike_criteria [:nickname, :name, :query]
   @equal_criteria [:email]
-  @role_criteria [:is_admin, :is_moderator]
   @contains_criteria [:ap_id, :nickname]
 
   @spec build(criteria()) :: Query.t()
@@ -102,15 +101,19 @@ defmodule Pleroma.User.Query do
     Enum.reduce(tags, query, &prepare_tag_criteria/2)
   end
 
-  defp compose_query({key, _}, query) when key in @role_criteria do
-    where(query, [u], fragment("(?->? @> 'true')", u.info, ^to_string(key)))
+  defp compose_query({:is_admin, _}, query) do
+    where(query, [u], u.is_admin)
+  end
+
+  defp compose_query({:is_moderator, _}, query) do
+    where(query, [u], u.is_moderator)
   end
 
   defp compose_query({:super_users, _}, query) do
     where(
       query,
       [u],
-      fragment("?->'is_admin' @> 'true' OR ?->'is_moderator' @> 'true'", u.info, u.info)
+      u.is_admin or u.is_moderator
     )
   end
 
@@ -119,7 +122,13 @@ defmodule Pleroma.User.Query do
   defp compose_query({:external, _}, query), do: location_query(query, false)
 
   defp compose_query({:active, _}, query) do
-    where(query, [u], fragment("not (?->'deactivated' @> 'true')", u.info))
+    User.restrict_deactivated(query)
+    |> where([u], not is_nil(u.nickname))
+  end
+
+  defp compose_query({:legacy_active, _}, query) do
+    query
+    |> where([u], fragment("not (?->'deactivated' @> 'true')", u.info))
     |> where([u], not is_nil(u.nickname))
   end
 
@@ -128,7 +137,7 @@ defmodule Pleroma.User.Query do
   end
 
   defp compose_query({:deactivated, true}, query) do
-    where(query, [u], fragment("?->'deactivated' @> 'true'", u.info))
+    where(query, [u], u.deactivated == ^true)
     |> where([u], not is_nil(u.nickname))
   end
 
diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex
index 0d697fe3d..bab8d92e2 100644
--- a/lib/pleroma/user/search.ex
+++ b/lib/pleroma/user/search.ex
@@ -108,14 +108,14 @@ defmodule Pleroma.User.Search do
   defp base_query(_user, false), do: User
   defp base_query(user, true), do: User.get_followers_query(user)
 
-  defp filter_blocked_user(query, %User{info: %{blocks: blocks}})
+  defp filter_blocked_user(query, %User{blocks: blocks})
        when length(blocks) > 0 do
     from(q in query, where: not (q.ap_id in ^blocks))
   end
 
   defp filter_blocked_user(query, _), do: query
 
-  defp filter_blocked_domains(query, %User{info: %{domain_blocks: domain_blocks}})
+  defp filter_blocked_domains(query, %User{domain_blocks: domain_blocks})
        when length(domain_blocks) > 0 do
     domains = Enum.join(domain_blocks, ",")
 
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 05ed81d8f..093ee8fcd 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -69,7 +69,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   defp check_actor_is_active(actor) do
     if not is_nil(actor) do
       with user <- User.get_cached_by_ap_id(actor),
-           false <- user.info.deactivated do
+           false <- user.deactivated do
         true
       else
         _e -> false
@@ -248,8 +248,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
          _ <- increase_replies_count_if_reply(create_data),
          _ <- increase_poll_votes_if_vote(create_data),
          {:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
-         # Changing note count prior to enqueuing federation task in order to avoid
-         # race conditions on updating user.info
          {:ok, _actor} <- increase_note_count_if_public(actor, activity),
          :ok <- maybe_federate(activity) do
       {:ok, activity}
@@ -445,8 +443,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
          {:ok, activity} <- insert(data, local, false),
          stream_out_participations(object, user),
          _ <- decrease_replies_count_if_reply(object),
-         # Changing note count prior to enqueuing federation task in order to avoid
-         # race conditions on updating user.info
          {:ok, _actor} <- decrease_note_count_if_public(user, object),
          :ok <- maybe_federate(activity) do
       {:ok, activity}
@@ -662,7 +658,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_thread_visibility(
          query,
-         %{"user" => %User{info: %{skip_thread_containment: true}}},
+         %{"user" => %User{skip_thread_containment: true}},
          _
        ),
        do: query
@@ -700,7 +696,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
       |> Map.put("user", reading_user)
       |> Map.put("actor_id", user.ap_id)
       |> Map.put("whole_db", true)
-      |> Map.put("pinned_activity_ids", user.info.pinned_activities)
+      |> Map.put("pinned_activity_ids", user.pinned_activities)
 
     recipients =
       user_activities_recipients(%{
@@ -861,8 +857,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query
 
-  defp restrict_muted(query, %{"muting_user" => %User{info: info}} = opts) do
-    mutes = info.mutes
+  defp restrict_muted(query, %{"muting_user" => %User{} = user} = opts) do
+    mutes = user.mutes
 
     query =
       from([activity] in query,
@@ -879,9 +875,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_muted(query, _), do: query
 
-  defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
-    blocks = info.blocks || []
-    domain_blocks = info.domain_blocks || []
+  defp restrict_blocked(query, %{"blocking_user" => %User{} = user}) do
+    blocks = user.blocks || []
+    domain_blocks = user.domain_blocks || []
 
     query =
       if has_named_binding?(query, :object), do: query, else: Activity.with_joined_object(query)
@@ -922,8 +918,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_pinned(query, _), do: query
 
-  defp restrict_muted_reblogs(query, %{"muting_user" => %User{info: info}}) do
-    muted_reblogs = info.muted_reblogs || []
+  defp restrict_muted_reblogs(query, %{"muting_user" => %User{} = user}) do
+    muted_reblogs = user.muted_reblogs || []
 
     from(
       activity in query,
@@ -1108,17 +1104,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     locked = data["manuallyApprovesFollowers"] || false
     data = Transmogrifier.maybe_fix_user_object(data)
     discoverable = data["discoverable"] || false
+    invisible = data["invisible"] || false
 
     user_data = %{
       ap_id: data["id"],
-      info: %{
-        ap_enabled: true,
-        source_data: data,
-        banner: banner,
-        fields: fields,
-        locked: locked,
-        discoverable: discoverable
-      },
+      ap_enabled: true,
+      source_data: data,
+      banner: banner,
+      fields: fields,
+      locked: locked,
+      discoverable: discoverable,
+      invisible: invisible,
       avatar: avatar,
       name: data["name"],
       follower_address: data["followers"],
@@ -1170,7 +1166,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     with {:enabled, true} <-
            {:enabled, Pleroma.Config.get([:instance, :external_user_synchronization])},
          {:ok, info} <- fetch_follow_information_for_user(data) do
-      info = Map.merge(data.info, info)
+      info = Map.merge(data[:info] || %{}, info)
       Map.put(data, :info, info)
     else
       {:enabled, false} ->
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 9010eab91..b2cd965fe 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -137,7 +137,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
     with %User{} = user <- User.get_cached_by_nickname(nickname),
          {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
          {:show_follows, true} <-
-           {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
+           {:show_follows, (for_user && for_user == user) || !user.hide_follows} do
       {page, _} = Integer.parse(page)
 
       conn
@@ -174,7 +174,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
     with %User{} = user <- User.get_cached_by_nickname(nickname),
          {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
          {:show_followers, true} <-
-           {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
+           {:show_followers, (for_user && for_user == user) || !user.hide_followers} do
       {page, _} = Integer.parse(page)
 
       conn
@@ -387,7 +387,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
 
   def handle_user_activity(user, %{"type" => "Delete"} = params) do
     with %Object{} = object <- Object.normalize(params["object"]),
-         true <- user.info.is_moderator || user.ap_id == object.data["actor"],
+         true <- user.is_moderator || user.ap_id == object.data["actor"],
          {:ok, delete} <- ActivityPub.delete(object) do
       {:ok, delete}
     else
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
index b90193ca0..8abe18e29 100644
--- a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
@@ -11,7 +11,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
 
   # has the user successfully posted before?
   defp old_user?(%User{} = u) do
-    u.info.note_count > 0 || u.info.follower_count > 0
+    u.note_count > 0 || u.follower_count > 0
   end
 
   # does the post contain links?
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index 2aac4e8b9..4ea37fc7b 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -140,7 +140,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
     |> Enum.map(& &1.ap_id)
   end
 
-  defp maybe_use_sharedinbox(%User{info: %{source_data: data}}),
+  defp maybe_use_sharedinbox(%User{source_data: data}),
     do: (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
 
   @doc """
@@ -156,7 +156,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
   """
   def determine_inbox(
         %Activity{data: activity_data},
-        %User{info: %{source_data: data}} = user
+        %User{source_data: data} = user
       ) do
     to = activity_data["to"] || []
     cc = activity_data["cc"] || []
@@ -190,12 +190,12 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
 
     recipients
     |> Enum.filter(&User.ap_enabled?/1)
-    |> Enum.map(fn %{info: %{source_data: data}} -> data["inbox"] end)
+    |> Enum.map(fn %{source_data: data} -> data["inbox"] end)
     |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
     |> Instances.filter_reachable()
     |> Enum.each(fn {inbox, unreachable_since} ->
       %User{ap_id: ap_id} =
-        Enum.find(recipients, fn %{info: %{source_data: data}} -> data["inbox"] == inbox end)
+        Enum.find(recipients, fn %{source_data: data} -> data["inbox"] == inbox end)
 
       # Get all the recipients on the same host and add them to cc. Otherwise, a remote
       # instance would only accept a first message for the first recipient and ignore the rest.
diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex
index 830d1cde8..f90d75a8a 100644
--- a/lib/pleroma/web/activity_pub/relay.ex
+++ b/lib/pleroma/web/activity_pub/relay.ex
@@ -10,8 +10,12 @@ defmodule Pleroma.Web.ActivityPub.Relay do
   require Logger
 
   def get_actor do
-    "#{Pleroma.Web.Endpoint.url()}/relay"
-    |> User.get_or_create_service_actor_by_ap_id()
+    actor =
+      "#{Pleroma.Web.Endpoint.url()}/relay"
+      |> User.get_or_create_service_actor_by_ap_id()
+
+    {:ok, actor} = User.set_invisible(actor, true)
+    actor
   end
 
   @spec follow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 4eb718294..3c27b0d46 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -599,13 +599,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
           data,
         _options
       )
-      when object_type in ["Person", "Application", "Service", "Organization"] do
+      when object_type in [
+             "Person",
+             "Application",
+             "Service",
+             "Organization"
+           ] do
     with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
       {:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
 
-      banner = new_user_data[:info][:banner]
-      locked = new_user_data[:info][:locked] || false
-      attachment = get_in(new_user_data, [:info, :source_data, "attachment"]) || []
+      locked = new_user_data[:locked] || false
+      attachment = get_in(new_user_data, [:source_data, "attachment"]) || []
+      invisible = new_user_data[:invisible] || false
 
       fields =
         attachment
@@ -614,8 +619,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
 
       update_data =
         new_user_data
-        |> Map.take([:name, :bio, :avatar])
-        |> Map.put(:info, %{banner: banner, locked: locked, fields: fields})
+        |> Map.take([:avatar, :banner, :bio, :name])
+        |> Map.put(:fields, fields)
+        |> Map.put(:locked, locked)
+        |> Map.put(:invisible, invisible)
 
       actor
       |> User.upgrade_changeset(update_data, true)
@@ -982,7 +989,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
     %{"type" => "Mention", "href" => ap_id, "name" => "@#{nickname}"}
   end
 
-  def take_emoji_tags(%User{info: %{emoji: emoji} = _user_info} = _user) do
+  def take_emoji_tags(%User{emoji: emoji}) do
     emoji
     |> Enum.flat_map(&Map.to_list/1)
     |> Enum.map(&build_emoji_tag/1)
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 4ef479f96..699d5162f 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -49,26 +49,28 @@ defmodule Pleroma.Web.ActivityPub.Utils do
 
   def determine_explicit_mentions(_), do: []
 
-  @spec recipient_in_collection(any(), any()) :: boolean()
-  defp recipient_in_collection(ap_id, coll) when is_binary(coll), do: ap_id == coll
-  defp recipient_in_collection(ap_id, coll) when is_list(coll), do: ap_id in coll
-  defp recipient_in_collection(_, _), do: false
+  @spec label_in_collection?(any(), any()) :: boolean()
+  defp label_in_collection?(ap_id, coll) when is_binary(coll), do: ap_id == coll
+  defp label_in_collection?(ap_id, coll) when is_list(coll), do: ap_id in coll
+  defp label_in_collection?(_, _), do: false
+
+  @spec label_in_message?(String.t(), map()) :: boolean()
+  def label_in_message?(label, params),
+    do:
+      [params["to"], params["cc"], params["bto"], params["bcc"]]
+      |> Enum.any?(&label_in_collection?(label, &1))
+
+  @spec unaddressed_message?(map()) :: boolean()
+  def unaddressed_message?(params),
+    do:
+      [params["to"], params["cc"], params["bto"], params["bcc"]]
+      |> Enum.all?(&is_nil(&1))
 
   @spec recipient_in_message(User.t(), User.t(), map()) :: boolean()
-  def recipient_in_message(%User{ap_id: ap_id} = recipient, %User{} = actor, params) do
-    addresses = [params["to"], params["cc"], params["bto"], params["bcc"]]
-
-    cond do
-      Enum.any?(addresses, &recipient_in_collection(ap_id, &1)) -> true
-      # if the message is unaddressed at all, then assume it is directly addressed
-      # to the recipient
-      Enum.all?(addresses, &is_nil(&1)) -> true
-      # if the message is sent from somebody the user is following, then assume it
-      # is addressed to the recipient
-      User.following?(recipient, actor) -> true
-      true -> false
-    end
-  end
+  def recipient_in_message(%User{ap_id: ap_id} = recipient, %User{} = actor, params),
+    do:
+      label_in_message?(ap_id, params) || unaddressed_message?(params) ||
+        User.following?(recipient, actor)
 
   defp extract_list(target) when is_binary(target), do: [target]
   defp extract_list(lst) when is_list(lst), do: lst
@@ -76,8 +78,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do
 
   def maybe_splice_recipient(ap_id, params) do
     need_splice? =
-      !recipient_in_collection(ap_id, params["to"]) &&
-        !recipient_in_collection(ap_id, params["cc"])
+      !label_in_collection?(ap_id, params["to"]) &&
+        !label_in_collection?(ap_id, params["cc"])
 
     if need_splice? do
       cc_list = extract_list(params["cc"])
@@ -491,10 +493,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do
         %Activity{data: %{"actor" => actor}},
         object
       ) do
-    announcements = take_announcements(object)
+    unless actor |> User.get_cached_by_ap_id() |> User.invisible?() do
+      announcements = take_announcements(object)
 
-    with announcements <- Enum.uniq([actor | announcements]) do
-      update_element_in_object("announcement", announcements, object)
+      with announcements <- Enum.uniq([actor | announcements]) do
+        update_element_in_object("announcement", announcements, object)
+      end
+    else
+      {:ok, object}
     end
   end
 
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 9b39d1629..cf08045c9 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -55,7 +55,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
         "owner" => user.ap_id,
         "publicKeyPem" => public_key
       },
-      "endpoints" => endpoints
+      "endpoints" => endpoints,
+      "invisible" => User.invisible?(user)
     }
     |> Map.merge(Utils.make_json_ld_header())
   end
@@ -78,8 +79,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
     emoji_tags = Transmogrifier.take_emoji_tags(user)
 
     fields =
-      user.info
-      |> User.Info.fields()
+      user
+      |> User.fields()
       |> Enum.map(fn %{"name" => name, "value" => value} ->
         %{
           "name" => Pleroma.HTML.strip_tags(name),
@@ -99,7 +100,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
       "name" => user.name,
       "summary" => user.bio,
       "url" => user.ap_id,
-      "manuallyApprovesFollowers" => user.info.locked,
+      "manuallyApprovesFollowers" => user.locked,
       "publicKey" => %{
         "id" => "#{user.ap_id}#main-key",
         "owner" => user.ap_id,
@@ -107,8 +108,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
       },
       "endpoints" => endpoints,
       "attachment" => fields,
-      "tag" => (user.info.source_data["tag"] || []) ++ emoji_tags,
-      "discoverable" => user.info.discoverable
+      "tag" => (user.source_data["tag"] || []) ++ emoji_tags,
+      "discoverable" => user.discoverable
     }
     |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
     |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
@@ -116,8 +117,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
   end
 
   def render("following.json", %{user: user, page: page} = opts) do
-    showing_items = (opts[:for] && opts[:for] == user) || !user.info.hide_follows
-    showing_count = showing_items || !user.info.hide_follows_count
+    showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows
+    showing_count = showing_items || !user.hide_follows_count
 
     query = User.get_friends_query(user)
     query = from(user in query, select: [:ap_id])
@@ -135,8 +136,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
   end
 
   def render("following.json", %{user: user} = opts) do
-    showing_items = (opts[:for] && opts[:for] == user) || !user.info.hide_follows
-    showing_count = showing_items || !user.info.hide_follows_count
+    showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows
+    showing_count = showing_items || !user.hide_follows_count
 
     query = User.get_friends_query(user)
     query = from(user in query, select: [:ap_id])
@@ -155,7 +156,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
       "totalItems" => total,
       "first" =>
         if showing_items do
-          collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
+          collection(following, "#{user.ap_id}/following", 1, !user.hide_follows)
         else
           "#{user.ap_id}/following?page=1"
         end
@@ -164,8 +165,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
   end
 
   def render("followers.json", %{user: user, page: page} = opts) do
-    showing_items = (opts[:for] && opts[:for] == user) || !user.info.hide_followers
-    showing_count = showing_items || !user.info.hide_followers_count
+    showing_items = (opts[:for] && opts[:for] == user) || !user.hide_followers
+    showing_count = showing_items || !user.hide_followers_count
 
     query = User.get_followers_query(user)
     query = from(user in query, select: [:ap_id])
@@ -183,8 +184,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
   end
 
   def render("followers.json", %{user: user} = opts) do
-    showing_items = (opts[:for] && opts[:for] == user) || !user.info.hide_followers
-    showing_count = showing_items || !user.info.hide_followers_count
+    showing_items = (opts[:for] && opts[:for] == user) || !user.hide_followers
+    showing_count = showing_items || !user.hide_followers_count
 
     query = User.get_followers_query(user)
     query = from(user in query, select: [:ap_id])
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 47bc4e1ca..cd4097493 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
   alias Pleroma.Object
   alias Pleroma.Repo
   alias Pleroma.User
+  alias Pleroma.Web.ActivityPub.Utils
 
   require Pleroma.Constants
 
@@ -15,7 +16,7 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
   def is_public?(%Object{data: data}), do: is_public?(data)
   def is_public?(%Activity{data: data}), do: is_public?(data)
   def is_public?(%{"directMessage" => true}), do: false
-  def is_public?(data), do: Pleroma.Constants.as_public() in (data["to"] ++ (data["cc"] || []))
+  def is_public?(data), do: Utils.label_in_message?(Pleroma.Constants.as_public(), data)
 
   def is_private?(activity) do
     with false <- is_public?(activity),
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index b6d3f79c8..7ffbb23e7 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -51,8 +51,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
            :tag_users,
            :untag_users,
            :right_add,
-           :right_delete,
-           :set_activation_status
+           :right_delete
          ]
   )
 
@@ -250,9 +249,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
     user = User.get_cached_by_nickname(nickname)
 
-    {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
+    {:ok, updated_user} = User.deactivate(user, !user.deactivated)
 
-    action = if user.info.deactivated, do: "activate", else: "deactivate"
+    action = if user.deactivated, do: "activate", else: "deactivate"
 
     ModerationLog.insert_log(%{
       actor: admin,
@@ -364,11 +363,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
         "nicknames" => nicknames
       })
       when permission_group in ["moderator", "admin"] do
-    info = Map.put(%{}, "is_" <> permission_group, true)
+    update = %{:"is_#{permission_group}" => true}
 
     users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
 
-    User.update_info(users, &User.Info.admin_api_update(&1, info))
+    for u <- users, do: User.admin_api_update(u, update)
 
     ModerationLog.insert_log(%{
       action: "grant",
@@ -377,7 +376,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
       permission: permission_group
     })
 
-    json(conn, info)
+    json(conn, update)
   end
 
   def right_add_multiple(conn, _) do
@@ -389,12 +388,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
         "nickname" => nickname
       })
       when permission_group in ["moderator", "admin"] do
-    info = Map.put(%{}, "is_" <> permission_group, true)
+    fields = %{:"is_#{permission_group}" => true}
 
     {:ok, user} =
       nickname
       |> User.get_cached_by_nickname()
-      |> User.update_info(&User.Info.admin_api_update(&1, info))
+      |> User.admin_api_update(fields)
 
     ModerationLog.insert_log(%{
       action: "grant",
@@ -403,7 +402,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
       permission: permission_group
     })
 
-    json(conn, info)
+    json(conn, fields)
   end
 
   def right_add(conn, _) do
@@ -415,8 +414,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
 
     conn
     |> json(%{
-      is_moderator: user.info.is_moderator,
-      is_admin: user.info.is_admin
+      is_moderator: user.is_moderator,
+      is_admin: user.is_admin
     })
   end
 
@@ -429,11 +428,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
       )
       when permission_group in ["moderator", "admin"] do
     with false <- Enum.member?(nicknames, admin_nickname) do
-      info = Map.put(%{}, "is_" <> permission_group, false)
+      update = %{:"is_#{permission_group}" => false}
 
       users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
 
-      User.update_info(users, &User.Info.admin_api_update(&1, info))
+      for u <- users, do: User.admin_api_update(u, update)
 
       ModerationLog.insert_log(%{
         action: "revoke",
@@ -442,7 +441,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
         permission: permission_group
       })
 
-      json(conn, info)
+      json(conn, update)
     else
       _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
     end
@@ -460,12 +459,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
         }
       )
       when permission_group in ["moderator", "admin"] do
-    info = Map.put(%{}, "is_" <> permission_group, false)
+    fields = %{:"is_#{permission_group}" => false}
 
     {:ok, user} =
       nickname
       |> User.get_cached_by_nickname()
-      |> User.update_info(&User.Info.admin_api_update(&1, info))
+      |> User.admin_api_update(fields)
 
     ModerationLog.insert_log(%{
       action: "revoke",
@@ -474,7 +473,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
       permission: permission_group
     })
 
-    json(conn, info)
+    json(conn, fields)
   end
 
   def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex
index 441269162..6aa7257ce 100644
--- a/lib/pleroma/web/admin_api/views/account_view.ex
+++ b/lib/pleroma/web/admin_api/views/account_view.ex
@@ -7,7 +7,6 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
 
   alias Pleroma.HTML
   alias Pleroma.User
-  alias Pleroma.User.Info
   alias Pleroma.Web.AdminAPI.AccountView
   alias Pleroma.Web.MediaProxy
 
@@ -34,9 +33,9 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
       "avatar" => avatar,
       "nickname" => user.nickname,
       "display_name" => display_name,
-      "deactivated" => user.info.deactivated,
+      "deactivated" => user.deactivated,
       "local" => user.local,
-      "roles" => Info.roles(user.info),
+      "roles" => User.roles(user),
       "tags" => user.tags || []
     }
   end
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 40b3930fb..e57345621 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -266,10 +266,10 @@ defmodule Pleroma.Web.CommonAPI do
   # Updates the emojis for a user based on their profile
   def update(user) do
     emoji = emoji_from_profile(user)
-    source_data = user.info |> Map.get(:source_data, %{}) |> Map.put("tag", emoji)
+    source_data = Map.put(user.source_data, "tag", emoji)
 
     user =
-      case User.update_info(user, &User.Info.set_source_data(&1, source_data)) do
+      case User.update_source_data(user, source_data) do
         {:ok, user} -> user
         _ -> user
       end
@@ -290,20 +290,20 @@ defmodule Pleroma.Web.CommonAPI do
            object: %Object{data: %{"type" => "Note"}}
          } = activity <- get_by_id_or_ap_id(id_or_ap_id),
          true <- Visibility.is_public?(activity),
-         {:ok, _user} <- User.update_info(user, &User.Info.add_pinnned_activity(&1, activity)) do
+         {:ok, _user} <- User.add_pinnned_activity(user, activity) do
       {:ok, activity}
     else
-      {:error, %{changes: %{info: %{errors: [pinned_activities: {err, _}]}}}} -> {:error, err}
+      {:error, %{errors: [pinned_activities: {err, _}]}} -> {:error, err}
       _ -> {:error, dgettext("errors", "Could not pin")}
     end
   end
 
   def unpin(id_or_ap_id, user) do
     with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
-         {:ok, _user} <- User.update_info(user, &User.Info.remove_pinnned_activity(&1, activity)) do
+         {:ok, _user} <- User.remove_pinnned_activity(user, activity) do
       {:ok, activity}
     else
-      %{errors: [pinned_activities: {err, _}]} -> {:error, err}
+      {:error, %{errors: [pinned_activities: {err, _}]}} -> {:error, err}
       _ -> {:error, dgettext("errors", "Could not unpin")}
     end
   end
@@ -395,14 +395,14 @@ defmodule Pleroma.Web.CommonAPI do
   defp set_visibility(activity, _), do: {:ok, activity}
 
   def hide_reblogs(user, %{ap_id: ap_id} = _muted) do
-    if ap_id not in user.info.muted_reblogs do
-      User.update_info(user, &User.Info.add_reblog_mute(&1, ap_id))
+    if ap_id not in user.muted_reblogs do
+      User.add_reblog_mute(user, ap_id)
     end
   end
 
   def show_reblogs(user, %{ap_id: ap_id} = _muted) do
-    if ap_id in user.info.muted_reblogs do
-      User.update_info(user, &User.Info.remove_reblog_mute(&1, ap_id))
+    if ap_id in user.muted_reblogs do
+      User.remove_reblog_mute(user, ap_id)
     end
   end
 end
diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex
index 93b38e8f4..ca261ad6e 100644
--- a/lib/pleroma/web/masto_fe_controller.ex
+++ b/lib/pleroma/web/masto_fe_controller.ex
@@ -42,7 +42,7 @@ defmodule Pleroma.Web.MastoFEController do
 
   @doc "PUT /api/web/settings"
   def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
-    with {:ok, _} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do
+    with {:ok, _} <- User.mastodon_settings_update(user, settings) do
       json(conn, %{})
     else
       e ->
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 9ef7fd48d..73fad519e 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -130,25 +130,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   def update_credentials(%{assigns: %{user: original_user}} = conn, params) do
     user = original_user
 
-    user_params =
-      %{}
-      |> add_if_present(params, "display_name", :name)
-      |> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value, user)} end)
-      |> add_if_present(params, "avatar", :avatar, fn value ->
-        with %Plug.Upload{} <- value,
-             {:ok, object} <- ActivityPub.upload(value, type: :avatar) do
-          {:ok, object.data}
-        end
-      end)
-
-    emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "")
-
-    user_info_emojis =
-      user.info
-      |> Map.get(:emoji, [])
-      |> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text))
-      |> Enum.dedup()
-
     params =
       if Map.has_key?(params, "fields_attributes") do
         Map.update!(params, "fields_attributes", fn fields ->
@@ -160,7 +141,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
         params
       end
 
-    info_params =
+    user_params =
       [
         :no_rich_text,
         :locked,
@@ -176,15 +157,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
       |> Enum.reduce(%{}, fn key, acc ->
         add_if_present(acc, params, to_string(key), key, &{:ok, truthy_param?(&1)})
       end)
-      |> add_if_present(params, "default_scope", :default_scope)
-      |> add_if_present(params, "fields_attributes", :fields, fn fields ->
-        fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
-
-        {:ok, fields}
-      end)
-      |> add_if_present(params, "fields_attributes", :raw_fields)
-      |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value ->
-        {:ok, Map.merge(user.info.pleroma_settings_store, value)}
+      |> add_if_present(params, "display_name", :name)
+      |> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value, user)} end)
+      |> add_if_present(params, "avatar", :avatar, fn value ->
+        with %Plug.Upload{} <- value,
+             {:ok, object} <- ActivityPub.upload(value, type: :avatar) do
+          {:ok, object.data}
+        end
       end)
       |> add_if_present(params, "header", :banner, fn value ->
         with %Plug.Upload{} <- value,
@@ -198,12 +177,27 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
           {:ok, object.data}
         end
       end)
-      |> Map.put(:emoji, user_info_emojis)
+      |> add_if_present(params, "fields_attributes", :fields, fn fields ->
+        fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
 
-    changeset =
+        {:ok, fields}
+      end)
+      |> add_if_present(params, "fields_attributes", :raw_fields)
+      |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value ->
+        {:ok, Map.merge(user.pleroma_settings_store, value)}
+      end)
+      |> add_if_present(params, "default_scope", :default_scope)
+
+    emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "")
+
+    user_emojis =
       user
-      |> User.update_changeset(user_params)
-      |> User.change_info(&User.Info.profile_update(&1, info_params))
+      |> Map.get(:emoji, [])
+      |> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text))
+      |> Enum.dedup()
+
+    user_params = Map.put(user_params, :emoji, user_emojis)
+    changeset = User.update_changeset(user, user_params)
 
     with {:ok, user} <- User.update_and_set_cache(changeset) do
       if original_user != user, do: CommonAPI.update(user)
@@ -269,7 +263,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
     followers =
       cond do
         for_user && user.id == for_user.id -> MastodonAPI.get_followers(user, params)
-        user.info.hide_followers -> []
+        user.hide_followers -> []
         true -> MastodonAPI.get_followers(user, params)
       end
 
@@ -283,7 +277,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
     followers =
       cond do
         for_user && user.id == for_user.id -> MastodonAPI.get_friends(user, params)
-        user.info.hide_follows -> []
+        user.hide_follows -> []
         true -> MastodonAPI.get_friends(user, params)
       end
 
diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
index c7606246b..456fe7ab2 100644
--- a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
@@ -21,8 +21,8 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
   plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
 
   @doc "GET /api/v1/domain_blocks"
-  def index(%{assigns: %{user: %{info: info}}} = conn, _) do
-    json(conn, Map.get(info, :domain_blocks, []))
+  def index(%{assigns: %{user: user}} = conn, _) do
+    json(conn, Map.get(user, :domain_blocks, []))
   end
 
   @doc "POST /api/v1/domain_blocks"
diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex
new file mode 100644
index 000000000..ce025624d
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex
@@ -0,0 +1,32 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.MarkerController do
+  use Pleroma.Web, :controller
+  alias Pleroma.Plugs.OAuthScopesPlug
+
+  plug(
+    OAuthScopesPlug,
+    %{scopes: ["read:statuses"]}
+    when action == :index
+  )
+
+  plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :upsert)
+  plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
+  action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+  # GET /api/v1/markers
+  def index(%{assigns: %{user: user}} = conn, params) do
+    markers = Pleroma.Marker.get_markers(user, params["timeline"])
+    render(conn, "markers.json", %{markers: markers})
+  end
+
+  # POST /api/v1/markers
+  def upsert(%{assigns: %{user: user}} = conn, params) do
+    with {:ok, result} <- Pleroma.Marker.upsert(user, params),
+         markers <- Map.values(result) do
+      render(conn, "markers.json", %{markers: markers})
+    end
+  end
+end
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 2d4976891..e30fed610 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -74,23 +74,23 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
     user_info = User.get_cached_user_info(user)
 
     following_count =
-      if !user.info.hide_follows_count or !user.info.hide_follows or opts[:for] == user do
+      if !user.hide_follows_count or !user.hide_follows or opts[:for] == user do
         user_info.following_count
       else
         0
       end
 
     followers_count =
-      if !user.info.hide_followers_count or !user.info.hide_followers or opts[:for] == user do
+      if !user.hide_followers_count or !user.hide_followers or opts[:for] == user do
         user_info.follower_count
       else
         0
       end
 
-    bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"]
+    bot = (user.source_data["type"] || "Person") in ["Application", "Service"]
 
     emojis =
-      (user.info.source_data["tag"] || [])
+      (user.source_data["tag"] || [])
       |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
       |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
         %{
@@ -102,8 +102,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
       end)
 
     fields =
-      user.info
-      |> User.Info.fields()
+      user
+      |> User.fields()
       |> Enum.map(fn %{"name" => name, "value" => value} ->
         %{
           "name" => Pleroma.HTML.strip_tags(name),
@@ -111,23 +111,19 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
         }
       end)
 
-    raw_fields = Map.get(user.info, :raw_fields, [])
-
     bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for]))
     relationship = render("relationship.json", %{user: opts[:for], target: user})
 
-    discoverable = user.info.discoverable
-
     %{
       id: to_string(user.id),
       username: username_from_nickname(user.nickname),
       acct: user.nickname,
       display_name: display_name,
-      locked: user_info.locked,
+      locked: user.locked,
       created_at: Utils.to_masto_date(user.inserted_at),
       followers_count: followers_count,
       following_count: following_count,
-      statuses_count: user_info.note_count,
+      statuses_count: user.note_count,
       note: bio || "",
       url: User.profile_url(user),
       avatar: image,
@@ -140,9 +136,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
       source: %{
         note: HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
         sensitive: false,
-        fields: raw_fields,
+        fields: user.raw_fields,
         pleroma: %{
-          discoverable: discoverable
+          discoverable: user.discoverable
         }
       },
 
@@ -150,14 +146,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
       pleroma: %{
         confirmation_pending: user_info.confirmation_pending,
         tags: user.tags,
-        hide_followers_count: user.info.hide_followers_count,
-        hide_follows_count: user.info.hide_follows_count,
-        hide_followers: user.info.hide_followers,
-        hide_follows: user.info.hide_follows,
-        hide_favorites: user.info.hide_favorites,
+        hide_followers_count: user.hide_followers_count,
+        hide_follows_count: user.hide_follows_count,
+        hide_followers: user.hide_followers,
+        hide_follows: user.hide_follows,
+        hide_favorites: user.hide_favorites,
         relationship: relationship,
-        skip_thread_containment: user.info.skip_thread_containment,
-        background_image: image_url(user.info.background) |> MediaProxy.url()
+        skip_thread_containment: user.skip_thread_containment,
+        background_image: image_url(user.background) |> MediaProxy.url()
       }
     }
     |> maybe_put_role(user, opts[:for])
@@ -195,21 +191,21 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
          data,
          %User{id: user_id} = user,
          %User{id: user_id},
-         user_info
+         _user_info
        ) do
     data
-    |> Kernel.put_in([:source, :privacy], user_info.default_scope)
-    |> Kernel.put_in([:source, :pleroma, :show_role], user.info.show_role)
-    |> Kernel.put_in([:source, :pleroma, :no_rich_text], user.info.no_rich_text)
+    |> Kernel.put_in([:source, :privacy], user.default_scope)
+    |> Kernel.put_in([:source, :pleroma, :show_role], user.show_role)
+    |> Kernel.put_in([:source, :pleroma, :no_rich_text], user.no_rich_text)
   end
 
   defp maybe_put_settings(data, _, _, _), do: data
 
-  defp maybe_put_settings_store(data, %User{info: info, id: id}, %User{id: id}, %{
+  defp maybe_put_settings_store(data, %User{} = user, %User{}, %{
          with_pleroma_settings: true
        }) do
     data
-    |> Kernel.put_in([:pleroma, :settings_store], info.pleroma_settings_store)
+    |> Kernel.put_in([:pleroma, :settings_store], user.pleroma_settings_store)
   end
 
   defp maybe_put_settings_store(data, _, _, _), do: data
@@ -223,28 +219,28 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
 
   defp maybe_put_chat_token(data, _, _, _), do: data
 
-  defp maybe_put_role(data, %User{info: %{show_role: true}} = user, _) do
+  defp maybe_put_role(data, %User{show_role: true} = user, _) do
     data
-    |> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin)
-    |> Kernel.put_in([:pleroma, :is_moderator], user.info.is_moderator)
+    |> Kernel.put_in([:pleroma, :is_admin], user.is_admin)
+    |> Kernel.put_in([:pleroma, :is_moderator], user.is_moderator)
   end
 
   defp maybe_put_role(data, %User{id: user_id} = user, %User{id: user_id}) do
     data
-    |> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin)
-    |> Kernel.put_in([:pleroma, :is_moderator], user.info.is_moderator)
+    |> Kernel.put_in([:pleroma, :is_admin], user.is_admin)
+    |> Kernel.put_in([:pleroma, :is_moderator], user.is_moderator)
   end
 
   defp maybe_put_role(data, _, _), do: data
 
   defp maybe_put_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
-    Kernel.put_in(data, [:pleroma, :notification_settings], user.info.notification_settings)
+    Kernel.put_in(data, [:pleroma, :notification_settings], user.notification_settings)
   end
 
   defp maybe_put_notification_settings(data, _, _), do: data
 
-  defp maybe_put_activation_status(data, user, %User{info: %{is_admin: true}}) do
-    Kernel.put_in(data, [:pleroma, :deactivated], user.info.deactivated)
+  defp maybe_put_activation_status(data, user, %User{is_admin: true}) do
+    Kernel.put_in(data, [:pleroma, :deactivated], user.deactivated)
   end
 
   defp maybe_put_activation_status(data, _, _), do: data
@@ -253,7 +249,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
     data
     |> Kernel.put_in(
       [:pleroma, :unread_conversation_count],
-      user.info.unread_conversation_count
+      user.unread_conversation_count
     )
   end
 
diff --git a/lib/pleroma/web/mastodon_api/views/marker_view.ex b/lib/pleroma/web/mastodon_api/views/marker_view.ex
new file mode 100644
index 000000000..38fbeed5f
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/marker_view.ex
@@ -0,0 +1,17 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.MarkerView do
+  use Pleroma.Web, :view
+
+  def render("markers.json", %{markers: markers}) do
+    Enum.reduce(markers, %{}, fn m, acc ->
+      Map.put_new(acc, m.timeline, %{
+        last_read_id: m.last_read_id,
+        version: m.lock_version,
+        updated_at: NaiveDateTime.to_iso8601(m.updated_at)
+      })
+    end)
+  end
+end
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 9b8dd3086..b785ca9d4 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -498,6 +498,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
   defp present?(false), do: false
   defp present?(_), do: true
 
-  defp pinned?(%Activity{id: id}, %User{info: %{pinned_activities: pinned_activities}}),
+  defp pinned?(%Activity{id: id}, %User{pinned_activities: pinned_activities}),
     do: id in pinned_activities
 end
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 03c9a5027..fe71aca8c 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -202,9 +202,9 @@ defmodule Pleroma.Web.OAuth.OAuthController do
     with {:ok, %User{} = user} <- Authenticator.get_user(conn),
          {:ok, app} <- Token.Utils.fetch_app(conn),
          {:auth_active, true} <- {:auth_active, User.auth_active?(user)},
-         {:user_active, true} <- {:user_active, !user.info.deactivated},
+         {:user_active, true} <- {:user_active, !user.deactivated},
          {:password_reset_pending, false} <-
-           {:password_reset_pending, user.info.password_reset_pending},
+           {:password_reset_pending, user.password_reset_pending},
          {:ok, scopes} <- validate_scopes(app, params),
          {:ok, auth} <- Authorization.create_authorization(app, user, scopes),
          {:ok, token} <- Token.exchange_token(app, auth) do
diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
index 7ce9d5da2..db6faac83 100644
--- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
@@ -80,9 +80,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
 
   @doc "PATCH /api/v1/pleroma/accounts/update_banner"
   def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
-    new_info = %{"banner" => %{}}
-
-    with {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
+    with {:ok, user} <- User.update_banner(user, %{}) do
       CommonAPI.update(user)
       json(conn, %{url: nil})
     end
@@ -90,8 +88,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
 
   def update_banner(%{assigns: %{user: user}} = conn, params) do
     with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
-         new_info <- %{"banner" => object.data},
-         {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
+         {:ok, user} <- User.update_banner(user, object.data) do
       CommonAPI.update(user)
       %{"url" => [%{"href" => href} | _]} = object.data
 
@@ -101,17 +98,14 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
 
   @doc "PATCH /api/v1/pleroma/accounts/update_background"
   def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
-    new_info = %{"background" => %{}}
-
-    with {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
+    with {:ok, _user} <- User.update_background(user, %{}) do
       json(conn, %{url: nil})
     end
   end
 
   def update_background(%{assigns: %{user: user}} = conn, params) do
     with {:ok, object} <- ActivityPub.upload(params, type: :background),
-         new_info <- %{"background" => object.data},
-         {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
+         {:ok, _user} <- User.update_background(user, object.data) do
       %{"url" => [%{"href" => href} | _]} = object.data
 
       json(conn, %{url: href})
@@ -119,7 +113,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
   end
 
   @doc "GET /api/v1/pleroma/accounts/:id/favourites"
-  def favourites(%{assigns: %{account: %{info: %{hide_favorites: true}}}} = conn, _params) do
+  def favourites(%{assigns: %{account: %{hide_favorites: true}}} = conn, _params) do
     render_error(conn, :forbidden, "Can't get favorites")
   end
 
diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
index d71d72dd5..8cf552b7e 100644
--- a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
@@ -24,9 +24,7 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do
     with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)),
          # Reject if not an image
          %{type: "image"} = attachment <- render_attachment(object) do
-      # Sure!
-      # Save to the user's info
-      {:ok, _user} = User.update_info(user, &User.Info.mascot_update(&1, attachment))
+      {:ok, _user} = User.mascot_update(user, attachment)
 
       json(conn, attachment)
     else
diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex
index 35d3ff07c..dd445e8bf 100644
--- a/lib/pleroma/web/push/impl.ex
+++ b/lib/pleroma/web/push/impl.ex
@@ -125,6 +125,10 @@ defmodule Pleroma.Web.Push.Impl do
     end
   end
 
+  def format_title(%{activity: %{data: %{"directMessage" => true}}}) do
+    "New Direct Message"
+  end
+
   def format_title(%{activity: %{data: %{"type" => type}}}) do
     case type do
       "Create" -> "New Mention"
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index f2588f89e..f69c5c2bc 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -405,6 +405,9 @@ defmodule Pleroma.Web.Router do
     get("/push/subscription", SubscriptionController, :get)
     put("/push/subscription", SubscriptionController, :update)
     delete("/push/subscription", SubscriptionController, :delete)
+
+    get("/markers", MarkerController, :index)
+    post("/markers", MarkerController, :upsert)
   end
 
   scope "/api/web", Pleroma.Web do
diff --git a/lib/pleroma/web/streamer/worker.ex b/lib/pleroma/web/streamer/worker.ex
index 0ea224874..c2ee9e1f5 100644
--- a/lib/pleroma/web/streamer/worker.ex
+++ b/lib/pleroma/web/streamer/worker.ex
@@ -129,12 +129,12 @@ defmodule Pleroma.Web.Streamer.Worker do
   end
 
   defp should_send?(%User{} = user, %Activity{} = item) do
-    blocks = user.info.blocks || []
-    mutes = user.info.mutes || []
-    reblog_mutes = user.info.muted_reblogs || []
+    blocks = user.blocks || []
+    mutes = user.mutes || []
+    reblog_mutes = user.muted_reblogs || []
     recipient_blocks = MapSet.new(blocks ++ mutes)
     recipients = MapSet.new(item.recipients)
-    domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks)
+    domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
 
     with parent when not is_nil(parent) <- Object.normalize(item),
          true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)),
@@ -212,7 +212,7 @@ defmodule Pleroma.Web.Streamer.Worker do
   end
 
   @spec thread_containment(Activity.t(), User.t()) :: boolean()
-  defp thread_containment(_activity, %User{info: %{skip_thread_containment: true}}), do: true
+  defp thread_containment(_activity, %User{skip_thread_containment: true}), do: true
 
   defp thread_containment(activity, user) do
     if Config.get([:instance, :skip_thread_containment]) do
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
index bf5a6ae42..39f10c49f 100644
--- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
@@ -20,11 +20,12 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
   action_fallback(:errors)
 
   def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
-    new_info = [need_confirmation: false]
-
-    with %User{info: info} = user <- User.get_cached_by_id(uid),
-         true <- user.local and info.confirmation_pending and info.confirmation_token == token,
-         {:ok, _} <- User.update_info(user, &User.Info.confirmation_changeset(&1, new_info)) do
+    with %User{} = user <- User.get_cached_by_id(uid),
+         true <- user.local and user.confirmation_pending and user.confirmation_token == token,
+         {:ok, _} <-
+           user
+           |> User.confirmation_changeset(need_confirmation: false)
+           |> User.update_and_set_cache() do
       redirect(conn, to: "/")
     end
   end
diff --git a/lib/pleroma/web/views/masto_fe_view.ex b/lib/pleroma/web/views/masto_fe_view.ex
index 85b164b59..c39b7f095 100644
--- a/lib/pleroma/web/views/masto_fe_view.ex
+++ b/lib/pleroma/web/views/masto_fe_view.ex
@@ -61,12 +61,12 @@ defmodule Pleroma.Web.MastoFEView do
       },
       poll_limits: Config.get([:instance, :poll_limits]),
       rights: %{
-        delete_others_notice: present?(user.info.is_moderator),
-        admin: present?(user.info.is_admin)
+        delete_others_notice: present?(user.is_moderator),
+        admin: present?(user.is_admin)
       },
       compose: %{
         me: "#{user.id}",
-        default_privacy: user.info.default_scope,
+        default_privacy: user.default_scope,
         default_sensitive: false,
         allow_content_types: Config.get([:instance, :allowed_post_formats])
       },
@@ -86,7 +86,7 @@ defmodule Pleroma.Web.MastoFEView do
           "video\/mp4"
         ]
       },
-      settings: user.info.settings || @default_settings,
+      settings: user.settings || @default_settings,
       push_subscription: nil,
       accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},
       custom_emojis: render(CustomEmojiView, "index.json", custom_emojis: custom_emojis),
diff --git a/priv/repo/migrations/20190710125158_add_following_address_from_source_data.exs b/priv/repo/migrations/20190710125158_add_following_address_from_source_data.exs
index 779aa382e..a5170d521 100644
--- a/priv/repo/migrations/20190710125158_add_following_address_from_source_data.exs
+++ b/priv/repo/migrations/20190710125158_add_following_address_from_source_data.exs
@@ -5,7 +5,11 @@ defmodule Pleroma.Repo.Migrations.AddFollowingAddressFromSourceData do
 
   def change do
     query =
-      User.external_users_query()
+      User.Query.build(%{
+        external: true,
+        legacy_active: true,
+        order_by: :id
+      })
       |> select([u], struct(u, [:id, :ap_id, :info]))
 
     Pleroma.Repo.stream(query)
diff --git a/priv/repo/migrations/20190711042024_copy_muted_to_muted_notifications.exs b/priv/repo/migrations/20190711042024_copy_muted_to_muted_notifications.exs
index a5eec848b..fc9bf70ba 100644
--- a/priv/repo/migrations/20190711042024_copy_muted_to_muted_notifications.exs
+++ b/priv/repo/migrations/20190711042024_copy_muted_to_muted_notifications.exs
@@ -1,6 +1,5 @@
 defmodule Pleroma.Repo.Migrations.CopyMutedToMutedNotifications do
   use Ecto.Migration
-  alias Pleroma.User
 
   def change do
     execute(
diff --git a/priv/repo/migrations/20191009154608_copy_users_info_fields_to_users.exs b/priv/repo/migrations/20191009154608_copy_users_info_fields_to_users.exs
new file mode 100644
index 000000000..9dd27511c
--- /dev/null
+++ b/priv/repo/migrations/20191009154608_copy_users_info_fields_to_users.exs
@@ -0,0 +1,187 @@
+defmodule Pleroma.Repo.Migrations.CopyUsersInfoFieldsToUsers do
+  use Ecto.Migration
+
+  @jsonb_array_default "'[]'::jsonb"
+
+  @info_fields [
+    :banner,
+    :background,
+    :source_data,
+    :note_count,
+    :follower_count,
+    :following_count,
+    :locked,
+    :confirmation_pending,
+    :password_reset_pending,
+    :confirmation_token,
+    :default_scope,
+    :blocks,
+    :domain_blocks,
+    :mutes,
+    :muted_reblogs,
+    :muted_notifications,
+    :subscribers,
+    :deactivated,
+    :no_rich_text,
+    :ap_enabled,
+    :is_moderator,
+    :is_admin,
+    :show_role,
+    :settings,
+    :magic_key,
+    :uri,
+    :hide_followers_count,
+    :hide_follows_count,
+    :hide_followers,
+    :hide_follows,
+    :hide_favorites,
+    :unread_conversation_count,
+    :pinned_activities,
+    :email_notifications,
+    :mascot,
+    :emoji,
+    :pleroma_settings_store,
+    :fields,
+    :raw_fields,
+    :discoverable,
+    :invisible,
+    :skip_thread_containment,
+    :notification_settings
+  ]
+
+  @jsonb_fields [
+    :banner,
+    :background,
+    :source_data,
+    :settings,
+    :email_notifications,
+    :mascot,
+    :pleroma_settings_store,
+    :notification_settings
+  ]
+
+  @array_jsonb_fields [:emoji, :fields, :raw_fields]
+
+  @int_fields [:note_count, :follower_count, :following_count, :unread_conversation_count]
+
+  @boolean_fields [
+    :locked,
+    :confirmation_pending,
+    :password_reset_pending,
+    :deactivated,
+    :no_rich_text,
+    :ap_enabled,
+    :is_moderator,
+    :is_admin,
+    :show_role,
+    :hide_followers_count,
+    :hide_follows_count,
+    :hide_followers,
+    :hide_follows,
+    :hide_favorites,
+    :discoverable,
+    :invisible,
+    :skip_thread_containment
+  ]
+
+  @array_text_fields [
+    :blocks,
+    :domain_blocks,
+    :mutes,
+    :muted_reblogs,
+    :muted_notifications,
+    :subscribers,
+    :pinned_activities
+  ]
+
+  def change do
+    alter table(:users) do
+      add(:banner, :map, default: %{})
+      add(:background, :map, default: %{})
+      add(:source_data, :map, default: %{})
+      add(:note_count, :integer, default: 0)
+      add(:follower_count, :integer, default: 0)
+      add(:following_count, :integer, default: nil)
+      add(:locked, :boolean, default: false, null: false)
+      add(:confirmation_pending, :boolean, default: false, null: false)
+      add(:password_reset_pending, :boolean, default: false, null: false)
+      add(:confirmation_token, :text, default: nil)
+      add(:default_scope, :string, default: "public")
+      add(:blocks, {:array, :text}, default: [])
+      add(:domain_blocks, {:array, :text}, default: [])
+      add(:mutes, {:array, :text}, default: [])
+      add(:muted_reblogs, {:array, :text}, default: [])
+      add(:muted_notifications, {:array, :text}, default: [])
+      add(:subscribers, {:array, :text}, default: [])
+      add(:deactivated, :boolean, default: false, null: false)
+      add(:no_rich_text, :boolean, default: false, null: false)
+      add(:ap_enabled, :boolean, default: false, null: false)
+      add(:is_moderator, :boolean, default: false, null: false)
+      add(:is_admin, :boolean, default: false, null: false)
+      add(:show_role, :boolean, default: true, null: false)
+      add(:settings, :map, default: nil)
+      add(:magic_key, :text, default: nil)
+      add(:uri, :text, default: nil)
+      add(:hide_followers_count, :boolean, default: false, null: false)
+      add(:hide_follows_count, :boolean, default: false, null: false)
+      add(:hide_followers, :boolean, default: false, null: false)
+      add(:hide_follows, :boolean, default: false, null: false)
+      add(:hide_favorites, :boolean, default: true, null: false)
+      add(:unread_conversation_count, :integer, default: 0)
+      add(:pinned_activities, {:array, :text}, default: [])
+      add(:email_notifications, :map, default: %{"digest" => false})
+      add(:mascot, :map, default: nil)
+      add(:emoji, :map, default: fragment(@jsonb_array_default))
+      add(:pleroma_settings_store, :map, default: %{})
+      add(:fields, :map, default: fragment(@jsonb_array_default))
+      add(:raw_fields, :map, default: fragment(@jsonb_array_default))
+      add(:discoverable, :boolean, default: false, null: false)
+      add(:invisible, :boolean, default: false, null: false)
+      add(:notification_settings, :map, default: %{})
+      add(:skip_thread_containment, :boolean, default: false, null: false)
+    end
+
+    if direction() == :up do
+      for f <- @info_fields do
+        set_field = "update users set #{f} ="
+
+        # Coercion of null::jsonb to NULL
+        jsonb = "case when info->>'#{f}' IS NULL then null else info->'#{f}' end"
+
+        cond do
+          f in @jsonb_fields ->
+            execute("#{set_field} #{jsonb}")
+
+          f in @array_jsonb_fields ->
+            execute("#{set_field} coalesce(#{jsonb}, #{@jsonb_array_default})")
+
+          f in @int_fields ->
+            execute("#{set_field} (info->>'#{f}')::int")
+
+          f in @boolean_fields ->
+            execute("#{set_field} coalesce((info->>'#{f}')::boolean, false)")
+
+          f in @array_text_fields ->
+            execute("#{set_field} ARRAY(SELECT jsonb_array_elements_text(#{jsonb}))")
+
+          true ->
+            execute("#{set_field} info->>'#{f}'")
+        end
+      end
+
+      for index_name <- [
+            :users_deactivated_index,
+            :users_is_moderator_index,
+            :users_is_admin_index,
+            :users_subscribers_index
+          ] do
+        drop_if_exists(index(:users, [], name: index_name))
+      end
+    end
+
+    create_if_not_exists(index(:users, [:deactivated]))
+    create_if_not_exists(index(:users, [:is_moderator]))
+    create_if_not_exists(index(:users, [:is_admin]))
+    create_if_not_exists(index(:users, [:subscribers]))
+  end
+end
diff --git a/priv/repo/migrations/20191014181019_create_markers.exs b/priv/repo/migrations/20191014181019_create_markers.exs
new file mode 100644
index 000000000..c717831ba
--- /dev/null
+++ b/priv/repo/migrations/20191014181019_create_markers.exs
@@ -0,0 +1,15 @@
+defmodule Pleroma.Repo.Migrations.CreateMarkers do
+  use Ecto.Migration
+
+  def change do
+    create_if_not_exists table(:markers) do
+      add(:user_id, references(:users, type: :uuid, on_delete: :delete_all))
+      add(:timeline, :string, default: "", null: false)
+      add(:last_read_id, :string, default: "", null: false)
+      add(:lock_version, :integer, default: 0, null: false)
+      timestamps()
+    end
+
+    create_if_not_exists(unique_index(:markers, [:user_id, :timeline]))
+  end
+end
diff --git a/priv/repo/migrations/20191017225002_drop_websub_tables.exs b/priv/repo/migrations/20191017225002_drop_websub_tables.exs
index 9e483b2a1..4cf67a59c 100644
--- a/priv/repo/migrations/20191017225002_drop_websub_tables.exs
+++ b/priv/repo/migrations/20191017225002_drop_websub_tables.exs
@@ -1,8 +1,10 @@
 defmodule Pleroma.Repo.Migrations.DropWebsubTables do
   use Ecto.Migration
 
-  def change do
+  def up do
     drop_if_exists(table(:websub_client_subscriptions))
     drop_if_exists(table(:websub_server_subscriptions))
   end
+
+  def down, do: :noop
 end
diff --git a/priv/static/schemas/litepub-0.1.jsonld b/priv/static/schemas/litepub-0.1.jsonld
index 1cfcb7ec7..c8e69cab5 100644
--- a/priv/static/schemas/litepub-0.1.jsonld
+++ b/priv/static/schemas/litepub-0.1.jsonld
@@ -19,6 +19,7 @@
             "value": "schema:value",
             "sensitive": "as:sensitive",
             "litepub": "http://litepub.social/ns#",
+            "invisible": "litepub:invisible",
             "directMessage": "litepub:directMessage",
             "listMessage": {
                 "@id": "litepub:listMessage",
diff --git a/test/conversation/participation_test.exs b/test/conversation/participation_test.exs
index 64c350904..91867bf70 100644
--- a/test/conversation/participation_test.exs
+++ b/test/conversation/participation_test.exs
@@ -36,8 +36,8 @@ defmodule Pleroma.Conversation.ParticipationTest do
     [%{read: true}] = Participation.for_user(user)
     [%{read: false} = participation] = Participation.for_user(other_user)
 
-    assert User.get_cached_by_id(user.id).info.unread_conversation_count == 0
-    assert User.get_cached_by_id(other_user.id).info.unread_conversation_count == 1
+    assert User.get_cached_by_id(user.id).unread_conversation_count == 0
+    assert User.get_cached_by_id(other_user.id).unread_conversation_count == 1
 
     {:ok, _} =
       CommonAPI.post(other_user, %{
@@ -52,8 +52,8 @@ defmodule Pleroma.Conversation.ParticipationTest do
     [%{read: false}] = Participation.for_user(user)
     [%{read: true}] = Participation.for_user(other_user)
 
-    assert User.get_cached_by_id(user.id).info.unread_conversation_count == 1
-    assert User.get_cached_by_id(other_user.id).info.unread_conversation_count == 0
+    assert User.get_cached_by_id(user.id).unread_conversation_count == 1
+    assert User.get_cached_by_id(other_user.id).unread_conversation_count == 0
   end
 
   test "for a new conversation, it sets the recipents of the participation" do
diff --git a/test/daemons/digest_email_daemon_test.exs b/test/daemons/digest_email_daemon_test.exs
index 3168f3b9a..faf592d5f 100644
--- a/test/daemons/digest_email_daemon_test.exs
+++ b/test/daemons/digest_email_daemon_test.exs
@@ -20,7 +20,7 @@ defmodule Pleroma.DigestEmailDaemonTest do
       |> Timex.to_naive_datetime()
 
     user2 = insert(:user, last_digest_emailed_at: date)
-    User.switch_email_notifications(user2, "digest", true)
+    {:ok, _} = User.switch_email_notifications(user2, "digest", true)
     CommonAPI.post(user, %{"status" => "hey @#{user2.nickname}!"})
 
     DigestEmailDaemon.perform()
diff --git a/test/emails/user_email_test.exs b/test/emails/user_email_test.exs
index 963565f7c..9e145977e 100644
--- a/test/emails/user_email_test.exs
+++ b/test/emails/user_email_test.exs
@@ -36,7 +36,7 @@ defmodule Pleroma.Emails.UserEmailTest do
 
   test "build account confirmation email" do
     config = Pleroma.Config.get(:instance)
-    user = insert(:user, info: %Pleroma.User.Info{confirmation_token: "conf-token"})
+    user = insert(:user, confirmation_token: "conf-token")
     email = UserEmail.account_confirmation_email(user)
     assert email.from == {config[:name], config[:notify_email]}
     assert email.to == [{user.name, user.email}]
diff --git a/test/fixtures/tesla_mock/relay@mastdon.example.org.json b/test/fixtures/tesla_mock/relay@mastdon.example.org.json
new file mode 100644
index 000000000..c1fab7d3b
--- /dev/null
+++ b/test/fixtures/tesla_mock/relay@mastdon.example.org.json
@@ -0,0 +1,55 @@
+{
+  "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", {
+    "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+    "sensitive": "as:sensitive",
+    "movedTo": "as:movedTo",
+    "Hashtag": "as:Hashtag",
+    "ostatus": "http://ostatus.org#",
+    "atomUri": "ostatus:atomUri",
+    "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+    "conversation": "ostatus:conversation",
+    "toot": "http://joinmastodon.org/ns#",
+    "Emoji": "toot:Emoji"
+  }],
+  "id": "http://mastodon.example.org/users/admin",
+  "type": "Application",
+  "invisible": true,
+  "following": "http://mastodon.example.org/users/admin/following",
+  "followers": "http://mastodon.example.org/users/admin/followers",
+  "inbox": "http://mastodon.example.org/users/admin/inbox",
+  "outbox": "http://mastodon.example.org/users/admin/outbox",
+  "preferredUsername": "admin",
+  "name": null,
+  "summary": "\u003cp\u003e\u003c/p\u003e",
+  "url": "http://mastodon.example.org/@admin",
+  "manuallyApprovesFollowers": false,
+  "publicKey": {
+    "id": "http://mastodon.example.org/users/admin#main-key",
+    "owner": "http://mastodon.example.org/users/admin",
+    "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtc4Tir+3ADhSNF6VKrtW\nOU32T01w7V0yshmQei38YyiVwVvFu8XOP6ACchkdxbJ+C9mZud8qWaRJKVbFTMUG\nNX4+6Q+FobyuKrwN7CEwhDALZtaN2IPbaPd6uG1B7QhWorrY+yFa8f2TBM3BxnUy\nI4T+bMIZIEYG7KtljCBoQXuTQmGtuffO0UwJksidg2ffCF5Q+K//JfQagJ3UzrR+\nZXbKMJdAw4bCVJYs4Z5EhHYBwQWiXCyMGTd7BGlmMkY6Av7ZqHKC/owp3/0EWDNz\nNqF09Wcpr3y3e8nA10X40MJqp/wR+1xtxp+YGbq/Cj5hZGBG7etFOmIpVBrDOhry\nBwIDAQAB\n-----END PUBLIC KEY-----\n"
+  },
+  "attachment": [{
+      "type": "PropertyValue",
+      "name": "foo",
+      "value": "bar"
+    },
+    {
+      "type": "PropertyValue",
+      "name": "foo1",
+      "value": "bar1"
+    }
+  ],
+  "endpoints": {
+    "sharedInbox": "http://mastodon.example.org/inbox"
+  },
+  "icon": {
+    "type": "Image",
+    "mediaType": "image/jpeg",
+    "url": "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
+  },
+  "image": {
+    "type": "Image",
+    "mediaType": "image/png",
+    "url": "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
+  }
+}
diff --git a/test/formatter_test.exs b/test/formatter_test.exs
index 3bff51527..087bdbcc2 100644
--- a/test/formatter_test.exs
+++ b/test/formatter_test.exs
@@ -125,10 +125,10 @@ defmodule Pleroma.FormatterTest do
       gsimg = insert(:user, %{nickname: "gsimg"})
 
       archaeme =
-        insert(:user, %{
+        insert(:user,
           nickname: "archa_eme_",
-          info: %User.Info{source_data: %{"url" => "https://archeme/@archa_eme_"}}
-        })
+          source_data: %{"url" => "https://archeme/@archa_eme_"}
+        )
 
       archaeme_remote = insert(:user, %{nickname: "archaeme@archae.me"})
 
diff --git a/test/marker_test.exs b/test/marker_test.exs
new file mode 100644
index 000000000..04bd67fe6
--- /dev/null
+++ b/test/marker_test.exs
@@ -0,0 +1,51 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.MarkerTest do
+  use Pleroma.DataCase
+  alias Pleroma.Marker
+
+  import Pleroma.Factory
+
+  describe "get_markers/2" do
+    test "returns user markers" do
+      user = insert(:user)
+      marker = insert(:marker, user: user)
+      insert(:marker, timeline: "home", user: user)
+      assert Marker.get_markers(user, ["notifications"]) == [refresh_record(marker)]
+    end
+  end
+
+  describe "upsert/2" do
+    test "creates a marker" do
+      user = insert(:user)
+
+      {:ok, %{"notifications" => %Marker{} = marker}} =
+        Marker.upsert(
+          user,
+          %{"notifications" => %{"last_read_id" => "34"}}
+        )
+
+      assert marker.timeline == "notifications"
+      assert marker.last_read_id == "34"
+      assert marker.lock_version == 0
+    end
+
+    test "updates exist marker" do
+      user = insert(:user)
+      marker = insert(:marker, user: user, last_read_id: "8909")
+
+      {:ok, %{"notifications" => %Marker{}}} =
+        Marker.upsert(
+          user,
+          %{"notifications" => %{"last_read_id" => "9909"}}
+        )
+
+      marker = refresh_record(marker)
+      assert marker.timeline == "notifications"
+      assert marker.last_read_id == "9909"
+      assert marker.lock_version == 0
+    end
+  end
+end
diff --git a/test/moderation_log_test.exs b/test/moderation_log_test.exs
index 81c0fef12..4240f6a65 100644
--- a/test/moderation_log_test.exs
+++ b/test/moderation_log_test.exs
@@ -12,8 +12,8 @@ defmodule Pleroma.ModerationLogTest do
 
   describe "user moderation" do
     setup do
-      admin = insert(:user, info: %{is_admin: true})
-      moderator = insert(:user, info: %{is_moderator: true})
+      admin = insert(:user, is_admin: true)
+      moderator = insert(:user, is_moderator: true)
       subject1 = insert(:user)
       subject2 = insert(:user)
 
diff --git a/test/notification_test.exs b/test/notification_test.exs
index 54c0f9877..f8d429223 100644
--- a/test/notification_test.exs
+++ b/test/notification_test.exs
@@ -136,7 +136,7 @@ defmodule Pleroma.NotificationTest do
 
     test "it disables notifications from followers" do
       follower = insert(:user)
-      followed = insert(:user, info: %{notification_settings: %{"followers" => false}})
+      followed = insert(:user, notification_settings: %{"followers" => false})
       User.follow(follower, followed)
       {:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"})
       refute Notification.create_notification(activity, followed)
@@ -144,13 +144,13 @@ defmodule Pleroma.NotificationTest do
 
     test "it disables notifications from non-followers" do
       follower = insert(:user)
-      followed = insert(:user, info: %{notification_settings: %{"non_followers" => false}})
+      followed = insert(:user, notification_settings: %{"non_followers" => false})
       {:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"})
       refute Notification.create_notification(activity, followed)
     end
 
     test "it disables notifications from people the user follows" do
-      follower = insert(:user, info: %{notification_settings: %{"follows" => false}})
+      follower = insert(:user, notification_settings: %{"follows" => false})
       followed = insert(:user)
       User.follow(follower, followed)
       follower = Repo.get(User, follower.id)
@@ -159,7 +159,7 @@ defmodule Pleroma.NotificationTest do
     end
 
     test "it disables notifications from people the user does not follow" do
-      follower = insert(:user, info: %{notification_settings: %{"non_follows" => false}})
+      follower = insert(:user, notification_settings: %{"non_follows" => false})
       followed = insert(:user)
       {:ok, activity} = CommonAPI.post(followed, %{"status" => "hey @#{follower.nickname}"})
       refute Notification.create_notification(activity, follower)
@@ -683,7 +683,7 @@ defmodule Pleroma.NotificationTest do
       assert Notification.for_user(user) == []
     end
 
-    test "it returns notifications for muted user with notifications and with_muted parameter" do
+    test "it returns notifications from a muted user when with_muted is set" do
       user = insert(:user)
       muted = insert(:user)
       {:ok, user} = User.mute(user, muted)
@@ -693,27 +693,27 @@ defmodule Pleroma.NotificationTest do
       assert length(Notification.for_user(user, %{with_muted: true})) == 1
     end
 
-    test "it returns notifications for blocked user and with_muted parameter" do
+    test "it doesn't return notifications from a blocked user when with_muted is set" do
       user = insert(:user)
       blocked = insert(:user)
       {:ok, user} = User.block(user, blocked)
 
       {:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})
 
-      assert length(Notification.for_user(user, %{with_muted: true})) == 1
+      assert length(Notification.for_user(user, %{with_muted: true})) == 0
     end
 
-    test "it returns notificatitons for blocked domain and with_muted parameter" do
+    test "it doesn't return notifications from a domain-blocked user when with_muted is set" do
       user = insert(:user)
       blocked = insert(:user, ap_id: "http://some-domain.com")
       {:ok, user} = User.block_domain(user, "some-domain.com")
 
       {:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})
 
-      assert length(Notification.for_user(user, %{with_muted: true})) == 1
+      assert length(Notification.for_user(user, %{with_muted: true})) == 0
     end
 
-    test "it returns notifications for muted thread with_muted parameter" do
+    test "it returns notifications from muted threads when with_muted is set" do
       user = insert(:user)
       another_user = insert(:user)
 
diff --git a/test/plugs/admin_secret_authentication_plug_test.exs b/test/plugs/admin_secret_authentication_plug_test.exs
index e1d4b391f..c94a62c10 100644
--- a/test/plugs/admin_secret_authentication_plug_test.exs
+++ b/test/plugs/admin_secret_authentication_plug_test.exs
@@ -37,6 +37,6 @@ defmodule Pleroma.Plugs.AdminSecretAuthenticationPlugTest do
       %{conn | params: %{"admin_token" => "password123"}}
       |> AdminSecretAuthenticationPlug.call(%{})
 
-    assert conn.assigns[:user].info.is_admin
+    assert conn.assigns[:user].is_admin
   end
 end
diff --git a/test/plugs/user_enabled_plug_test.exs b/test/plugs/user_enabled_plug_test.exs
index c0fafcab1..996a7d77b 100644
--- a/test/plugs/user_enabled_plug_test.exs
+++ b/test/plugs/user_enabled_plug_test.exs
@@ -17,7 +17,7 @@ defmodule Pleroma.Plugs.UserEnabledPlugTest do
   end
 
   test "with a user that is deactivated, it removes that user", %{conn: conn} do
-    user = insert(:user, info: %{deactivated: true})
+    user = insert(:user, deactivated: true)
 
     conn =
       conn
diff --git a/test/plugs/user_is_admin_plug_test.exs b/test/plugs/user_is_admin_plug_test.exs
index 9e05fff18..136dcc54e 100644
--- a/test/plugs/user_is_admin_plug_test.exs
+++ b/test/plugs/user_is_admin_plug_test.exs
@@ -9,7 +9,7 @@ defmodule Pleroma.Plugs.UserIsAdminPlugTest do
   import Pleroma.Factory
 
   test "accepts a user that is admin" do
-    user = insert(:user, info: %{is_admin: true})
+    user = insert(:user, is_admin: true)
 
     conn =
       build_conn()
diff --git a/test/signature_test.exs b/test/signature_test.exs
index 6b168f2d9..15cf10fb6 100644
--- a/test/signature_test.exs
+++ b/test/signature_test.exs
@@ -42,7 +42,7 @@ defmodule Pleroma.SignatureTest do
     test "it returns key" do
       expected_result = {:ok, @rsa_public_key}
 
-      user = insert(:user, %{info: %{source_data: %{"publicKey" => @public_key}}})
+      user = insert(:user, source_data: %{"publicKey" => @public_key})
 
       assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == expected_result
     end
@@ -54,7 +54,7 @@ defmodule Pleroma.SignatureTest do
     end
 
     test "it returns error if public key is empty" do
-      user = insert(:user, %{info: %{source_data: %{"publicKey" => %{}}}})
+      user = insert(:user, source_data: %{"publicKey" => %{}})
 
       assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == {:error, :error}
     end
diff --git a/test/support/factory.ex b/test/support/factory.ex
index 4bd4c145a..e3f797f64 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -376,4 +376,13 @@ defmodule Pleroma.Factory do
         )
     }
   end
+
+  def marker_factory do
+    %Pleroma.Marker{
+      user: build(:user),
+      timeline: "notifications",
+      lock_version: 0,
+      last_read_id: "1"
+    }
+  end
 end
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index 7d65209fb..eba22c40b 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -348,6 +348,14 @@ defmodule HttpRequestMock do
      }}
   end
 
+  def get("http://mastodon.example.org/users/relay", _, _, Accept: "application/activity+json") do
+    {:ok,
+     %Tesla.Env{
+       status: 200,
+       body: File.read!("test/fixtures/tesla_mock/relay@mastdon.example.org.json")
+     }}
+  end
+
   def get("http://mastodon.example.org/users/gargron", _, _, Accept: "application/activity+json") do
     {:error, :nxdomain}
   end
diff --git a/test/tasks/count_statuses_test.exs b/test/tasks/count_statuses_test.exs
index 6035da3c3..bb5dc88f8 100644
--- a/test/tasks/count_statuses_test.exs
+++ b/test/tasks/count_statuses_test.exs
@@ -22,18 +22,18 @@ defmodule Mix.Tasks.Pleroma.CountStatusesTest do
     user = refresh_record(user)
     user2 = refresh_record(user2)
 
-    assert %{info: %{note_count: 2}} = user
-    assert %{info: %{note_count: 1}} = user2
+    assert %{note_count: 2} = user
+    assert %{note_count: 1} = user2
 
-    {:ok, user} = User.update_info(user, &User.Info.set_note_count(&1, 0))
-    {:ok, user2} = User.update_info(user2, &User.Info.set_note_count(&1, 0))
+    {:ok, user} = User.update_note_count(user, 0)
+    {:ok, user2} = User.update_note_count(user2, 0)
 
-    assert %{info: %{note_count: 0}} = user
-    assert %{info: %{note_count: 0}} = user2
+    assert %{note_count: 0} = user
+    assert %{note_count: 0} = user2
 
     assert capture_io(fn -> Mix.Tasks.Pleroma.CountStatuses.run([]) end) == "Done\n"
 
-    assert %{info: %{note_count: 2}} = refresh_record(user)
-    assert %{info: %{note_count: 1}} = refresh_record(user2)
+    assert %{note_count: 2} = refresh_record(user)
+    assert %{note_count: 1} = refresh_record(user2)
   end
 end
diff --git a/test/tasks/database_test.exs b/test/tasks/database_test.exs
index bf5ba7883..0c7883f33 100644
--- a/test/tasks/database_test.exs
+++ b/test/tasks/database_test.exs
@@ -72,26 +72,26 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do
   describe "running update_users_following_followers_counts" do
     test "following and followers count are updated" do
       [user, user2] = insert_pair(:user)
-      {:ok, %User{info: info} = user} = User.follow(user, user2)
+      {:ok, %User{} = user} = User.follow(user, user2)
 
       following = User.following(user)
 
       assert length(following) == 2
-      assert info.follower_count == 0
+      assert user.follower_count == 0
 
       {:ok, user} =
         user
-        |> User.change_info(&Ecto.Changeset.change(&1, %{follower_count: 3}))
+        |> Ecto.Changeset.change(%{follower_count: 3})
         |> Repo.update()
 
-      assert user.info.follower_count == 3
+      assert user.follower_count == 3
 
       assert :ok == Mix.Tasks.Pleroma.Database.run(["update_users_following_followers_counts"])
 
       user = User.get_by_id(user.id)
 
       assert length(User.following(user)) == 2
-      assert user.info.follower_count == 0
+      assert user.follower_count == 0
     end
   end
 
diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs
index c0e4ba85c..bfd0ccbc5 100644
--- a/test/tasks/user_test.exs
+++ b/test/tasks/user_test.exs
@@ -58,8 +58,8 @@ defmodule Mix.Tasks.Pleroma.UserTest do
       assert user.name == unsaved.name
       assert user.email == unsaved.email
       assert user.bio == unsaved.bio
-      assert user.info.is_moderator
-      assert user.info.is_admin
+      assert user.is_moderator
+      assert user.is_admin
     end
 
     test "user is not created" do
@@ -113,11 +113,11 @@ defmodule Mix.Tasks.Pleroma.UserTest do
       assert message =~ " deactivated"
 
       user = User.get_cached_by_nickname(user.nickname)
-      assert user.info.deactivated
+      assert user.deactivated
     end
 
     test "user is activated" do
-      user = insert(:user, info: %{deactivated: true})
+      user = insert(:user, deactivated: true)
 
       Mix.Tasks.Pleroma.User.run(["toggle_activated", user.nickname])
 
@@ -125,7 +125,7 @@ defmodule Mix.Tasks.Pleroma.UserTest do
       assert message =~ " activated"
 
       user = User.get_cached_by_nickname(user.nickname)
-      refute user.info.deactivated
+      refute user.deactivated
     end
 
     test "no user to toggle" do
@@ -156,7 +156,7 @@ defmodule Mix.Tasks.Pleroma.UserTest do
 
       user = User.get_cached_by_nickname(user.nickname)
       assert Enum.empty?(User.get_friends(user))
-      assert user.info.deactivated
+      assert user.deactivated
     end
 
     test "no user to unsubscribe" do
@@ -183,13 +183,13 @@ defmodule Mix.Tasks.Pleroma.UserTest do
       assert message =~ ~r/Admin status .* true/
 
       user = User.get_cached_by_nickname(user.nickname)
-      assert user.info.is_moderator
-      assert user.info.locked
-      assert user.info.is_admin
+      assert user.is_moderator
+      assert user.locked
+      assert user.is_admin
     end
 
     test "All statuses unset" do
-      user = insert(:user, info: %{is_moderator: true, locked: true, is_admin: true})
+      user = insert(:user, locked: true, is_moderator: true, is_admin: true)
 
       Mix.Tasks.Pleroma.User.run([
         "set",
@@ -209,9 +209,9 @@ defmodule Mix.Tasks.Pleroma.UserTest do
       assert message =~ ~r/Admin status .* false/
 
       user = User.get_cached_by_nickname(user.nickname)
-      refute user.info.is_moderator
-      refute user.info.locked
-      refute user.info.is_admin
+      refute user.is_moderator
+      refute user.locked
+      refute user.is_admin
     end
 
     test "no user to set status" do
@@ -359,28 +359,28 @@ defmodule Mix.Tasks.Pleroma.UserTest do
 
   describe "running toggle_confirmed" do
     test "user is confirmed" do
-      %{id: id, nickname: nickname} = insert(:user, info: %{confirmation_pending: false})
+      %{id: id, nickname: nickname} = insert(:user, confirmation_pending: false)
 
       assert :ok = Mix.Tasks.Pleroma.User.run(["toggle_confirmed", nickname])
       assert_received {:mix_shell, :info, [message]}
       assert message == "#{nickname} needs confirmation."
 
       user = Repo.get(User, id)
-      assert user.info.confirmation_pending
-      assert user.info.confirmation_token
+      assert user.confirmation_pending
+      assert user.confirmation_token
     end
 
     test "user is not confirmed" do
       %{id: id, nickname: nickname} =
-        insert(:user, info: %{confirmation_pending: true, confirmation_token: "some token"})
+        insert(:user, confirmation_pending: true, confirmation_token: "some token")
 
       assert :ok = Mix.Tasks.Pleroma.User.run(["toggle_confirmed", nickname])
       assert_received {:mix_shell, :info, [message]}
       assert message == "#{nickname} doesn't need confirmation."
 
       user = Repo.get(User, id)
-      refute user.info.confirmation_pending
-      refute user.info.confirmation_token
+      refute user.confirmation_pending
+      refute user.confirmation_token
     end
 
     test "it prints an error message when user is not exist" do
diff --git a/test/user_info_test.exs b/test/user_info_test.exs
deleted file mode 100644
index 2d795594e..000000000
--- a/test/user_info_test.exs
+++ /dev/null
@@ -1,24 +0,0 @@
-defmodule Pleroma.UserInfoTest do
-  alias Pleroma.Repo
-  alias Pleroma.User.Info
-
-  use Pleroma.DataCase
-
-  import Pleroma.Factory
-
-  describe "update_email_notifications/2" do
-    setup do
-      user = insert(:user, %{info: %{email_notifications: %{"digest" => true}}})
-
-      {:ok, user: user}
-    end
-
-    test "Notifications are updated", %{user: user} do
-      true = user.info.email_notifications["digest"]
-      changeset = Info.update_email_notifications(user.info, %{"digest" => false})
-      assert changeset.valid?
-      {:ok, result} = Ecto.Changeset.apply_action(changeset, :insert)
-      assert result.email_notifications["digest"] == false
-    end
-  end
-end
diff --git a/test/user_test.exs b/test/user_test.exs
index f4224ad0a..188295a86 100644
--- a/test/user_test.exs
+++ b/test/user_test.exs
@@ -68,7 +68,7 @@ defmodule Pleroma.UserTest do
 
   test "returns all pending follow requests" do
     unlocked = insert(:user)
-    locked = insert(:user, %{info: %{locked: true}})
+    locked = insert(:user, locked: true)
     follower = insert(:user)
 
     CommonAPI.follow(follower, unlocked)
@@ -81,7 +81,7 @@ defmodule Pleroma.UserTest do
   end
 
   test "doesn't return already accepted or duplicate follow requests" do
-    locked = insert(:user, %{info: %{locked: true}})
+    locked = insert(:user, locked: true)
     pending_follower = insert(:user)
     accepted_follower = insert(:user)
 
@@ -94,7 +94,7 @@ defmodule Pleroma.UserTest do
   end
 
   test "clears follow requests when requester is blocked" do
-    followed = insert(:user, %{info: %{locked: true}})
+    followed = insert(:user, locked: true)
     follower = insert(:user)
 
     CommonAPI.follow(follower, followed)
@@ -150,14 +150,14 @@ defmodule Pleroma.UserTest do
     user = User.get_cached_by_id(user.id)
 
     followed = User.get_cached_by_ap_id(followed.ap_id)
-    assert followed.info.follower_count == 1
+    assert followed.follower_count == 1
 
     assert User.ap_followers(followed) in User.following(user)
   end
 
   test "can't follow a deactivated users" do
     user = insert(:user)
-    followed = insert(:user, info: %{deactivated: true})
+    followed = insert(:user, %{deactivated: true})
 
     {:error, _} = User.follow(user, followed)
   end
@@ -181,8 +181,8 @@ defmodule Pleroma.UserTest do
   end
 
   test "local users do not automatically follow local locked accounts" do
-    follower = insert(:user, info: %{locked: true})
-    followed = insert(:user, info: %{locked: true})
+    follower = insert(:user, locked: true)
+    followed = insert(:user, locked: true)
 
     {:ok, follower} = User.maybe_direct_follow(follower, followed)
 
@@ -381,8 +381,8 @@ defmodule Pleroma.UserTest do
 
       {:ok, user} = Repo.insert(changeset)
 
-      assert user.info.confirmation_pending
-      assert user.info.confirmation_token
+      assert user.confirmation_pending
+      assert user.confirmation_token
     end
 
     test "it creates confirmed user if :confirmed option is given" do
@@ -391,8 +391,8 @@ defmodule Pleroma.UserTest do
 
       {:ok, user} = Repo.insert(changeset)
 
-      refute user.info.confirmation_pending
-      refute user.info.confirmation_token
+      refute user.confirmation_pending
+      refute user.confirmation_token
     end
   end
 
@@ -481,7 +481,8 @@ defmodule Pleroma.UserTest do
       assert orig_user.last_refreshed_at == a_week_ago
 
       {:ok, user} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin")
-      assert user.info.source_data["endpoints"]
+
+      assert user.source_data["endpoints"]
 
       refute user.last_refreshed_at == orig_user.last_refreshed_at
     end
@@ -587,63 +588,63 @@ defmodule Pleroma.UserTest do
   end
 
   describe "updating note and follower count" do
-    test "it sets the info->note_count property" do
+    test "it sets the note_count property" do
       note = insert(:note)
 
       user = User.get_cached_by_ap_id(note.data["actor"])
 
-      assert user.info.note_count == 0
+      assert user.note_count == 0
 
       {:ok, user} = User.update_note_count(user)
 
-      assert user.info.note_count == 1
+      assert user.note_count == 1
     end
 
-    test "it increases the info->note_count property" do
+    test "it increases the note_count property" do
       note = insert(:note)
       user = User.get_cached_by_ap_id(note.data["actor"])
 
-      assert user.info.note_count == 0
+      assert user.note_count == 0
 
       {:ok, user} = User.increase_note_count(user)
 
-      assert user.info.note_count == 1
+      assert user.note_count == 1
 
       {:ok, user} = User.increase_note_count(user)
 
-      assert user.info.note_count == 2
+      assert user.note_count == 2
     end
 
-    test "it decreases the info->note_count property" do
+    test "it decreases the note_count property" do
       note = insert(:note)
       user = User.get_cached_by_ap_id(note.data["actor"])
 
-      assert user.info.note_count == 0
+      assert user.note_count == 0
 
       {:ok, user} = User.increase_note_count(user)
 
-      assert user.info.note_count == 1
+      assert user.note_count == 1
 
       {:ok, user} = User.decrease_note_count(user)
 
-      assert user.info.note_count == 0
+      assert user.note_count == 0
 
       {:ok, user} = User.decrease_note_count(user)
 
-      assert user.info.note_count == 0
+      assert user.note_count == 0
     end
 
-    test "it sets the info->follower_count property" do
+    test "it sets the follower_count property" do
       user = insert(:user)
       follower = insert(:user)
 
       User.follow(follower, user)
 
-      assert user.info.follower_count == 0
+      assert user.follower_count == 0
 
       {:ok, user} = User.update_follower_count(user)
 
-      assert user.info.follower_count == 1
+      assert user.follower_count == 1
     end
   end
 
@@ -903,14 +904,14 @@ defmodule Pleroma.UserTest do
   describe ".deactivate" do
     test "can de-activate then re-activate a user" do
       user = insert(:user)
-      assert false == user.info.deactivated
+      assert false == user.deactivated
       {:ok, user} = User.deactivate(user)
-      assert true == user.info.deactivated
+      assert true == user.deactivated
       {:ok, user} = User.deactivate(user, false)
-      assert false == user.info.deactivated
+      assert false == user.deactivated
     end
 
-    test "hide a user from followers " do
+    test "hide a user from followers" do
       user = insert(:user)
       user2 = insert(:user)
 
@@ -990,7 +991,7 @@ defmodule Pleroma.UserTest do
     end
 
     test "it deletes deactivated user" do
-      {:ok, user} = insert(:user, info: %{deactivated: true}) |> User.set_cache()
+      {:ok, user} = insert(:user, deactivated: true) |> User.set_cache()
 
       {:ok, job} = User.delete(user)
       {:ok, _user} = ObanHelpers.perform(job)
@@ -1081,11 +1082,9 @@ defmodule Pleroma.UserTest do
         ap_id: user.ap_id,
         name: user.name,
         nickname: user.nickname,
-        info: %{
-          fields: [
-            %{"name" => "myfield", "value" => String.duplicate("h", current_max_length + 1)}
-          ]
-        }
+        fields: [
+          %{"name" => "myfield", "value" => String.duplicate("h", current_max_length + 1)}
+        ]
       }
 
       assert {:ok, %User{}} = User.insert_or_update_user(data)
@@ -1129,7 +1128,7 @@ defmodule Pleroma.UserTest do
     end
 
     test "html_filter_policy returns TwitterText scrubber when rich-text is disabled" do
-      user = insert(:user, %{info: %{no_rich_text: true}})
+      user = insert(:user, no_rich_text: true)
 
       assert Pleroma.HTML.Scrubber.TwitterText == User.html_filter_policy(user)
     end
@@ -1166,8 +1165,8 @@ defmodule Pleroma.UserTest do
   test "auth_active?/1 works correctly" do
     Pleroma.Config.put([:instance, :account_activation_required], true)
 
-    local_user = insert(:user, local: true, info: %{confirmation_pending: true})
-    confirmed_user = insert(:user, local: true, info: %{confirmation_pending: false})
+    local_user = insert(:user, local: true, confirmation_pending: true)
+    confirmed_user = insert(:user, local: true, confirmation_pending: false)
     remote_user = insert(:user, local: false)
 
     refute User.auth_active?(local_user)
@@ -1184,25 +1183,39 @@ defmodule Pleroma.UserTest do
 
     test "returns false for remote users" do
       user = insert(:user, local: false)
-      remote_admin_user = insert(:user, local: false, info: %{is_admin: true})
+      remote_admin_user = insert(:user, local: false, is_admin: true)
 
       refute User.superuser?(user)
       refute User.superuser?(remote_admin_user)
     end
 
     test "returns true for local moderators" do
-      user = insert(:user, local: true, info: %{is_moderator: true})
+      user = insert(:user, local: true, is_moderator: true)
 
       assert User.superuser?(user)
     end
 
     test "returns true for local admins" do
-      user = insert(:user, local: true, info: %{is_admin: true})
+      user = insert(:user, local: true, is_admin: true)
 
       assert User.superuser?(user)
     end
   end
 
+  describe "invisible?/1" do
+    test "returns true for an invisible user" do
+      user = insert(:user, local: true, invisible: true)
+
+      assert User.invisible?(user)
+    end
+
+    test "returns false for a non-invisible user" do
+      user = insert(:user, local: true)
+
+      refute User.invisible?(user)
+    end
+  end
+
   describe "visible_for?/2" do
     test "returns true when the account is itself" do
       user = insert(:user, local: true)
@@ -1213,14 +1226,14 @@ defmodule Pleroma.UserTest do
     test "returns false when the account is unauthenticated and auth is required" do
       Pleroma.Config.put([:instance, :account_activation_required], true)
 
-      user = insert(:user, local: true, info: %{confirmation_pending: true})
+      user = insert(:user, local: true, confirmation_pending: true)
       other_user = insert(:user, local: true)
 
       refute User.visible_for?(user, other_user)
     end
 
     test "returns true when the account is unauthenticated and auth is not required" do
-      user = insert(:user, local: true, info: %{confirmation_pending: true})
+      user = insert(:user, local: true, confirmation_pending: true)
       other_user = insert(:user, local: true)
 
       assert User.visible_for?(user, other_user)
@@ -1229,8 +1242,8 @@ defmodule Pleroma.UserTest do
     test "returns true when the account is unauthenticated and being viewed by a privileged account (auth required)" do
       Pleroma.Config.put([:instance, :account_activation_required], true)
 
-      user = insert(:user, local: true, info: %{confirmation_pending: true})
-      other_user = insert(:user, local: true, info: %{is_admin: true})
+      user = insert(:user, local: true, confirmation_pending: true)
+      other_user = insert(:user, local: true, is_admin: true)
 
       assert User.visible_for?(user, other_user)
     end
@@ -1296,7 +1309,7 @@ defmodule Pleroma.UserTest do
 
       users =
         Enum.map(1..total, fn _ ->
-          insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
+          insert(:user, last_digest_emailed_at: days_ago(20), deactivated: false)
         end)
 
       inactive_users_ids =
@@ -1314,7 +1327,7 @@ defmodule Pleroma.UserTest do
 
       users =
         Enum.map(1..total, fn _ ->
-          insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
+          insert(:user, last_digest_emailed_at: days_ago(20), deactivated: false)
         end)
 
       {inactive, active} = Enum.split(users, trunc(total / 2))
@@ -1347,7 +1360,7 @@ defmodule Pleroma.UserTest do
 
       users =
         Enum.map(1..total, fn _ ->
-          insert(:user, last_digest_emailed_at: days_ago(20), info: %{deactivated: false})
+          insert(:user, last_digest_emailed_at: days_ago(20), deactivated: false)
         end)
 
       [sender | recipients] = users
@@ -1387,19 +1400,19 @@ defmodule Pleroma.UserTest do
 
   describe "toggle_confirmation/1" do
     test "if user is confirmed" do
-      user = insert(:user, info: %{confirmation_pending: false})
+      user = insert(:user, confirmation_pending: false)
       {:ok, user} = User.toggle_confirmation(user)
 
-      assert user.info.confirmation_pending
-      assert user.info.confirmation_token
+      assert user.confirmation_pending
+      assert user.confirmation_token
     end
 
     test "if user is unconfirmed" do
-      user = insert(:user, info: %{confirmation_pending: true, confirmation_token: "some token"})
+      user = insert(:user, confirmation_pending: true, confirmation_token: "some token")
       {:ok, user} = User.toggle_confirmation(user)
 
-      refute user.info.confirmation_pending
-      refute user.info.confirmation_token
+      refute user.confirmation_pending
+      refute user.confirmation_token
     end
   end
 
@@ -1435,7 +1448,7 @@ defmodule Pleroma.UserTest do
       user1 = insert(:user, local: false, ap_id: "http://localhost:4001/users/masto_closed")
       user2 = insert(:user, local: false, ap_id: "http://localhost:4001/users/fuser2")
       insert(:user, local: true)
-      insert(:user, local: false, info: %{deactivated: true})
+      insert(:user, local: false, deactivated: true)
       {:ok, user1: user1, user2: user2}
     end
 
@@ -1554,7 +1567,7 @@ defmodule Pleroma.UserTest do
           local: false,
           follower_address: "http://localhost:4001/users/masto_closed/followers",
           following_address: "http://localhost:4001/users/masto_closed/following",
-          info: %{ap_enabled: true}
+          ap_enabled: true
         )
 
       assert User.user_info(other_user).following_count == 0
@@ -1577,7 +1590,7 @@ defmodule Pleroma.UserTest do
           local: false,
           follower_address: "http://localhost:4001/users/masto_closed/followers",
           following_address: "http://localhost:4001/users/masto_closed/following",
-          info: %{ap_enabled: true}
+          ap_enabled: true
         )
 
       assert User.user_info(other_user).following_count == 0
@@ -1600,7 +1613,7 @@ defmodule Pleroma.UserTest do
           local: false,
           follower_address: "http://localhost:4001/users/masto_closed/followers",
           following_address: "http://localhost:4001/users/masto_closed/following",
-          info: %{ap_enabled: true}
+          ap_enabled: true
         )
 
       assert User.user_info(other_user).following_count == 0
@@ -1640,41 +1653,6 @@ defmodule Pleroma.UserTest do
     end
   end
 
-  describe "set_password_reset_pending/2" do
-    setup do
-      [user: insert(:user)]
-    end
-
-    test "sets password_reset_pending to true", %{user: user} do
-      %{password_reset_pending: password_reset_pending} = user.info
-
-      refute password_reset_pending
-
-      {:ok, %{info: %{password_reset_pending: password_reset_pending}}} =
-        User.force_password_reset(user)
-
-      assert password_reset_pending
-    end
-  end
-
-  test "change_info/2" do
-    user = insert(:user)
-    assert user.info.hide_follows == false
-
-    changeset = User.change_info(user, &User.Info.profile_update(&1, %{hide_follows: true}))
-    assert changeset.changes.info.changes.hide_follows == true
-  end
-
-  test "update_info/2" do
-    user = insert(:user)
-    assert user.info.hide_follows == false
-
-    assert {:ok, _} = User.update_info(user, &User.Info.profile_update(&1, %{hide_follows: true}))
-
-    assert %{info: %{hide_follows: true}} = Repo.get(User, user.id)
-    assert {:ok, %{info: %{hide_follows: true}}} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
-  end
-
   describe "get_cached_by_nickname_or_id" do
     setup do
       limit_to_local_content = Pleroma.Config.get([:instance, :limit_to_local_content])
@@ -1731,4 +1709,18 @@ defmodule Pleroma.UserTest do
       assert %User{} = User.get_cached_by_nickname_or_id(local_user.nickname)
     end
   end
+
+  describe "update_email_notifications/2" do
+    setup do
+      user = insert(:user, email_notifications: %{"digest" => true})
+
+      {:ok, user: user}
+    end
+
+    test "Notifications are updated", %{user: user} do
+      true = user.email_notifications["digest"]
+      assert {:ok, result} = User.update_email_notifications(user, %{"digest" => false})
+      assert result.email_notifications["digest"] == false
+    end
+  end
 end
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs
index 6a3e48b5e..a5414c521 100644
--- a/test/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/web/activity_pub/activity_pub_controller_test.exs
@@ -354,6 +354,87 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
       assert Activity.get_by_ap_id(data["id"])
     end
 
+    test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
+      user = insert(:user)
+
+      data =
+        Map.put(data, "to", user.ap_id)
+        |> Map.delete("cc")
+
+      conn =
+        conn
+        |> assign(:valid_signature, true)
+        |> put_req_header("content-type", "application/activity+json")
+        |> post("/users/#{user.nickname}/inbox", data)
+
+      assert "ok" == json_response(conn, 200)
+      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+      assert Activity.get_by_ap_id(data["id"])
+    end
+
+    test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
+      user = insert(:user)
+
+      data =
+        Map.put(data, "cc", user.ap_id)
+        |> Map.delete("to")
+
+      conn =
+        conn
+        |> assign(:valid_signature, true)
+        |> put_req_header("content-type", "application/activity+json")
+        |> post("/users/#{user.nickname}/inbox", data)
+
+      assert "ok" == json_response(conn, 200)
+      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+      %Activity{} = activity = Activity.get_by_ap_id(data["id"])
+      assert user.ap_id in activity.recipients
+    end
+
+    test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
+      user = insert(:user)
+
+      data =
+        Map.put(data, "bcc", user.ap_id)
+        |> Map.delete("to")
+        |> Map.delete("cc")
+
+      conn =
+        conn
+        |> assign(:valid_signature, true)
+        |> put_req_header("content-type", "application/activity+json")
+        |> post("/users/#{user.nickname}/inbox", data)
+
+      assert "ok" == json_response(conn, 200)
+      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+      assert Activity.get_by_ap_id(data["id"])
+    end
+
+    test "it accepts announces with to as string instead of array", %{conn: conn} do
+      user = insert(:user)
+
+      data = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "actor" => "http://mastodon.example.org/users/admin",
+        "id" => "http://mastodon.example.org/users/admin/statuses/19512778738411822/activity",
+        "object" => "https://mastodon.social/users/emelie/statuses/101849165031453009",
+        "to" => "https://www.w3.org/ns/activitystreams#Public",
+        "cc" => [user.ap_id],
+        "type" => "Announce"
+      }
+
+      conn =
+        conn
+        |> assign(:valid_signature, true)
+        |> put_req_header("content-type", "application/activity+json")
+        |> post("/users/#{user.nickname}/inbox", data)
+
+      assert "ok" == json_response(conn, 200)
+      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
+      %Activity{} = activity = Activity.get_by_ap_id(data["id"])
+      assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
+    end
+
     test "it accepts messages from actors that are followed by the user", %{
       conn: conn,
       data: data
@@ -683,7 +764,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
 
     test "it returns returns a uri if the user has 'hide_followers' set", %{conn: conn} do
       user = insert(:user)
-      user_two = insert(:user, %{info: %{hide_followers: true}})
+      user_two = insert(:user, hide_followers: true)
       User.follow(user, user_two)
 
       result =
@@ -696,7 +777,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
 
     test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is not authenticated",
          %{conn: conn} do
-      user = insert(:user, %{info: %{hide_followers: true}})
+      user = insert(:user, hide_followers: true)
 
       result =
         conn
@@ -708,7 +789,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
 
     test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
          %{conn: conn} do
-      user = insert(:user, %{info: %{hide_followers: true}})
+      user = insert(:user, hide_followers: true)
       other_user = insert(:user)
       {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
 
@@ -764,7 +845,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
     end
 
     test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
-      user = insert(:user, %{info: %{hide_follows: true}})
+      user = insert(:user, hide_follows: true)
       user_two = insert(:user)
       User.follow(user, user_two)
 
@@ -778,7 +859,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
 
     test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is not authenticated",
          %{conn: conn} do
-      user = insert(:user, %{info: %{hide_follows: true}})
+      user = insert(:user, hide_follows: true)
 
       result =
         conn
@@ -790,7 +871,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
 
     test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
          %{conn: conn} do
-      user = insert(:user, %{info: %{hide_follows: true}})
+      user = insert(:user, hide_follows: true)
       other_user = insert(:user)
       {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
 
diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs
index cb447ebf0..0e5eb0c50 100644
--- a/test/web/activity_pub/activity_pub_test.exs
+++ b/test/web/activity_pub/activity_pub_test.exs
@@ -174,11 +174,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
       assert user.ap_id == user_id
       assert user.nickname == "admin@mastodon.example.org"
-      assert user.info.source_data
-      assert user.info.ap_enabled
+      assert user.source_data
+      assert user.ap_enabled
       assert user.follower_address == "http://mastodon.example.org/users/admin/followers"
     end
 
+    test "it returns a user that is invisible" do
+      user_id = "http://mastodon.example.org/users/relay"
+      {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
+      assert User.invisible?(user)
+    end
+
     test "it fetches the appropriate tag-restricted posts" do
       user = insert(:user)
 
@@ -360,7 +366,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       assert activity.actor == user.ap_id
 
       user = User.get_cached_by_id(user.id)
-      assert user.info.note_count == 0
+      assert user.note_count == 0
     end
 
     test "can be fetched into a timeline" do
@@ -423,7 +429,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
         })
 
       user = User.get_cached_by_id(user.id)
-      assert user.info.note_count == 2
+      assert user.note_count == 2
     end
 
     test "increases replies count" do
@@ -1090,7 +1096,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     end
 
     test "decrements user note count only for public activities" do
-      user = insert(:user, info: %{note_count: 10})
+      user = insert(:user, note_count: 10)
 
       {:ok, a1} =
         CommonAPI.post(User.get_cached_by_id(user.id), %{
@@ -1122,7 +1128,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       {:ok, _} = Object.normalize(a4) |> ActivityPub.delete()
 
       user = User.get_cached_by_id(user.id)
-      assert user.info.note_count == 10
+      assert user.note_count == 10
     end
 
     test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do
@@ -1386,9 +1392,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
           following_address: "http://localhost:4001/users/masto_closed/following"
         )
 
-      {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
-      assert info.hide_followers == true
-      assert info.hide_follows == false
+      {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
+      assert follow_info.hide_followers == true
+      assert follow_info.hide_follows == false
     end
 
     test "detects hidden follows" do
@@ -1409,9 +1415,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
           following_address: "http://localhost:4001/users/masto_closed/following"
         )
 
-      {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
-      assert info.hide_followers == false
-      assert info.hide_follows == true
+      {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
+      assert follow_info.hide_followers == false
+      assert follow_info.hide_follows == true
     end
   end
 end
diff --git a/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs b/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs
index 03dc299ec..b524fdd23 100644
--- a/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs
+++ b/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs
@@ -35,7 +35,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do
     test "it allows posts without links" do
       user = insert(:user)
 
-      assert user.info.note_count == 0
+      assert user.note_count == 0
 
       message =
         @linkless_message
@@ -47,7 +47,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do
     test "it disallows posts with links" do
       user = insert(:user)
 
-      assert user.info.note_count == 0
+      assert user.note_count == 0
 
       message =
         @linkful_message
@@ -59,9 +59,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do
 
   describe "with old user" do
     test "it allows posts without links" do
-      user = insert(:user, info: %{note_count: 1})
+      user = insert(:user, note_count: 1)
 
-      assert user.info.note_count == 1
+      assert user.note_count == 1
 
       message =
         @linkless_message
@@ -71,9 +71,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do
     end
 
     test "it allows posts with links" do
-      user = insert(:user, info: %{note_count: 1})
+      user = insert(:user, note_count: 1)
 
-      assert user.info.note_count == 1
+      assert user.note_count == 1
 
       message =
         @linkful_message
@@ -85,9 +85,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do
 
   describe "with followed new user" do
     test "it allows posts without links" do
-      user = insert(:user, info: %{follower_count: 1})
+      user = insert(:user, follower_count: 1)
 
-      assert user.info.follower_count == 1
+      assert user.follower_count == 1
 
       message =
         @linkless_message
@@ -97,9 +97,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do
     end
 
     test "it allows posts with links" do
-      user = insert(:user, info: %{follower_count: 1})
+      user = insert(:user, follower_count: 1)
 
-      assert user.info.follower_count == 1
+      assert user.follower_count == 1
 
       message =
         @linkful_message
@@ -133,7 +133,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do
 
   describe "with contentless-objects" do
     test "it does not reject them or error out" do
-      user = insert(:user, info: %{note_count: 1})
+      user = insert(:user, note_count: 1)
 
       message =
         @response_message
diff --git a/test/web/activity_pub/publisher_test.exs b/test/web/activity_pub/publisher_test.exs
index df03b4008..e885e5a5a 100644
--- a/test/web/activity_pub/publisher_test.exs
+++ b/test/web/activity_pub/publisher_test.exs
@@ -27,7 +27,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
     test "it returns sharedInbox for messages involving as:Public in to" do
       user =
         insert(:user, %{
-          info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}}
+          source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}
         })
 
       activity = %Activity{
@@ -40,7 +40,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
     test "it returns sharedInbox for messages involving as:Public in cc" do
       user =
         insert(:user, %{
-          info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}}
+          source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}
         })
 
       activity = %Activity{
@@ -53,7 +53,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
     test "it returns sharedInbox for messages involving multiple recipients in to" do
       user =
         insert(:user, %{
-          info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}}
+          source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}
         })
 
       user_two = insert(:user)
@@ -69,7 +69,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
     test "it returns sharedInbox for messages involving multiple recipients in cc" do
       user =
         insert(:user, %{
-          info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}}
+          source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}
         })
 
       user_two = insert(:user)
@@ -84,14 +84,12 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
 
     test "it returns sharedInbox for messages involving multiple recipients in total" do
       user =
-        insert(:user, %{
-          info: %{
-            source_data: %{
-              "inbox" => "http://example.com/personal-inbox",
-              "endpoints" => %{"sharedInbox" => "http://example.com/inbox"}
-            }
+        insert(:user,
+          source_data: %{
+            "inbox" => "http://example.com/personal-inbox",
+            "endpoints" => %{"sharedInbox" => "http://example.com/inbox"}
           }
-        })
+        )
 
       user_two = insert(:user)
 
@@ -104,14 +102,12 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
 
     test "it returns inbox for messages involving single recipients in total" do
       user =
-        insert(:user, %{
-          info: %{
-            source_data: %{
-              "inbox" => "http://example.com/personal-inbox",
-              "endpoints" => %{"sharedInbox" => "http://example.com/inbox"}
-            }
+        insert(:user,
+          source_data: %{
+            "inbox" => "http://example.com/personal-inbox",
+            "endpoints" => %{"sharedInbox" => "http://example.com/inbox"}
           }
-        })
+        )
 
       activity = %Activity{
         data: %{"to" => [user.ap_id], "cc" => []}
@@ -241,10 +237,8 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
       follower =
         insert(:user,
           local: false,
-          info: %{
-            ap_enabled: true,
-            source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"}
-          }
+          source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"},
+          ap_enabled: true
         )
 
       actor = insert(:user, follower_address: follower.ap_id)
@@ -278,19 +272,15 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
       fetcher =
         insert(:user,
           local: false,
-          info: %{
-            ap_enabled: true,
-            source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"}
-          }
+          source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"},
+          ap_enabled: true
         )
 
       another_fetcher =
         insert(:user,
           local: false,
-          info: %{
-            ap_enabled: true,
-            source_data: %{"inbox" => "https://domain2.com/users/nick1/inbox"}
-          }
+          source_data: %{"inbox" => "https://domain2.com/users/nick1/inbox"},
+          ap_enabled: true
         )
 
       actor = insert(:user)
diff --git a/test/web/activity_pub/relay_test.exs b/test/web/activity_pub/relay_test.exs
index 19c73541e..98dc78f46 100644
--- a/test/web/activity_pub/relay_test.exs
+++ b/test/web/activity_pub/relay_test.exs
@@ -20,6 +20,11 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do
     assert user.ap_id == "#{Pleroma.Web.Endpoint.url()}/relay"
   end
 
+  test "relay actor is invisible" do
+    user = Relay.get_actor()
+    assert User.invisible?(user)
+  end
+
   describe "follow/1" do
     test "returns errors when user not found" do
       assert capture_log(fn ->
diff --git a/test/web/activity_pub/transmogrifier/follow_handling_test.exs b/test/web/activity_pub/transmogrifier/follow_handling_test.exs
index 99ab573c5..75cfbea2e 100644
--- a/test/web/activity_pub/transmogrifier/follow_handling_test.exs
+++ b/test/web/activity_pub/transmogrifier/follow_handling_test.exs
@@ -58,7 +58,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do
     end
 
     test "with locked accounts, it does not create a follow or an accept" do
-      user = insert(:user, info: %{locked: true})
+      user = insert(:user, locked: true)
 
       data =
         File.read!("test/fixtures/mastodon-follow-activity.json")
diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs
index e648ff95a..a4a3cf42e 100644
--- a/test/web/activity_pub/transmogrifier_test.exs
+++ b/test/web/activity_pub/transmogrifier_test.exs
@@ -145,7 +145,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
 
       user = User.get_cached_by_ap_id(object_data["actor"])
 
-      assert user.info.note_count == 1
+      assert user.note_count == 1
     end
 
     test "it works for incoming notices with hashtags" do
@@ -582,7 +582,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
                }
              ]
 
-      assert user.info.banner["url"] == [
+      assert user.banner["url"] == [
                %{
                  "href" =>
                    "https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
@@ -601,7 +601,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
 
       user = User.get_cached_by_ap_id(activity.actor)
 
-      assert User.Info.fields(user.info) == [
+      assert User.fields(user) == [
                %{"name" => "foo", "value" => "bar"},
                %{"name" => "foo1", "value" => "bar1"}
              ]
@@ -622,7 +622,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
 
       user = User.get_cached_by_ap_id(user.ap_id)
 
-      assert User.Info.fields(user.info) == [
+      assert User.fields(user) == [
                %{"name" => "foo", "value" => "updated"},
                %{"name" => "foo1", "value" => "updated"}
              ]
@@ -640,7 +640,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
 
       user = User.get_cached_by_ap_id(user.ap_id)
 
-      assert User.Info.fields(user.info) == [
+      assert User.fields(user) == [
                %{"name" => "foo", "value" => "updated"},
                %{"name" => "foo1", "value" => "updated"}
              ]
@@ -651,7 +651,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
 
       user = User.get_cached_by_ap_id(user.ap_id)
 
-      assert User.Info.fields(user.info) == []
+      assert User.fields(user) == []
     end
 
     test "it works for incoming update activities which lock the account" do
@@ -674,7 +674,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
 
       user = User.get_cached_by_ap_id(data["actor"])
-      assert user.info.locked == true
+      assert user.locked == true
     end
 
     test "it works for incoming deletes" do
@@ -915,7 +915,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
 
     test "it works for incoming accepts which were orphaned" do
       follower = insert(:user)
-      followed = insert(:user, %{info: %User.Info{locked: true}})
+      followed = insert(:user, locked: true)
 
       {:ok, follow_activity} = ActivityPub.follow(follower, followed)
 
@@ -937,7 +937,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
 
     test "it works for incoming accepts which are referenced by IRI only" do
       follower = insert(:user)
-      followed = insert(:user, %{info: %User.Info{locked: true}})
+      followed = insert(:user, locked: true)
 
       {:ok, follow_activity} = ActivityPub.follow(follower, followed)
 
@@ -957,7 +957,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
 
     test "it fails for incoming accepts which cannot be correlated" do
       follower = insert(:user)
-      followed = insert(:user, %{info: %User.Info{locked: true}})
+      followed = insert(:user, locked: true)
 
       accept_data =
         File.read!("test/fixtures/mastodon-accept-activity.json")
@@ -976,7 +976,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
 
     test "it fails for incoming rejects which cannot be correlated" do
       follower = insert(:user)
-      followed = insert(:user, %{info: %User.Info{locked: true}})
+      followed = insert(:user, locked: true)
 
       accept_data =
         File.read!("test/fixtures/mastodon-reject-activity.json")
@@ -995,7 +995,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
 
     test "it works for incoming rejects which are orphaned" do
       follower = insert(:user)
-      followed = insert(:user, %{info: %User.Info{locked: true}})
+      followed = insert(:user, locked: true)
 
       {:ok, follower} = User.follow(follower, followed)
       {:ok, _follow_activity} = ActivityPub.follow(follower, followed)
@@ -1021,7 +1021,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
 
     test "it works for incoming rejects which are referenced by IRI only" do
       follower = insert(:user)
-      followed = insert(:user, %{info: %User.Info{locked: true}})
+      followed = insert(:user, locked: true)
 
       {:ok, follower} = User.follow(follower, followed)
       {:ok, follow_activity} = ActivityPub.follow(follower, followed)
@@ -1106,6 +1106,50 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       assert activity.data["actor"] == other_user.ap_id
       assert activity.data["cc"] == [user.ap_id]
     end
+
+    test "it correctly processes messages with non-array to field" do
+      user = insert(:user)
+
+      message = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "to" => "https://www.w3.org/ns/activitystreams#Public",
+        "type" => "Create",
+        "object" => %{
+          "content" => "blah blah blah",
+          "type" => "Note",
+          "attributedTo" => user.ap_id,
+          "inReplyTo" => nil
+        },
+        "actor" => user.ap_id
+      }
+
+      assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+
+      assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
+    end
+
+    test "it correctly processes messages with non-array cc field" do
+      user = insert(:user)
+
+      message = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "to" => user.follower_address,
+        "cc" => "https://www.w3.org/ns/activitystreams#Public",
+        "type" => "Create",
+        "object" => %{
+          "content" => "blah blah blah",
+          "type" => "Note",
+          "attributedTo" => user.ap_id,
+          "inReplyTo" => nil
+        },
+        "actor" => user.ap_id
+      }
+
+      assert {:ok, activity} = Transmogrifier.handle_incoming(message)
+
+      assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
+      assert [user.follower_address] == activity.data["to"]
+    end
   end
 
   describe "prepare outgoing" do
@@ -1298,18 +1342,18 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
       assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
 
       user = User.get_cached_by_id(user.id)
-      assert user.info.note_count == 1
+      assert user.note_count == 1
 
       {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
       ObanHelpers.perform_all()
 
-      assert user.info.ap_enabled
-      assert user.info.note_count == 1
+      assert user.ap_enabled
+      assert user.note_count == 1
       assert user.follower_address == "https://niu.moe/users/rye/followers"
       assert user.following_address == "https://niu.moe/users/rye/following"
 
       user = User.get_cached_by_id(user.id)
-      assert user.info.note_count == 1
+      assert user.note_count == 1
 
       activity = Activity.get_by_id(activity.id)
       assert user.follower_address in activity.recipients
@@ -1330,7 +1374,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
                      "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
                  }
                ]
-             } = user.info.banner
+             } = user.banner
 
       refute "..." in activity.recipients
 
diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs
index c57ea7eb9..c1b000fac 100644
--- a/test/web/activity_pub/utils_test.exs
+++ b/test/web/activity_pub/utils_test.exs
@@ -297,7 +297,7 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
 
   describe "update_follow_state_for_all/2" do
     test "updates the state of all Follow activities with the same actor and object" do
-      user = insert(:user, info: %{locked: true})
+      user = insert(:user, locked: true)
       follower = insert(:user)
 
       {:ok, follow_activity} = ActivityPub.follow(follower, user)
@@ -321,7 +321,7 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
 
   describe "update_follow_state/2" do
     test "updates the state of the given follow activity" do
-      user = insert(:user, info: %{locked: true})
+      user = insert(:user, locked: true)
       follower = insert(:user)
 
       {:ok, follow_activity} = ActivityPub.follow(follower, user)
diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs
index 3155749aa..3299be2d5 100644
--- a/test/web/activity_pub/views/user_view_test.exs
+++ b/test/web/activity_pub/views/user_view_test.exs
@@ -29,7 +29,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
 
     {:ok, user} =
       insert(:user)
-      |> User.upgrade_changeset(%{info: %{fields: fields}})
+      |> User.upgrade_changeset(%{fields: fields})
       |> User.update_and_set_cache()
 
     assert %{
@@ -38,7 +38,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
   end
 
   test "Renders with emoji tags" do
-    user = insert(:user, %{info: %{emoji: [%{"bib" => "/test"}]}})
+    user = insert(:user, emoji: [%{"bib" => "/test"}])
 
     assert %{
              "tag" => [
@@ -64,9 +64,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
     user =
       insert(:user,
         avatar: %{"url" => [%{"href" => "https://someurl"}]},
-        info: %{
-          banner: %{"url" => [%{"href" => "https://somebanner"}]}
-        }
+        banner: %{"url" => [%{"href" => "https://somebanner"}]}
       )
 
     {:ok, user} = User.ensure_keys_present(user)
@@ -76,6 +74,12 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
     assert result["image"]["url"] == "https://somebanner"
   end
 
+  test "renders an invisible user with the invisible property set to true" do
+    user = insert(:user, invisible: true)
+
+    assert %{"invisible" => true} = UserView.render("service.json", %{user: user})
+  end
+
   describe "endpoints" do
     test "local users have a usable endpoints structure" do
       user = insert(:user)
@@ -121,8 +125,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
       other_user = insert(:user)
       {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
       assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user})
-      info = Map.merge(user.info, %{hide_followers_count: true, hide_followers: true})
-      user = Map.put(user, :info, info)
+      user = Map.merge(user, %{hide_followers_count: true, hide_followers: true})
       assert %{"totalItems" => 0} = UserView.render("followers.json", %{user: user})
     end
 
@@ -131,8 +134,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
       other_user = insert(:user)
       {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
       assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user})
-      info = Map.merge(user.info, %{hide_followers_count: false, hide_followers: true})
-      user = Map.put(user, :info, info)
+      user = Map.merge(user, %{hide_followers_count: false, hide_followers: true})
       assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user})
     end
   end
@@ -143,8 +145,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
       other_user = insert(:user)
       {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
       assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user})
-      info = Map.merge(user.info, %{hide_follows_count: true, hide_follows: true})
-      user = Map.put(user, :info, info)
+      user = Map.merge(user, %{hide_follows_count: true, hide_follows: true})
       assert %{"totalItems" => 0} = UserView.render("following.json", %{user: user})
     end
 
@@ -153,8 +154,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
       other_user = insert(:user)
       {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
       assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user})
-      info = Map.merge(user.info, %{hide_follows_count: false, hide_follows: true})
-      user = Map.put(user, :info, info)
+      user = Map.merge(user, %{hide_follows_count: false, hide_follows: true})
       assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user})
     end
   end
diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
index 6dc0d4dca..22c989892 100644
--- a/test/web/admin_api/admin_api_controller_test.exs
+++ b/test/web/admin_api/admin_api_controller_test.exs
@@ -25,7 +25,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "DELETE /api/pleroma/admin/users" do
     test "single user" do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       user = insert(:user)
 
       conn =
@@ -43,7 +43,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "multiple users" do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       user_one = insert(:user)
       user_two = insert(:user)
 
@@ -67,7 +67,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "/api/pleroma/admin/users" do
     test "Create" do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
 
       conn =
         build_conn()
@@ -97,7 +97,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "Cannot create user with exisiting email" do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       user = insert(:user)
 
       conn =
@@ -128,7 +128,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "Cannot create user with exisiting nickname" do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       user = insert(:user)
 
       conn =
@@ -159,7 +159,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "Multiple user creation works in transaction" do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       user = insert(:user)
 
       conn =
@@ -208,7 +208,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "/api/pleroma/admin/users/:nickname" do
     test "Show", %{conn: conn} do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       user = insert(:user)
 
       conn =
@@ -231,7 +231,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "when the user doesn't exist", %{conn: conn} do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       user = build(:user)
 
       conn =
@@ -245,7 +245,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "/api/pleroma/admin/users/follow" do
     test "allows to force-follow another user" do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       user = insert(:user)
       follower = insert(:user)
 
@@ -271,7 +271,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "/api/pleroma/admin/users/unfollow" do
     test "allows to force-unfollow another user" do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       user = insert(:user)
       follower = insert(:user)
 
@@ -299,7 +299,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "PUT /api/pleroma/admin/users/tag" do
     setup do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       user1 = insert(:user, %{tags: ["x"]})
       user2 = insert(:user, %{tags: ["y"]})
       user3 = insert(:user, %{tags: ["unchanged"]})
@@ -348,7 +348,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "DELETE /api/pleroma/admin/users/tag" do
     setup do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       user1 = insert(:user, %{tags: ["x"]})
       user2 = insert(:user, %{tags: ["y", "z"]})
       user3 = insert(:user, %{tags: ["unchanged"]})
@@ -397,7 +397,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "/api/pleroma/admin/users/:nickname/permission_group" do
     test "GET is giving user_info" do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
 
       conn =
         build_conn()
@@ -412,7 +412,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "/:right POST, can add to a permission group" do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       user = insert(:user)
 
       conn =
@@ -432,7 +432,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "/:right POST, can add to a permission group (multiple)" do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       user_one = insert(:user)
       user_two = insert(:user)
 
@@ -455,8 +455,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "/:right DELETE, can remove from a permission group" do
-      admin = insert(:user, info: %{is_admin: true})
-      user = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
+      user = insert(:user, is_admin: true)
 
       conn =
         build_conn()
@@ -475,9 +475,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "/:right DELETE, can remove from a permission group (multiple)" do
-      admin = insert(:user, info: %{is_admin: true})
-      user_one = insert(:user, info: %{is_admin: true})
-      user_two = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
+      user_one = insert(:user, is_admin: true)
+      user_two = insert(:user, is_admin: true)
 
       conn =
         build_conn()
@@ -502,7 +502,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "POST /api/pleroma/admin/email_invite, with valid config" do
     setup do
-      [user: insert(:user, info: %{is_admin: true})]
+      [user: insert(:user, is_admin: true)]
     end
 
     clear_config([:instance, :registrations_open]) do
@@ -562,7 +562,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do
     setup do
-      [user: insert(:user, info: %{is_admin: true})]
+      [user: insert(:user, is_admin: true)]
     end
 
     clear_config([:instance, :registrations_open])
@@ -594,7 +594,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
   end
 
   test "/api/pleroma/admin/users/:nickname/password_reset" do
-    admin = insert(:user, info: %{is_admin: true})
+    admin = insert(:user, is_admin: true)
     user = insert(:user)
 
     conn =
@@ -610,7 +610,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "GET /api/pleroma/admin/users" do
     setup do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
 
       conn =
         build_conn()
@@ -626,7 +626,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       users =
         [
           %{
-            "deactivated" => admin.info.deactivated,
+            "deactivated" => admin.deactivated,
             "id" => admin.id,
             "nickname" => admin.nickname,
             "roles" => %{"admin" => true, "moderator" => false},
@@ -636,7 +636,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
             "display_name" => HTML.strip_tags(admin.name || admin.nickname)
           },
           %{
-            "deactivated" => user.info.deactivated,
+            "deactivated" => user.deactivated,
             "id" => user.id,
             "nickname" => user.nickname,
             "roles" => %{"admin" => false, "moderator" => false},
@@ -677,7 +677,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                "page_size" => 50,
                "users" => [
                  %{
-                   "deactivated" => user.info.deactivated,
+                   "deactivated" => user.deactivated,
                    "id" => user.id,
                    "nickname" => user.nickname,
                    "roles" => %{"admin" => false, "moderator" => false},
@@ -701,7 +701,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                "page_size" => 50,
                "users" => [
                  %{
-                   "deactivated" => user.info.deactivated,
+                   "deactivated" => user.deactivated,
                    "id" => user.id,
                    "nickname" => user.nickname,
                    "roles" => %{"admin" => false, "moderator" => false},
@@ -725,7 +725,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                "page_size" => 50,
                "users" => [
                  %{
-                   "deactivated" => user.info.deactivated,
+                   "deactivated" => user.deactivated,
                    "id" => user.id,
                    "nickname" => user.nickname,
                    "roles" => %{"admin" => false, "moderator" => false},
@@ -749,7 +749,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                "page_size" => 50,
                "users" => [
                  %{
-                   "deactivated" => user.info.deactivated,
+                   "deactivated" => user.deactivated,
                    "id" => user.id,
                    "nickname" => user.nickname,
                    "roles" => %{"admin" => false, "moderator" => false},
@@ -773,7 +773,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                "page_size" => 50,
                "users" => [
                  %{
-                   "deactivated" => user.info.deactivated,
+                   "deactivated" => user.deactivated,
                    "id" => user.id,
                    "nickname" => user.nickname,
                    "roles" => %{"admin" => false, "moderator" => false},
@@ -797,7 +797,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                "page_size" => 1,
                "users" => [
                  %{
-                   "deactivated" => user.info.deactivated,
+                   "deactivated" => user.deactivated,
                    "id" => user.id,
                    "nickname" => user.nickname,
                    "roles" => %{"admin" => false, "moderator" => false},
@@ -816,7 +816,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                "page_size" => 1,
                "users" => [
                  %{
-                   "deactivated" => user2.info.deactivated,
+                   "deactivated" => user2.deactivated,
                    "id" => user2.id,
                    "nickname" => user2.nickname,
                    "roles" => %{"admin" => false, "moderator" => false},
@@ -830,7 +830,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "only local users" do
-      admin = insert(:user, info: %{is_admin: true}, nickname: "john")
+      admin = insert(:user, is_admin: true, nickname: "john")
       user = insert(:user, nickname: "bob")
 
       insert(:user, nickname: "bobb", local: false)
@@ -845,7 +845,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                "page_size" => 50,
                "users" => [
                  %{
-                   "deactivated" => user.info.deactivated,
+                   "deactivated" => user.deactivated,
                    "id" => user.id,
                    "nickname" => user.nickname,
                    "roles" => %{"admin" => false, "moderator" => false},
@@ -859,7 +859,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "only local users with no query", %{admin: old_admin} do
-      admin = insert(:user, info: %{is_admin: true}, nickname: "john")
+      admin = insert(:user, is_admin: true, nickname: "john")
       user = insert(:user, nickname: "bob")
 
       insert(:user, nickname: "bobb", local: false)
@@ -872,7 +872,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       users =
         [
           %{
-            "deactivated" => user.info.deactivated,
+            "deactivated" => user.deactivated,
             "id" => user.id,
             "nickname" => user.nickname,
             "roles" => %{"admin" => false, "moderator" => false},
@@ -882,7 +882,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
             "display_name" => HTML.strip_tags(user.name || user.nickname)
           },
           %{
-            "deactivated" => admin.info.deactivated,
+            "deactivated" => admin.deactivated,
             "id" => admin.id,
             "nickname" => admin.nickname,
             "roles" => %{"admin" => true, "moderator" => false},
@@ -912,7 +912,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "load only admins", %{conn: conn, admin: admin} do
-      second_admin = insert(:user, info: %{is_admin: true})
+      second_admin = insert(:user, is_admin: true)
       insert(:user)
       insert(:user)
 
@@ -951,7 +951,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "load only moderators", %{conn: conn} do
-      moderator = insert(:user, info: %{is_moderator: true})
+      moderator = insert(:user, is_moderator: true)
       insert(:user)
       insert(:user)
 
@@ -1016,11 +1016,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "it works with multiple filters" do
-      admin = insert(:user, nickname: "john", info: %{is_admin: true})
-      user = insert(:user, nickname: "bob", local: false, info: %{deactivated: true})
+      admin = insert(:user, nickname: "john", is_admin: true)
+      user = insert(:user, nickname: "bob", local: false, deactivated: true)
 
-      insert(:user, nickname: "ken", local: true, info: %{deactivated: true})
-      insert(:user, nickname: "bobb", local: false, info: %{deactivated: false})
+      insert(:user, nickname: "ken", local: true, deactivated: true)
+      insert(:user, nickname: "bobb", local: false, deactivated: false)
 
       conn =
         build_conn()
@@ -1032,7 +1032,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                "page_size" => 50,
                "users" => [
                  %{
-                   "deactivated" => user.info.deactivated,
+                   "deactivated" => user.deactivated,
                    "id" => user.id,
                    "nickname" => user.nickname,
                    "roles" => %{"admin" => false, "moderator" => false},
@@ -1047,9 +1047,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
   end
 
   test "PATCH /api/pleroma/admin/users/activate" do
-    admin = insert(:user, info: %{is_admin: true})
-    user_one = insert(:user, info: %{deactivated: true})
-    user_two = insert(:user, info: %{deactivated: true})
+    admin = insert(:user, is_admin: true)
+    user_one = insert(:user, deactivated: true)
+    user_two = insert(:user, deactivated: true)
 
     conn =
       build_conn()
@@ -1069,9 +1069,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
   end
 
   test "PATCH /api/pleroma/admin/users/deactivate" do
-    admin = insert(:user, info: %{is_admin: true})
-    user_one = insert(:user, info: %{deactivated: false})
-    user_two = insert(:user, info: %{deactivated: false})
+    admin = insert(:user, is_admin: true)
+    user_one = insert(:user, deactivated: false)
+    user_two = insert(:user, deactivated: false)
 
     conn =
       build_conn()
@@ -1091,7 +1091,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
   end
 
   test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do
-    admin = insert(:user, info: %{is_admin: true})
+    admin = insert(:user, is_admin: true)
     user = insert(:user)
 
     conn =
@@ -1101,7 +1101,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
     assert json_response(conn, 200) ==
              %{
-               "deactivated" => !user.info.deactivated,
+               "deactivated" => !user.deactivated,
                "id" => user.id,
                "nickname" => user.nickname,
                "roles" => %{"admin" => false, "moderator" => false},
@@ -1119,7 +1119,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "POST /api/pleroma/admin/users/invite_token" do
     setup do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
 
       conn =
         build_conn()
@@ -1183,7 +1183,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "GET /api/pleroma/admin/users/invites" do
     setup do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
 
       conn =
         build_conn()
@@ -1221,7 +1221,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "POST /api/pleroma/admin/users/revoke_invite" do
     test "with token" do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       {:ok, invite} = UserInviteToken.create_invite()
 
       conn =
@@ -1241,7 +1241,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "with invalid token" do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
 
       conn =
         build_conn()
@@ -1254,7 +1254,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "GET /api/pleroma/admin/reports/:id" do
     setup %{conn: conn} do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
 
       %{conn: assign(conn, :user, admin)}
     end
@@ -1287,7 +1287,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "PUT /api/pleroma/admin/reports/:id" do
     setup %{conn: conn} do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       [reporter, target_user] = insert_pair(:user)
       activity = insert(:note_activity, user: target_user)
 
@@ -1348,7 +1348,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "GET /api/pleroma/admin/reports" do
     setup %{conn: conn} do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
 
       %{conn: assign(conn, :user, admin)}
     end
@@ -1468,7 +1468,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
   #
   describe "POST /api/pleroma/admin/reports/:id/respond" do
     setup %{conn: conn} do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
 
       %{conn: assign(conn, :user, admin), admin: admin}
     end
@@ -1523,7 +1523,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "PUT /api/pleroma/admin/statuses/:id" do
     setup %{conn: conn} do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       activity = insert(:note_activity)
 
       %{conn: assign(conn, :user, admin), id: activity.id, admin: admin}
@@ -1589,7 +1589,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "DELETE /api/pleroma/admin/statuses/:id" do
     setup %{conn: conn} do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       activity = insert(:note_activity)
 
       %{conn: assign(conn, :user, admin), id: activity.id, admin: admin}
@@ -1619,7 +1619,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "GET /api/pleroma/admin/config" do
     setup %{conn: conn} do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
 
       %{conn: assign(conn, :user, admin)}
     end
@@ -1656,7 +1656,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "POST /api/pleroma/admin/config" do
     setup %{conn: conn} do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
 
       temp_file = "config/test.exported_from_db.secret.exs"
 
@@ -2224,7 +2224,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "config mix tasks run" do
     setup %{conn: conn} do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
 
       temp_file = "config/test.exported_from_db.secret.exs"
 
@@ -2260,7 +2260,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "GET /api/pleroma/admin/users/:nickname/statuses" do
     setup do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       user = insert(:user)
 
       date1 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!()
@@ -2317,8 +2317,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "GET /api/pleroma/admin/moderation_log" do
     setup %{conn: conn} do
-      admin = insert(:user, info: %{is_admin: true})
-      moderator = insert(:user, info: %{is_moderator: true})
+      admin = insert(:user, is_admin: true)
+      moderator = insert(:user, is_moderator: true)
 
       %{conn: assign(conn, :user, admin), admin: admin, moderator: moderator}
     end
@@ -2526,14 +2526,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "PATCH /users/:nickname/force_password_reset" do
     setup %{conn: conn} do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       user = insert(:user)
 
       %{conn: assign(conn, :user, admin), admin: admin, user: user}
     end
 
     test "sets password_reset_pending to true", %{admin: admin, user: user} do
-      assert user.info.password_reset_pending == false
+      assert user.password_reset_pending == false
 
       conn =
         build_conn()
@@ -2544,13 +2544,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
       ObanHelpers.perform_all()
 
-      assert User.get_by_id(user.id).info.password_reset_pending == true
+      assert User.get_by_id(user.id).password_reset_pending == true
     end
   end
 
   describe "relays" do
     setup %{conn: conn} do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
 
       %{conn: assign(conn, :user, admin), admin: admin}
     end
diff --git a/test/web/admin_api/search_test.exs b/test/web/admin_api/search_test.exs
index 9df4cd539..082e691c4 100644
--- a/test/web/admin_api/search_test.exs
+++ b/test/web/admin_api/search_test.exs
@@ -47,9 +47,9 @@ defmodule Pleroma.Web.AdminAPI.SearchTest do
     end
 
     test "it returns active/deactivated users" do
-      insert(:user, info: %{deactivated: true})
-      insert(:user, info: %{deactivated: true})
-      insert(:user, info: %{deactivated: false})
+      insert(:user, deactivated: true)
+      insert(:user, deactivated: true)
+      insert(:user, deactivated: false)
 
       {:ok, _results, active_count} =
         Search.user(%{
@@ -70,7 +70,7 @@ defmodule Pleroma.Web.AdminAPI.SearchTest do
     test "it returns specific user" do
       insert(:user)
       insert(:user)
-      user = insert(:user, nickname: "bob", local: true, info: %{deactivated: false})
+      user = insert(:user, nickname: "bob", local: true, deactivated: false)
 
       {:ok, _results, total_count} = Search.user(%{query: ""})
 
@@ -108,7 +108,7 @@ defmodule Pleroma.Web.AdminAPI.SearchTest do
     end
 
     test "it returns admin user" do
-      admin = insert(:user, info: %{is_admin: true})
+      admin = insert(:user, is_admin: true)
       insert(:user)
       insert(:user)
 
@@ -119,7 +119,7 @@ defmodule Pleroma.Web.AdminAPI.SearchTest do
     end
 
     test "it returns moderator user" do
-      moderator = insert(:user, info: %{is_moderator: true})
+      moderator = insert(:user, is_moderator: true)
       insert(:user)
       insert(:user)
 
diff --git a/test/web/admin_api/views/report_view_test.exs b/test/web/admin_api/views/report_view_test.exs
index 475705857..ef4a806e4 100644
--- a/test/web/admin_api/views/report_view_test.exs
+++ b/test/web/admin_api/views/report_view_test.exs
@@ -49,6 +49,8 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do
     {:ok, report_activity} =
       CommonAPI.report(user, %{"account_id" => other_user.id, "status_ids" => [activity.id]})
 
+    other_user = Pleroma.User.get_by_id(other_user.id)
+
     expected = %{
       content: nil,
       actor:
diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs
index 83df44c36..1d2f20617 100644
--- a/test/web/common_api/common_api_test.exs
+++ b/test/web/common_api/common_api_test.exs
@@ -100,7 +100,7 @@ defmodule Pleroma.Web.CommonAPITest do
 
     {:ok, activity} = CommonAPI.update(user)
     user = User.get_cached_by_ap_id(user.ap_id)
-    [firefox] = user.info.source_data["tag"]
+    [firefox] = user.source_data["tag"]
 
     assert firefox["name"] == ":firefox:"
 
@@ -291,7 +291,7 @@ defmodule Pleroma.Web.CommonAPITest do
       id = activity.id
       user = refresh_record(user)
 
-      assert %User{info: %{pinned_activities: [^id]}} = user
+      assert %User{pinned_activities: [^id]} = user
     end
 
     test "unlisted statuses can be pinned", %{user: user} do
@@ -325,7 +325,7 @@ defmodule Pleroma.Web.CommonAPITest do
 
       user = refresh_record(user)
 
-      assert %User{info: %{pinned_activities: []}} = user
+      assert %User{pinned_activities: []} = user
     end
 
     test "should unpin when deleting a status", %{user: user, activity: activity} do
@@ -337,7 +337,7 @@ defmodule Pleroma.Web.CommonAPITest do
 
       user = refresh_record(user)
 
-      assert %User{info: %{pinned_activities: []}} = user
+      assert %User{pinned_activities: []} = user
     end
   end
 
@@ -468,7 +468,7 @@ defmodule Pleroma.Web.CommonAPITest do
 
   describe "accept_follow_request/2" do
     test "after acceptance, it sets all existing pending follow request states to 'accept'" do
-      user = insert(:user, info: %{locked: true})
+      user = insert(:user, locked: true)
       follower = insert(:user)
       follower_two = insert(:user)
 
@@ -488,7 +488,7 @@ defmodule Pleroma.Web.CommonAPITest do
     end
 
     test "after rejection, it sets all existing pending follow request states to 'reject'" do
-      user = insert(:user, info: %{locked: true})
+      user = insert(:user, locked: true)
       follower = insert(:user)
       follower_two = insert(:user)
 
diff --git a/test/web/federator_test.exs b/test/web/federator_test.exs
index bdaefdce1..c224197c3 100644
--- a/test/web/federator_test.exs
+++ b/test/web/federator_test.exs
@@ -81,14 +81,16 @@ defmodule Pleroma.Web.FederatorTest do
         local: false,
         nickname: "nick1@domain.com",
         ap_id: "https://domain.com/users/nick1",
-        info: %{ap_enabled: true, source_data: %{"inbox" => inbox1}}
+        source_data: %{"inbox" => inbox1},
+        ap_enabled: true
       })
 
       insert(:user, %{
         local: false,
         nickname: "nick2@domain2.com",
         ap_id: "https://domain2.com/users/nick2",
-        info: %{ap_enabled: true, source_data: %{"inbox" => inbox2}}
+        source_data: %{"inbox" => inbox2},
+        ap_enabled: true
       })
 
       dt = NaiveDateTime.utc_now()
diff --git a/test/web/masto_fe_controller_test.exs b/test/web/masto_fe_controller_test.exs
index ab9dab352..b5dbd4a25 100644
--- a/test/web/masto_fe_controller_test.exs
+++ b/test/web/masto_fe_controller_test.exs
@@ -23,7 +23,7 @@ defmodule Pleroma.Web.MastodonAPI.MastoFEController do
     assert _result = json_response(conn, 200)
 
     user = User.get_cached_by_ap_id(user.ap_id)
-    assert user.info.settings == %{"programming" => "socks"}
+    assert user.settings == %{"programming" => "socks"}
   end
 
   describe "index/2 redirections" do
diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
index 618031b40..519b56d6c 100644
--- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
@@ -153,7 +153,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
         |> json_response(200)
 
       assert response["pleroma"]["skip_thread_containment"] == true
-      assert refresh_record(user).info.skip_thread_containment
+      assert refresh_record(user).skip_thread_containment
     end
 
     test "updates the user's hide_follows status", %{conn: conn} do
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index f8cd9630c..8fc2d9300 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -269,7 +269,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
     test "getting followers, hide_followers", %{conn: conn} do
       user = insert(:user)
-      other_user = insert(:user, %{info: %{hide_followers: true}})
+      other_user = insert(:user, hide_followers: true)
       {:ok, _user} = User.follow(user, other_user)
 
       conn =
@@ -281,7 +281,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
     test "getting followers, hide_followers, same user requesting", %{conn: conn} do
       user = insert(:user)
-      other_user = insert(:user, %{info: %{hide_followers: true}})
+      other_user = insert(:user, hide_followers: true)
       {:ok, _user} = User.follow(user, other_user)
 
       conn =
@@ -349,7 +349,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     end
 
     test "getting following, hide_follows", %{conn: conn} do
-      user = insert(:user, %{info: %{hide_follows: true}})
+      user = insert(:user, hide_follows: true)
       other_user = insert(:user)
       {:ok, user} = User.follow(user, other_user)
 
@@ -361,7 +361,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     end
 
     test "getting following, hide_follows, same user requesting", %{conn: conn} do
-      user = insert(:user, %{info: %{hide_follows: true}})
+      user = insert(:user, hide_follows: true)
       other_user = insert(:user)
       {:ok, user} = User.follow(user, other_user)
 
@@ -683,7 +683,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       token_from_db = Repo.preload(token_from_db, :user)
       assert token_from_db.user
 
-      assert token_from_db.user.info.confirmation_pending
+      assert token_from_db.user.confirmation_pending
     end
 
     test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do
@@ -727,7 +727,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         token_from_db = Repo.preload(token_from_db, :user)
         assert token_from_db.user
 
-        assert token_from_db.user.info.confirmation_pending
+        assert token_from_db.user.confirmation_pending
       end
 
       conn =
@@ -812,7 +812,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     end
 
     test "verify_credentials default scope unlisted", %{conn: conn} do
-      user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}})
+      user = insert(:user, default_scope: "unlisted")
 
       conn =
         conn
@@ -824,7 +824,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     end
 
     test "locked accounts", %{conn: conn} do
-      user = insert(:user, %{info: %User.Info{default_scope: "private"}})
+      user = insert(:user, default_scope: "private")
 
       conn =
         conn
diff --git a/test/web/mastodon_api/controllers/conversation_controller_test.exs b/test/web/mastodon_api/controllers/conversation_controller_test.exs
index d89a87179..542af4944 100644
--- a/test/web/mastodon_api/controllers/conversation_controller_test.exs
+++ b/test/web/mastodon_api/controllers/conversation_controller_test.exs
@@ -17,7 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
 
     {:ok, user_two} = User.follow(user_two, user_one)
 
-    assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 0
+    assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
 
     {:ok, direct} =
       CommonAPI.post(user_one, %{
@@ -25,7 +25,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
         "visibility" => "direct"
       })
 
-    assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 1
+    assert User.get_cached_by_id(user_two.id).unread_conversation_count == 1
 
     {:ok, _follower_only} =
       CommonAPI.post(user_one, %{
@@ -56,7 +56,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
     assert is_binary(res_id)
     assert unread == false
     assert res_last_status["id"] == direct.id
-    assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 0
+    assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0
   end
 
   test "updates the last_status on reply", %{conn: conn} do
@@ -95,8 +95,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
         "visibility" => "direct"
       })
 
-    assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 0
-    assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 1
+    assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0
+    assert User.get_cached_by_id(user_two.id).unread_conversation_count == 1
 
     [%{"id" => direct_conversation_id, "unread" => true}] =
       conn
@@ -110,8 +110,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
       |> post("/api/v1/conversations/#{direct_conversation_id}/read")
       |> json_response(200)
 
-    assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 0
-    assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 0
+    assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0
+    assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
 
     # The conversation is marked as unread on reply
     {:ok, _} =
@@ -127,8 +127,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
       |> get("/api/v1/conversations")
       |> json_response(200)
 
-    assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 1
-    assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 0
+    assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1
+    assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
 
     # A reply doesn't increment the user's unread_conversation_count if the conversation is unread
     {:ok, _} =
@@ -138,8 +138,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do
         "in_reply_to_status_id" => direct.id
       })
 
-    assert User.get_cached_by_id(user_one.id).info.unread_conversation_count == 1
-    assert User.get_cached_by_id(user_two.id).info.unread_conversation_count == 0
+    assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1
+    assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0
   end
 
   test "(vanilla) Mastodon frontend behaviour", %{conn: conn} do
diff --git a/test/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/web/mastodon_api/controllers/follow_request_controller_test.exs
index 89b676201..288cd9029 100644
--- a/test/web/mastodon_api/controllers/follow_request_controller_test.exs
+++ b/test/web/mastodon_api/controllers/follow_request_controller_test.exs
@@ -12,7 +12,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
 
   describe "locked accounts" do
     test "/api/v1/follow_requests works" do
-      user = insert(:user, %{info: %User.Info{locked: true}})
+      user = insert(:user, locked: true)
       other_user = insert(:user)
 
       {:ok, _activity} = ActivityPub.follow(other_user, user)
@@ -30,7 +30,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
     end
 
     test "/api/v1/follow_requests/:id/authorize works" do
-      user = insert(:user, %{info: %User.Info{locked: true}})
+      user = insert(:user, locked: true)
       other_user = insert(:user)
 
       {:ok, _activity} = ActivityPub.follow(other_user, user)
@@ -56,7 +56,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do
     end
 
     test "/api/v1/follow_requests/:id/reject works" do
-      user = insert(:user, %{info: %User.Info{locked: true}})
+      user = insert(:user, locked: true)
       other_user = insert(:user)
 
       {:ok, _activity} = ActivityPub.follow(other_user, user)
diff --git a/test/web/mastodon_api/controllers/instance_controller_test.exs b/test/web/mastodon_api/controllers/instance_controller_test.exs
index f8049f81f..e00de6b18 100644
--- a/test/web/mastodon_api/controllers/instance_controller_test.exs
+++ b/test/web/mastodon_api/controllers/instance_controller_test.exs
@@ -41,20 +41,13 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do
     user = insert(:user, %{local: true})
 
     user2 = insert(:user, %{local: true})
-    {:ok, _user2} = User.deactivate(user2, !user2.info.deactivated)
+    {:ok, _user2} = User.deactivate(user2, !user2.deactivated)
 
     insert(:user, %{local: false, nickname: "u@peer1.com"})
     insert(:user, %{local: false, nickname: "u@peer2.com"})
 
     {:ok, _} = Pleroma.Web.CommonAPI.post(user, %{"status" => "cofe"})
 
-    # Stats should count users with missing or nil `info.deactivated` value
-
-    {:ok, _user} =
-      user.id
-      |> User.get_cached_by_id()
-      |> User.update_info(&Ecto.Changeset.change(&1, %{deactivated: nil}))
-
     Pleroma.Stats.force_update()
 
     conn = get(conn, "/api/v1/instance")
diff --git a/test/web/mastodon_api/controllers/marker_controller_test.exs b/test/web/mastodon_api/controllers/marker_controller_test.exs
new file mode 100644
index 000000000..1fcad873d
--- /dev/null
+++ b/test/web/mastodon_api/controllers/marker_controller_test.exs
@@ -0,0 +1,124 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do
+  use Pleroma.Web.ConnCase
+
+  import Pleroma.Factory
+
+  describe "GET /api/v1/markers" do
+    test "gets markers with correct scopes", %{conn: conn} do
+      user = insert(:user)
+      token = insert(:oauth_token, user: user, scopes: ["read:statuses"])
+
+      {:ok, %{"notifications" => marker}} =
+        Pleroma.Marker.upsert(
+          user,
+          %{"notifications" => %{"last_read_id" => "69420"}}
+        )
+
+      response =
+        conn
+        |> assign(:user, user)
+        |> assign(:token, token)
+        |> get("/api/v1/markers", %{timeline: ["notifications"]})
+        |> json_response(200)
+
+      assert response == %{
+               "notifications" => %{
+                 "last_read_id" => "69420",
+                 "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at),
+                 "version" => 0
+               }
+             }
+    end
+
+    test "gets markers with missed scopes", %{conn: conn} do
+      user = insert(:user)
+      token = insert(:oauth_token, user: user, scopes: [])
+
+      Pleroma.Marker.upsert(user, %{"notifications" => %{"last_read_id" => "69420"}})
+
+      response =
+        conn
+        |> assign(:user, user)
+        |> assign(:token, token)
+        |> get("/api/v1/markers", %{timeline: ["notifications"]})
+        |> json_response(403)
+
+      assert response == %{"error" => "Insufficient permissions: read:statuses."}
+    end
+  end
+
+  describe "POST /api/v1/markers" do
+    test "creates a marker with correct scopes", %{conn: conn} do
+      user = insert(:user)
+      token = insert(:oauth_token, user: user, scopes: ["write:statuses"])
+
+      response =
+        conn
+        |> assign(:user, user)
+        |> assign(:token, token)
+        |> post("/api/v1/markers", %{
+          home: %{last_read_id: "777"},
+          notifications: %{"last_read_id" => "69420"}
+        })
+        |> json_response(200)
+
+      assert %{
+               "notifications" => %{
+                 "last_read_id" => "69420",
+                 "updated_at" => _,
+                 "version" => 0
+               }
+             } = response
+    end
+
+    test "updates exist marker", %{conn: conn} do
+      user = insert(:user)
+      token = insert(:oauth_token, user: user, scopes: ["write:statuses"])
+
+      {:ok, %{"notifications" => marker}} =
+        Pleroma.Marker.upsert(
+          user,
+          %{"notifications" => %{"last_read_id" => "69477"}}
+        )
+
+      response =
+        conn
+        |> assign(:user, user)
+        |> assign(:token, token)
+        |> post("/api/v1/markers", %{
+          home: %{last_read_id: "777"},
+          notifications: %{"last_read_id" => "69888"}
+        })
+        |> json_response(200)
+
+      assert response == %{
+               "notifications" => %{
+                 "last_read_id" => "69888",
+                 "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at),
+                 "version" => 0
+               }
+             }
+    end
+
+    test "creates a marker with missed scopes", %{conn: conn} do
+      user = insert(:user)
+      token = insert(:oauth_token, user: user, scopes: [])
+
+      response =
+        conn
+        |> assign(:user, user)
+        |> assign(:token, token)
+        |> post("/api/v1/markers", %{
+          home: %{last_read_id: "777"},
+          notifications: %{"last_read_id" => "69420"}
+        })
+        |> json_response(403)
+
+      assert response == %{"error" => "Insufficient permissions: write:statuses."}
+    end
+  end
+end
diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs
index 2de2725e0..a96fd860b 100644
--- a/test/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/web/mastodon_api/controllers/status_controller_test.exs
@@ -12,12 +12,16 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
   alias Pleroma.Object
   alias Pleroma.Repo
   alias Pleroma.ScheduledActivity
+  alias Pleroma.Tests.ObanHelpers
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.CommonAPI
 
   import Pleroma.Factory
 
+  clear_config([:instance, :federating])
+  clear_config([:instance, :allow_relay])
+
   describe "posting statuses" do
     setup do
       user = insert(:user)
@@ -29,6 +33,34 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
       [conn: conn]
     end
 
+    test "posting a status does not increment reblog_count when relaying", %{conn: conn} do
+      Pleroma.Config.put([:instance, :federating], true)
+      Pleroma.Config.get([:instance, :allow_relay], true)
+      user = insert(:user)
+
+      response =
+        conn
+        |> assign(:user, user)
+        |> post("api/v1/statuses", %{
+          "content_type" => "text/plain",
+          "source" => "Pleroma FE",
+          "status" => "Hello world",
+          "visibility" => "public"
+        })
+        |> json_response(200)
+
+      assert response["reblogs_count"] == 0
+      ObanHelpers.perform_all()
+
+      response =
+        conn
+        |> assign(:user, user)
+        |> get("api/v1/statuses/#{response["id"]}", %{})
+        |> json_response(200)
+
+      assert response["reblogs_count"] == 0
+    end
+
     test "posting a status", %{conn: conn} do
       idempotency_key = "Pikachu rocks!"
 
@@ -526,8 +558,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
     test "when you're an admin or moderator", %{conn: conn} do
       activity1 = insert(:note_activity)
       activity2 = insert(:note_activity)
-      admin = insert(:user, info: %{is_admin: true})
-      moderator = insert(:user, info: %{is_moderator: true})
+      admin = insert(:user, is_admin: true)
+      moderator = insert(:user, is_moderator: true)
 
       res_conn =
         conn
diff --git a/test/web/mastodon_api/mastodon_api_test.exs b/test/web/mastodon_api/mastodon_api_test.exs
index 7fcb2bd55..561ef05aa 100644
--- a/test/web/mastodon_api/mastodon_api_test.exs
+++ b/test/web/mastodon_api/mastodon_api_test.exs
@@ -14,11 +14,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPITest do
   import Pleroma.Factory
 
   describe "follow/3" do
-    test "returns error when user deactivated" do
+    test "returns error when followed user is deactivated" do
       follower = insert(:user)
-      user = insert(:user, local: true, info: %{deactivated: true})
+      user = insert(:user, local: true, deactivated: true)
       {:error, error} = MastodonAPI.follow(follower, user)
-      assert error == "Could not follow user: You are deactivated."
+      assert error == "Could not follow user: #{user.nickname} is deactivated."
     end
 
     test "following for user" do
diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs
index ad209b4a3..af88841ed 100644
--- a/test/web/mastodon_api/views/account_view_test.exs
+++ b/test/web/mastodon_api/views/account_view_test.exs
@@ -26,12 +26,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
 
     user =
       insert(:user, %{
-        info: %{
-          note_count: 5,
-          follower_count: 3,
-          source_data: source_data,
-          background: background_image
-        },
+        follower_count: 3,
+        note_count: 5,
+        source_data: source_data,
+        background: background_image,
         nickname: "shp@shitposter.club",
         name: ":karjalanpiirakka: shp",
         bio: "<script src=\"invalid-html\"></script><span>valid html</span>",
@@ -101,7 +99,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
       "non_followers" => true
     }
 
-    privacy = user.info.default_scope
+    privacy = user.default_scope
 
     assert %{
              pleroma: %{notification_settings: ^notification_settings},
@@ -112,7 +110,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
   test "Represent a Service(bot) account" do
     user =
       insert(:user, %{
-        info: %{note_count: 5, follower_count: 3, source_data: %{"type" => "Service"}},
+        follower_count: 3,
+        note_count: 5,
+        source_data: %{"type" => "Service"},
         nickname: "shp@shitposter.club",
         inserted_at: ~N[2017-08-15 15:47:06.597036]
       })
@@ -164,8 +164,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
   end
 
   test "Represent a deactivated user for an admin" do
-    admin = insert(:user, %{info: %{is_admin: true}})
-    deactivated_user = insert(:user, %{info: %{deactivated: true}})
+    admin = insert(:user, is_admin: true)
+    deactivated_user = insert(:user, deactivated: true)
     represented = AccountView.render("show.json", %{user: deactivated_user, for: admin})
     assert represented[:pleroma][:deactivated] == true
   end
@@ -253,7 +253,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
 
     test "represent a relationship for the user with a pending follow request" do
       user = insert(:user)
-      other_user = insert(:user, %{info: %User.Info{locked: true}})
+      other_user = insert(:user, locked: true)
 
       {:ok, user, other_user, _} = CommonAPI.follow(user, other_user)
       user = User.get_cached_by_id(user.id)
@@ -282,7 +282,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
   test "represent an embedded relationship" do
     user =
       insert(:user, %{
-        info: %{note_count: 5, follower_count: 0, source_data: %{"type" => "Service"}},
+        follower_count: 0,
+        note_count: 5,
+        source_data: %{"type" => "Service"},
         nickname: "shp@shitposter.club",
         inserted_at: ~N[2017-08-15 15:47:06.597036]
       })
@@ -352,7 +354,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
   end
 
   test "returns the settings store if the requesting user is the represented user and it's requested specifically" do
-    user = insert(:user, %{info: %User.Info{pleroma_settings_store: %{fe: "test"}}})
+    user = insert(:user, pleroma_settings_store: %{fe: "test"})
 
     result =
       AccountView.render("show.json", %{user: user, for: user, with_pleroma_settings: true})
@@ -374,14 +376,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
 
   describe "hiding follows/following" do
     test "shows when follows/followers stats are hidden and sets follow/follower count to 0" do
-      info = %{
-        hide_followers: true,
-        hide_followers_count: true,
-        hide_follows: true,
-        hide_follows_count: true
-      }
-
-      user = insert(:user, info: info)
+      user =
+        insert(:user, %{
+          hide_followers: true,
+          hide_followers_count: true,
+          hide_follows: true,
+          hide_follows_count: true
+        })
 
       other_user = insert(:user)
       {:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user)
@@ -395,7 +396,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
     end
 
     test "shows when follows/followers are hidden" do
-      user = insert(:user, info: %{hide_followers: true, hide_follows: true})
+      user = insert(:user, hide_followers: true, hide_follows: true)
       other_user = insert(:user)
       {:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user)
       {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
@@ -408,7 +409,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
     end
 
     test "shows actual follower/following count to the account owner" do
-      user = insert(:user, info: %{hide_followers: true, hide_follows: true})
+      user = insert(:user, hide_followers: true, hide_follows: true)
       other_user = insert(:user)
       {:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user)
       {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
@@ -456,7 +457,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
     end
 
     test "shows non-zero when follow requests are pending" do
-      user = insert(:user, %{info: %{locked: true}})
+      user = insert(:user, locked: true)
 
       assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user})
 
@@ -468,7 +469,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
     end
 
     test "decreases when accepting a follow request" do
-      user = insert(:user, %{info: %{locked: true}})
+      user = insert(:user, locked: true)
 
       assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user})
 
@@ -485,7 +486,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
     end
 
     test "decreases when rejecting a follow request" do
-      user = insert(:user, %{info: %{locked: true}})
+      user = insert(:user, locked: true)
 
       assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user})
 
@@ -502,14 +503,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
     end
 
     test "shows non-zero when historical unapproved requests are present" do
-      user = insert(:user, %{info: %{locked: true}})
+      user = insert(:user, locked: true)
 
       assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user})
 
       other_user = insert(:user)
       {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
 
-      {:ok, user} = User.update_info(user, &User.Info.user_upgrade(&1, %{locked: false}))
+      {:ok, user} = User.update_and_set_cache(user, %{locked: false})
 
       assert %{locked: false, follow_requests_count: 1} =
                AccountView.render("show.json", %{user: user, for: user})
diff --git a/test/web/mastodon_api/views/marker_view_test.exs b/test/web/mastodon_api/views/marker_view_test.exs
new file mode 100644
index 000000000..8a5c89d56
--- /dev/null
+++ b/test/web/mastodon_api/views/marker_view_test.exs
@@ -0,0 +1,27 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.MarkerViewTest do
+  use Pleroma.DataCase
+  alias Pleroma.Web.MastodonAPI.MarkerView
+  import Pleroma.Factory
+
+  test "returns markers" do
+    marker1 = insert(:marker, timeline: "notifications", last_read_id: "17")
+    marker2 = insert(:marker, timeline: "home", last_read_id: "42")
+
+    assert MarkerView.render("markers.json", %{markers: [marker1, marker2]}) == %{
+             "home" => %{
+               last_read_id: "42",
+               updated_at: NaiveDateTime.to_iso8601(marker2.updated_at),
+               version: 0
+             },
+             "notifications" => %{
+               last_read_id: "17",
+               updated_at: NaiveDateTime.to_iso8601(marker1.updated_at),
+               version: 0
+             }
+           }
+  end
+end
diff --git a/test/web/node_info_test.exs b/test/web/node_info_test.exs
index e15a0bfff..a3281b25b 100644
--- a/test/web/node_info_test.exs
+++ b/test/web/node_info_test.exs
@@ -24,8 +24,8 @@ defmodule Pleroma.Web.NodeInfoTest do
   end
 
   test "nodeinfo shows staff accounts", %{conn: conn} do
-    moderator = insert(:user, %{local: true, info: %{is_moderator: true}})
-    admin = insert(:user, %{local: true, info: %{is_admin: true}})
+    moderator = insert(:user, local: true, is_moderator: true)
+    admin = insert(:user, local: true, is_admin: true)
 
     conn =
       conn
diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs
index 41aaf6189..ad8d79083 100644
--- a/test/web/oauth/oauth_controller_test.exs
+++ b/test/web/oauth/oauth_controller_test.exs
@@ -780,8 +780,8 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
 
       {:ok, user} =
         insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password))
-        |> User.change_info(&User.Info.confirmation_changeset(&1, need_confirmation: true))
-        |> Repo.update()
+        |> User.confirmation_changeset(need_confirmation: true)
+        |> User.update_and_set_cache()
 
       refute Pleroma.User.auth_active?(user)
 
@@ -808,7 +808,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
       user =
         insert(:user,
           password_hash: Comeonin.Pbkdf2.hashpwsalt(password),
-          info: %{deactivated: true}
+          deactivated: true
         )
 
       app = insert(:oauth_app)
@@ -834,7 +834,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
       user =
         insert(:user,
           password_hash: Comeonin.Pbkdf2.hashpwsalt(password),
-          info: %{password_reset_pending: true}
+          password_reset_pending: true
         )
 
       app = insert(:oauth_app, scopes: ["read", "write"])
diff --git a/test/web/pleroma_api/controllers/account_controller_test.exs b/test/web/pleroma_api/controllers/account_controller_test.exs
index 3b4665afd..c809f510f 100644
--- a/test/web/pleroma_api/controllers/account_controller_test.exs
+++ b/test/web/pleroma_api/controllers/account_controller_test.exs
@@ -6,7 +6,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
   use Pleroma.Web.ConnCase
 
   alias Pleroma.Config
-  alias Pleroma.Repo
   alias Pleroma.Tests.ObanHelpers
   alias Pleroma.User
   alias Pleroma.Web.CommonAPI
@@ -20,10 +19,10 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
     setup do
       {:ok, user} =
         insert(:user)
-        |> User.change_info(&User.Info.confirmation_changeset(&1, need_confirmation: true))
-        |> Repo.update()
+        |> User.confirmation_changeset(need_confirmation: true)
+        |> User.update_and_set_cache()
 
-      assert user.info.confirmation_pending
+      assert user.confirmation_pending
 
       [user: user]
     end
@@ -105,7 +104,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
         |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => @image})
 
       user = refresh_record(user)
-      assert user.info.banner["type"] == "Image"
+      assert user.banner["type"] == "Image"
 
       assert %{"url" => _} = json_response(conn, 200)
     end
@@ -119,7 +118,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
         |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => ""})
 
       user = refresh_record(user)
-      assert user.info.banner == %{}
+      assert user.banner == %{}
 
       assert %{"url" => nil} = json_response(conn, 200)
     end
@@ -135,7 +134,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
         |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => @image})
 
       user = refresh_record(user)
-      assert user.info.background["type"] == "Image"
+      assert user.background["type"] == "Image"
       assert %{"url" => _} = json_response(conn, 200)
     end
 
@@ -148,14 +147,14 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
         |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => ""})
 
       user = refresh_record(user)
-      assert user.info.background == %{}
+      assert user.background == %{}
       assert %{"url" => nil} = json_response(conn, 200)
     end
   end
 
   describe "getting favorites timeline of specified user" do
     setup do
-      [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}})
+      [current_user, user] = insert_pair(:user, hide_favorites: false)
       [current_user: current_user, user: user]
     end
 
@@ -319,7 +318,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
       conn: conn,
       current_user: current_user
     } do
-      user = insert(:user, %{info: %{hide_favorites: true}})
+      user = insert(:user, hide_favorites: true)
       activity = insert(:note_activity)
       CommonAPI.favorite(activity.id, user)
 
@@ -341,7 +340,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do
         |> assign(:user, current_user)
         |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
 
-      assert user.info.hide_favorites
+      assert user.hide_favorites
       assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
     end
   end
diff --git a/test/web/pleroma_api/controllers/emoji_api_controller_test.exs b/test/web/pleroma_api/controllers/emoji_api_controller_test.exs
index 5f74460e8..3d3becefd 100644
--- a/test/web/pleroma_api/controllers/emoji_api_controller_test.exs
+++ b/test/web/pleroma_api/controllers/emoji_api_controller_test.exs
@@ -38,7 +38,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
   end
 
   test "listing remote packs" do
-    admin = insert(:user, info: %{is_admin: true})
+    admin = insert(:user, is_admin: true)
     conn = build_conn() |> assign(:user, admin)
 
     resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200)
@@ -121,7 +121,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
         text(File.read!("#{@emoji_dir_path}/test_pack_nonshared/nonshared.zip"))
     end)
 
-    admin = insert(:user, info: %{is_admin: true})
+    admin = insert(:user, is_admin: true)
 
     conn = build_conn() |> assign(:user, admin)
 
@@ -206,7 +206,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
       end)
 
       {:ok,
-       admin: insert(:user, info: %{is_admin: true}),
+       admin: insert(:user, is_admin: true),
        pack_file: pack_file,
        new_data: %{
          "license" => "Test license changed",
@@ -303,7 +303,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
       File.rm_rf!("#{@emoji_dir_path}/test_pack/dir_2")
     end)
 
-    admin = insert(:user, info: %{is_admin: true})
+    admin = insert(:user, is_admin: true)
 
     conn = build_conn()
 
@@ -391,7 +391,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
       File.rm_rf!("#{@emoji_dir_path}/test_created")
     end)
 
-    admin = insert(:user, info: %{is_admin: true})
+    admin = insert(:user, is_admin: true)
 
     conn = build_conn() |> assign(:user, admin)
 
@@ -431,7 +431,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do
 
     refute Map.has_key?(resp, "test_pack_for_import")
 
-    admin = insert(:user, info: %{is_admin: true})
+    admin = insert(:user, is_admin: true)
 
     assert conn
            |> assign(:user, admin)
diff --git a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs
index 9cccc8c8a..0c83edb56 100644
--- a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs
+++ b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs
@@ -108,7 +108,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
     [participation2, participation1] = Participation.for_user(other_user)
     assert Participation.get(participation2.id).read == false
     assert Participation.get(participation1.id).read == false
-    assert User.get_cached_by_id(other_user.id).info.unread_conversation_count == 2
+    assert User.get_cached_by_id(other_user.id).unread_conversation_count == 2
 
     [%{"unread" => false}, %{"unread" => false}] =
       conn
@@ -119,7 +119,7 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do
     [participation2, participation1] = Participation.for_user(other_user)
     assert Participation.get(participation2.id).read == true
     assert Participation.get(participation1.id).read == true
-    assert User.get_cached_by_id(other_user.id).info.unread_conversation_count == 0
+    assert User.get_cached_by_id(other_user.id).unread_conversation_count == 0
   end
 
   describe "POST /api/v1/pleroma/notifications/read" do
diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs
index 2f6ce4bd2..9b554601d 100644
--- a/test/web/push/impl_test.exs
+++ b/test/web/push/impl_test.exs
@@ -84,7 +84,7 @@ defmodule Pleroma.Web.Push.ImplTest do
            ) == :error
   end
 
-  test "delete subsciption if restult send message between 400..500" do
+  test "delete subscription if result send message between 400..500" do
     subscription = insert(:push_subscription)
 
     assert Impl.push_message(
@@ -97,7 +97,7 @@ defmodule Pleroma.Web.Push.ImplTest do
     refute Pleroma.Repo.get(Subscription, subscription.id)
   end
 
-  test "renders body for create activity" do
+  test "renders title and body for create activity" do
     user = insert(:user, nickname: "Bob")
 
     {:ok, activity} =
@@ -116,18 +116,24 @@ defmodule Pleroma.Web.Push.ImplTest do
              object
            ) ==
              "@Bob: Lorem ipsum dolor sit amet, consectetur  adipiscing elit. Fusce sagittis fini..."
+
+    assert Impl.format_title(%{activity: activity}) ==
+             "New Mention"
   end
 
-  test "renders body for follow activity" do
+  test "renders title and body for follow activity" do
     user = insert(:user, nickname: "Bob")
     other_user = insert(:user)
     {:ok, _, _, activity} = CommonAPI.follow(user, other_user)
     object = Object.normalize(activity)
 
     assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has followed you"
+
+    assert Impl.format_title(%{activity: activity}) ==
+             "New Follower"
   end
 
-  test "renders body for announce activity" do
+  test "renders title and body for announce activity" do
     user = insert(:user)
 
     {:ok, activity} =
@@ -141,9 +147,12 @@ defmodule Pleroma.Web.Push.ImplTest do
 
     assert Impl.format_body(%{activity: announce_activity}, user, object) ==
              "@#{user.nickname} repeated: Lorem ipsum dolor sit amet, consectetur  adipiscing elit. Fusce sagittis fini..."
+
+    assert Impl.format_title(%{activity: announce_activity}) ==
+             "New Repeat"
   end
 
-  test "renders body for like activity" do
+  test "renders title and body for like activity" do
     user = insert(:user, nickname: "Bob")
 
     {:ok, activity} =
@@ -156,5 +165,21 @@ defmodule Pleroma.Web.Push.ImplTest do
     object = Object.normalize(activity)
 
     assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has favorited your post"
+
+    assert Impl.format_title(%{activity: activity}) ==
+             "New Favorite"
+  end
+
+  test "renders title for create activity with direct visibility" do
+    user = insert(:user, nickname: "Bob")
+
+    {:ok, activity} =
+      CommonAPI.post(user, %{
+        "visibility" => "direct",
+        "status" => "This is just between you and me, pal"
+      })
+
+    assert Impl.format_title(%{activity: activity}) ==
+             "New Direct Message"
   end
 end
diff --git a/test/web/streamer/streamer_test.exs b/test/web/streamer/streamer_test.exs
index c674e71f5..cb1015171 100644
--- a/test/web/streamer/streamer_test.exs
+++ b/test/web/streamer/streamer_test.exs
@@ -215,7 +215,7 @@ defmodule Pleroma.Web.StreamerTest do
     test "it sends message if recipients invalid and thread containment is enabled but user's thread containment is disabled" do
       Pleroma.Config.put([:instance, :skip_thread_containment], false)
       author = insert(:user)
-      user = insert(:user, info: %{skip_thread_containment: true})
+      user = insert(:user, skip_thread_containment: true)
       User.follow(user, author, "accept")
 
       activity =
diff --git a/test/web/twitter_api/password_controller_test.exs b/test/web/twitter_api/password_controller_test.exs
index dc6d4e3e3..840c84a05 100644
--- a/test/web/twitter_api/password_controller_test.exs
+++ b/test/web/twitter_api/password_controller_test.exs
@@ -59,7 +59,7 @@ defmodule Pleroma.Web.TwitterAPI.PasswordControllerTest do
     end
 
     test "it sets password_reset_pending to false", %{conn: conn} do
-      user = insert(:user, info: %{password_reset_pending: true})
+      user = insert(:user, password_reset_pending: true)
 
       {:ok, token} = PasswordResetToken.create_token(user)
       {:ok, _access_token} = Token.create_token(insert(:oauth_app), user, %{})
@@ -75,7 +75,7 @@ defmodule Pleroma.Web.TwitterAPI.PasswordControllerTest do
       |> post("/api/pleroma/password_reset", %{data: params})
       |> html_response(:ok)
 
-      assert User.get_by_id(user.id).info.password_reset_pending == false
+      assert User.get_by_id(user.id).password_reset_pending == false
     end
   end
 end
diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs
index d1d61d11a..85a9be3e0 100644
--- a/test/web/twitter_api/twitter_api_test.exs
+++ b/test/web/twitter_api/twitter_api_test.exs
@@ -71,7 +71,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
     {:ok, user} = TwitterAPI.register_user(data)
     ObanHelpers.perform_all()
 
-    assert user.info.confirmation_pending
+    assert user.confirmation_pending
 
     email = Pleroma.Emails.UserEmail.account_confirmation_email(user)
 
diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs
index 5234a5271..f0211f59c 100644
--- a/test/web/twitter_api/util_controller_test.exs
+++ b/test/web/twitter_api/util_controller_test.exs
@@ -164,7 +164,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
                "follows" => true,
                "non_follows" => true,
                "non_followers" => true
-             } == user.info.notification_settings
+             } == user.notification_settings
     end
   end
 
@@ -370,7 +370,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
     end
 
     test "returns error when user is deactivated", %{conn: conn} do
-      user = insert(:user, info: %{deactivated: true})
+      user = insert(:user, deactivated: true)
       user2 = insert(:user)
 
       response =
@@ -568,7 +568,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
 
       user = User.get_cached_by_id(user.id)
 
-      assert user.info.deactivated == true
+      assert user.deactivated == true
     end
 
     test "it returns returns when password invalid", %{conn: conn} do
@@ -583,7 +583,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
       assert response == %{"error" => "Invalid password."}
       user = User.get_cached_by_id(user.id)
 
-      refute user.info.deactivated
+      refute user.deactivated
     end
   end