about summary refs log tree commit diff
path: root/app/serializers
diff options
context:
space:
mode:
authorStarfall <us@starfall.systems>2022-11-10 08:50:11 -0600
committerStarfall <us@starfall.systems>2022-11-10 08:50:11 -0600
commit67d1a0476d77e2ed0ca15dd2981c54c2b90b0742 (patch)
tree152f8c13a341d76738e8e2c09b24711936e6af68 /app/serializers
parentb581e6b6d4a5ba9ed4ae17427b7f2d5d158be4e5 (diff)
parentee7e49d1b1323618e16026bc8db8ab7f9459cc2d (diff)
Merge remote-tracking branch 'glitch/main'
- Remove Helm charts
- Lots of conflicts with our removal of recommended settings and custom
  icons
Diffstat (limited to 'app/serializers')
-rw-r--r--app/serializers/activitypub/add_serializer.rb23
-rw-r--r--app/serializers/activitypub/hashtag_serializer.rb2
-rw-r--r--app/serializers/activitypub/public_key_serializer.rb2
-rw-r--r--app/serializers/activitypub/remove_serializer.rb23
-rw-r--r--app/serializers/initial_state_serializer.rb35
-rw-r--r--app/serializers/manifest_serializer.rb12
-rw-r--r--app/serializers/nodeinfo/serializer.rb2
-rw-r--r--app/serializers/rest/account_serializer.rb7
-rw-r--r--app/serializers/rest/admin/account_serializer.rb8
-rw-r--r--app/serializers/rest/admin/canonical_email_block_serializer.rb9
-rw-r--r--app/serializers/rest/admin/email_domain_block_serializer.rb9
-rw-r--r--app/serializers/rest/admin/ip_block_serializer.rb14
-rw-r--r--app/serializers/rest/domain_block_serializer.rb17
-rw-r--r--app/serializers/rest/extended_description_serializer.rb23
-rw-r--r--app/serializers/rest/featured_tag_serializer.rb8
-rw-r--r--app/serializers/rest/instance_serializer.rb124
-rw-r--r--app/serializers/rest/privacy_policy_serializer.rb19
-rw-r--r--app/serializers/rest/relationship_serializer.rb7
-rw-r--r--app/serializers/rest/report_serializer.rb8
-rw-r--r--app/serializers/rest/translation_serializer.rb9
-rw-r--r--app/serializers/rest/v1/instance_serializer.rb119
21 files changed, 391 insertions, 89 deletions
diff --git a/app/serializers/activitypub/add_serializer.rb b/app/serializers/activitypub/add_serializer.rb
index 6f5aab17f..436b05086 100644
--- a/app/serializers/activitypub/add_serializer.rb
+++ b/app/serializers/activitypub/add_serializer.rb
@@ -1,10 +1,29 @@
 # frozen_string_literal: true
 
 class ActivityPub::AddSerializer < ActivityPub::Serializer
+  class UriSerializer < ActiveModel::Serializer
+    include RoutingHelper
+
+    def serializable_hash(*_args)
+      ActivityPub::TagManager.instance.uri_for(object)
+    end
+  end
+
+  def self.serializer_for(model, options)
+    case model.class.name
+    when 'Status'
+      UriSerializer
+    when 'FeaturedTag'
+      ActivityPub::HashtagSerializer
+    else
+      super
+    end
+  end
+
   include RoutingHelper
 
   attributes :type, :actor, :target
-  attribute :proper_object, key: :object
+  has_one :proper_object, key: :object
 
   def type
     'Add'
@@ -15,7 +34,7 @@ class ActivityPub::AddSerializer < ActivityPub::Serializer
   end
 
   def proper_object
-    ActivityPub::TagManager.instance.uri_for(object)
+    object
   end
 
   def target
diff --git a/app/serializers/activitypub/hashtag_serializer.rb b/app/serializers/activitypub/hashtag_serializer.rb
index 90929c57f..2b24eb8cc 100644
--- a/app/serializers/activitypub/hashtag_serializer.rb
+++ b/app/serializers/activitypub/hashtag_serializer.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class ActivityPub::HashtagSerializer < ActivityPub::Serializer
+  context_extensions :hashtag
+
   include RoutingHelper
 
   attributes :type, :href, :name
diff --git a/app/serializers/activitypub/public_key_serializer.rb b/app/serializers/activitypub/public_key_serializer.rb
index 62ed49e81..8621517e7 100644
--- a/app/serializers/activitypub/public_key_serializer.rb
+++ b/app/serializers/activitypub/public_key_serializer.rb
@@ -6,7 +6,7 @@ class ActivityPub::PublicKeySerializer < ActivityPub::Serializer
   attributes :id, :owner, :public_key_pem
 
   def id
-    [ActivityPub::TagManager.instance.uri_for(object), '#main-key'].join
+    ActivityPub::TagManager.instance.key_uri_for(object)
   end
 
   def owner
diff --git a/app/serializers/activitypub/remove_serializer.rb b/app/serializers/activitypub/remove_serializer.rb
index 7fefda59d..fb224f8a9 100644
--- a/app/serializers/activitypub/remove_serializer.rb
+++ b/app/serializers/activitypub/remove_serializer.rb
@@ -1,10 +1,29 @@
 # frozen_string_literal: true
 
 class ActivityPub::RemoveSerializer < ActivityPub::Serializer
+  class UriSerializer < ActiveModel::Serializer
+    include RoutingHelper
+
+    def serializable_hash(*_args)
+      ActivityPub::TagManager.instance.uri_for(object)
+    end
+  end
+
+  def self.serializer_for(model, options)
+    case model.class.name
+    when 'Status'
+      UriSerializer
+    when 'FeaturedTag'
+      ActivityPub::HashtagSerializer
+    else
+      super
+    end
+  end
+
   include RoutingHelper
 
   attributes :type, :actor, :target
-  attribute :proper_object, key: :object
+  has_one :proper_object, key: :object
 
   def type
     'Remove'
@@ -15,7 +34,7 @@ class ActivityPub::RemoveSerializer < ActivityPub::Serializer
   end
 
   def proper_object
-    ActivityPub::TagManager.instance.uri_for(object)
+    object
   end
 
   def target
diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb
index b555be633..d23daaf85 100644
--- a/app/serializers/initial_state_serializer.rb
+++ b/app/serializers/initial_state_serializer.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class InitialStateSerializer < ActiveModel::Serializer
+  include RoutingHelper
+
   attributes :meta, :compose, :accounts,
              :media_attachments, :settings,
              :max_toot_chars, :poll_limits,
@@ -22,22 +24,28 @@ class InitialStateSerializer < ActiveModel::Serializer
     }
   end
 
+  # rubocop:disable Metrics/AbcSize
   def meta
     store = {
       streaming_api_base_url: Rails.configuration.x.streaming_api_base_url,
       access_token: object.token,
       locale: I18n.locale,
-      domain: Rails.configuration.x.local_domain,
-      title: instance_presenter.site_title,
+      domain: instance_presenter.domain,
+      title: instance_presenter.title,
       admin: object.admin&.id&.to_s,
       search_enabled: Chewy.enabled?,
       repository: Mastodon::Version.repository,
-      source_url: Mastodon::Version.source_url,
-      version: Mastodon::Version.to_s,
+      source_url: instance_presenter.source_url,
+      version: instance_presenter.version,
       limited_federation_mode: Rails.configuration.x.whitelist_mode,
       mascot: instance_presenter.mascot&.file&.url,
       profile_directory: Setting.profile_directory,
       trends: Setting.trends,
+      registrations_open: Setting.registrations_mode != 'none' && !Rails.configuration.x.single_user_mode,
+      timeline_preview: Setting.timeline_preview,
+      activity_api_enabled: Setting.activity_api_enabled,
+      single_user_mode: Rails.configuration.x.single_user_mode,
+      translation_enabled: TranslationService.configured?,
     }
 
     if object.current_account
@@ -66,8 +74,16 @@ class InitialStateSerializer < ActiveModel::Serializer
       store[:crop_images]   = Setting.crop_images
     end
 
+    store[:disabled_account_id] = object.disabled_account.id.to_s if object.disabled_account
+    store[:moved_to_account_id] = object.moved_to_account.id.to_s if object.moved_to_account
+
+    if Rails.configuration.x.single_user_mode
+      store[:owner] = object.owner&.id&.to_s
+    end
+
     store
   end
+  # rubocop:enable Metrics/AbcSize
 
   def compose
     store = {}
@@ -86,8 +102,15 @@ class InitialStateSerializer < ActiveModel::Serializer
 
   def accounts
     store = {}
-    store[object.current_account.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.current_account, serializer: REST::AccountSerializer) if object.current_account
-    store[object.admin.id.to_s]           = ActiveModelSerializers::SerializableResource.new(object.admin, serializer: REST::AccountSerializer) if object.admin
+
+    ActiveRecord::Associations::Preloader.new.preload([object.current_account, object.admin, object.owner, object.disabled_account, object.moved_to_account].compact, [:account_stat, :user, { moved_to_account: [:account_stat, :user] }])
+
+    store[object.current_account.id.to_s]  = ActiveModelSerializers::SerializableResource.new(object.current_account, serializer: REST::AccountSerializer) if object.current_account
+    store[object.admin.id.to_s]            = ActiveModelSerializers::SerializableResource.new(object.admin, serializer: REST::AccountSerializer) if object.admin
+    store[object.owner.id.to_s]            = ActiveModelSerializers::SerializableResource.new(object.owner, serializer: REST::AccountSerializer) if object.owner
+    store[object.disabled_account.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.disabled_account, serializer: REST::AccountSerializer) if object.disabled_account
+    store[object.moved_to_account.id.to_s] = ActiveModelSerializers::SerializableResource.new(object.moved_to_account, serializer: REST::AccountSerializer) if object.moved_to_account
+
     store
   end
 
diff --git a/app/serializers/manifest_serializer.rb b/app/serializers/manifest_serializer.rb
index 9827323a8..5604325be 100644
--- a/app/serializers/manifest_serializer.rb
+++ b/app/serializers/manifest_serializer.rb
@@ -22,11 +22,11 @@ class ManifestSerializer < ActiveModel::Serializer
              :share_target, :shortcuts
 
   def name
-    object.site_title
+    object.title
   end
 
   def short_name
-    object.site_title
+    object.title
   end
 
   def icons
@@ -40,7 +40,7 @@ class ManifestSerializer < ActiveModel::Serializer
   end
 
   def theme_color
-    '#6364FF'
+    '#191b22'
   end
 
   def background_color
@@ -52,7 +52,7 @@ class ManifestSerializer < ActiveModel::Serializer
   end
 
   def start_url
-    '/web/home'
+    '/home'
   end
 
   def scope
@@ -77,11 +77,11 @@ class ManifestSerializer < ActiveModel::Serializer
     [
       {
         name: 'Compose new post',
-        url: '/web/publish',
+        url: '/publish',
       },
       {
         name: 'Notifications',
-        url: '/web/notifications',
+        url: '/notifications',
       },
     ]
   end
diff --git a/app/serializers/nodeinfo/serializer.rb b/app/serializers/nodeinfo/serializer.rb
index afae7f00a..f70cc38f0 100644
--- a/app/serializers/nodeinfo/serializer.rb
+++ b/app/serializers/nodeinfo/serializer.rb
@@ -38,7 +38,7 @@ class NodeInfo::Serializer < ActiveModel::Serializer
   end
 
   def metadata
-    []
+    {}
   end
 
   private
diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb
index e644a3f91..9a3ca75dc 100644
--- a/app/serializers/rest/account_serializer.rb
+++ b/app/serializers/rest/account_serializer.rb
@@ -14,6 +14,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
 
   attribute :suspended, if: :suspended?
   attribute :silenced, key: :limited, if: :silenced?
+  attribute :noindex, if: :local?
 
   class FieldSerializer < ActiveModel::Serializer
     include FormattingHelper
@@ -107,7 +108,11 @@ class REST::AccountSerializer < ActiveModel::Serializer
     object.silenced?
   end
 
-  delegate :suspended?, :silenced?, to: :object
+  def noindex
+    object.user_prefers_noindex?
+  end
+
+  delegate :suspended?, :silenced?, :local?, to: :object
 
   def moved_and_not_nested?
     object.moved? && object.moved_to_account.moved_to_account_id.nil?
diff --git a/app/serializers/rest/admin/account_serializer.rb b/app/serializers/rest/admin/account_serializer.rb
index 3480e8c5a..ad98a53e8 100644
--- a/app/serializers/rest/admin/account_serializer.rb
+++ b/app/serializers/rest/admin/account_serializer.rb
@@ -3,7 +3,7 @@
 class REST::Admin::AccountSerializer < ActiveModel::Serializer
   attributes :id, :username, :domain, :created_at,
              :email, :ip, :role, :confirmed, :suspended,
-             :silenced, :disabled, :approved, :locale,
+             :silenced, :sensitized, :disabled, :approved, :locale,
              :invite_request
 
   attribute :created_by_application_id, if: :created_by_application?
@@ -32,6 +32,10 @@ class REST::Admin::AccountSerializer < ActiveModel::Serializer
     object.silenced?
   end
 
+  def sensitized
+    object.sensitized?
+  end
+
   def confirmed
     object.user_confirmed?
   end
@@ -77,6 +81,6 @@ class REST::Admin::AccountSerializer < ActiveModel::Serializer
   end
 
   def ip
-    ips&.first
+    ips&.first&.ip
   end
 end
diff --git a/app/serializers/rest/admin/canonical_email_block_serializer.rb b/app/serializers/rest/admin/canonical_email_block_serializer.rb
new file mode 100644
index 000000000..fe385940a
--- /dev/null
+++ b/app/serializers/rest/admin/canonical_email_block_serializer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class REST::Admin::CanonicalEmailBlockSerializer < ActiveModel::Serializer
+  attributes :id, :canonical_email_hash
+
+  def id
+    object.id.to_s
+  end
+end
diff --git a/app/serializers/rest/admin/email_domain_block_serializer.rb b/app/serializers/rest/admin/email_domain_block_serializer.rb
new file mode 100644
index 000000000..a026ff680
--- /dev/null
+++ b/app/serializers/rest/admin/email_domain_block_serializer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class REST::Admin::EmailDomainBlockSerializer < ActiveModel::Serializer
+  attributes :id, :domain, :created_at, :history
+
+  def id
+    object.id.to_s
+  end
+end
diff --git a/app/serializers/rest/admin/ip_block_serializer.rb b/app/serializers/rest/admin/ip_block_serializer.rb
new file mode 100644
index 000000000..6a38f8b56
--- /dev/null
+++ b/app/serializers/rest/admin/ip_block_serializer.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class REST::Admin::IpBlockSerializer < ActiveModel::Serializer
+  attributes :id, :ip, :severity, :comment,
+             :created_at, :expires_at
+
+  def id
+    object.id.to_s
+  end
+
+  def ip
+    "#{object.ip}/#{object.ip.prefix}"
+  end
+end
diff --git a/app/serializers/rest/domain_block_serializer.rb b/app/serializers/rest/domain_block_serializer.rb
new file mode 100644
index 000000000..678463e13
--- /dev/null
+++ b/app/serializers/rest/domain_block_serializer.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class REST::DomainBlockSerializer < ActiveModel::Serializer
+  attributes :domain, :digest, :severity, :comment
+
+  def domain
+    object.public_domain
+  end
+
+  def digest
+    object.domain_digest
+  end
+
+  def comment
+    object.public_comment if instance_options[:with_comment]
+  end
+end
diff --git a/app/serializers/rest/extended_description_serializer.rb b/app/serializers/rest/extended_description_serializer.rb
new file mode 100644
index 000000000..c0fa3450d
--- /dev/null
+++ b/app/serializers/rest/extended_description_serializer.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class REST::ExtendedDescriptionSerializer < ActiveModel::Serializer
+  attributes :updated_at, :content
+
+  def updated_at
+    object.updated_at&.iso8601
+  end
+
+  def content
+    if object.text.present?
+      markdown.render(object.text)
+    else
+      ''
+    end
+  end
+
+  private
+
+  def markdown
+    @markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML)
+  end
+end
diff --git a/app/serializers/rest/featured_tag_serializer.rb b/app/serializers/rest/featured_tag_serializer.rb
index 8abcd9b90..c4b35ab03 100644
--- a/app/serializers/rest/featured_tag_serializer.rb
+++ b/app/serializers/rest/featured_tag_serializer.rb
@@ -16,4 +16,12 @@ class REST::FeaturedTagSerializer < ActiveModel::Serializer
   def name
     object.display_name
   end
+
+  def statuses_count
+    object.statuses_count.to_s
+  end
+
+  def last_status_at
+    object.last_status_at&.to_date&.iso8601
+  end
 end
diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb
index 575c6214e..5ae1099d0 100644
--- a/app/serializers/rest/instance_serializer.rb
+++ b/app/serializers/rest/instance_serializer.rb
@@ -1,74 +1,56 @@
 # frozen_string_literal: true
 
 class REST::InstanceSerializer < ActiveModel::Serializer
-  include RoutingHelper
-
-  attributes :uri, :title, :short_description, :description, :email,
-             :version, :urls, :stats, :thumbnail, :max_toot_chars, :poll_limits,
-             :languages, :registrations, :approval_required, :invites_enabled,
-             :configuration
-
-  has_one :contact_account, serializer: REST::AccountSerializer
-
-  has_many :rules, serializer: REST::RuleSerializer
-
-  delegate :contact_account, :rules, to: :instance_presenter
-
-  def uri
-    Rails.configuration.x.local_domain
-  end
-
-  def title
-    Setting.site_title
-  end
+  class ContactSerializer < ActiveModel::Serializer
+    attributes :email
 
-  def short_description
-    Setting.site_short_description
+    has_one :account, serializer: REST::AccountSerializer
   end
 
-  def description
-    Setting.site_description
-  end
+  include RoutingHelper
 
-  def email
-    Setting.site_contact_email
-  end
+  attributes :domain, :title, :version, :source_url, :description,
+             :usage, :thumbnail, :languages, :configuration,
+             :registrations
 
-  def version
-    Mastodon::Version.to_s
-  end
+  has_one :contact, serializer: ContactSerializer
+  has_many :rules, serializer: REST::RuleSerializer
 
   def thumbnail
-    instance_presenter.thumbnail ? full_asset_url(instance_presenter.thumbnail.file.url) : full_pack_url('media/images/preview.png')
-  end
-
-  def max_toot_chars
-    StatusLengthValidator::MAX_CHARS
-  end
-
-  def poll_limits
+    if object.thumbnail
+      {
+        url: full_asset_url(object.thumbnail.file.url(:'@1x')),
+        blurhash: object.thumbnail.blurhash,
+        versions: {
+          '@1x': full_asset_url(object.thumbnail.file.url(:'@1x')),
+          '@2x': full_asset_url(object.thumbnail.file.url(:'@2x')),
+        },
+      }
+    else
+      {
+        url: full_pack_url('media/images/preview.png'),
+      }
+    end
+  end
+
+  def usage
     {
-      max_options: PollValidator::MAX_OPTIONS,
-      max_option_chars: PollValidator::MAX_OPTION_CHARS,
-      min_expiration: PollValidator::MIN_EXPIRATION,
-      max_expiration: PollValidator::MAX_EXPIRATION,
+      users: {
+        active_month: object.active_user_count(4),
+      },
     }
   end
 
-  def stats
+  def configuration
     {
-      user_count: instance_presenter.user_count,
-      status_count: instance_presenter.status_count,
-      domain_count: instance_presenter.domain_count,
-    }
-  end
+      urls: {
+        streaming: Rails.configuration.x.streaming_api_base_url,
+      },
 
-  def urls
-    { streaming_api: Rails.configuration.x.streaming_api_base_url }
-  end
+      accounts: {
+        max_featured_tags: FeaturedTag::LIMIT,
+      },
 
-  def configuration
-    {
       statuses: {
         max_characters: StatusLengthValidator::MAX_CHARS,
         max_media_attachments: 4,
@@ -90,28 +72,36 @@ class REST::InstanceSerializer < ActiveModel::Serializer
         min_expiration: PollValidator::MIN_EXPIRATION,
         max_expiration: PollValidator::MAX_EXPIRATION,
       },
-    }
-  end
 
-  def languages
-    [I18n.default_locale]
+      translation: {
+        enabled: TranslationService.configured?,
+      },
+    }
   end
 
   def registrations
-    Setting.registrations_mode != 'none' && !Rails.configuration.x.single_user_mode
+    {
+      enabled: registrations_enabled?,
+      approval_required: Setting.registrations_mode == 'approved',
+      message: registrations_enabled? ? nil : registrations_message,
+    }
   end
 
-  def approval_required
-    Setting.registrations_mode == 'approved'
-  end
+  private
 
-  def invites_enabled
-    UserRole.everyone.can?(:invite_users)
+  def registrations_enabled?
+    Setting.registrations_mode != 'none' && !Rails.configuration.x.single_user_mode
   end
 
-  private
+  def registrations_message
+    if Setting.closed_registrations_message.present?
+      markdown.render(Setting.closed_registrations_message)
+    else
+      nil
+    end
+  end
 
-  def instance_presenter
-    @instance_presenter ||= InstancePresenter.new
+  def markdown
+    @markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML, no_images: true)
   end
 end
diff --git a/app/serializers/rest/privacy_policy_serializer.rb b/app/serializers/rest/privacy_policy_serializer.rb
new file mode 100644
index 000000000..f0572e714
--- /dev/null
+++ b/app/serializers/rest/privacy_policy_serializer.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class REST::PrivacyPolicySerializer < ActiveModel::Serializer
+  attributes :updated_at, :content
+
+  def updated_at
+    object.updated_at.iso8601
+  end
+
+  def content
+    markdown.render(object.text % { domain: Rails.configuration.x.local_domain })
+  end
+
+  private
+
+  def markdown
+    @markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML, escape_html: true, no_images: true)
+  end
+end
diff --git a/app/serializers/rest/relationship_serializer.rb b/app/serializers/rest/relationship_serializer.rb
index afd4cddf9..31fc60eb2 100644
--- a/app/serializers/rest/relationship_serializer.rb
+++ b/app/serializers/rest/relationship_serializer.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
 class REST::RelationshipSerializer < ActiveModel::Serializer
-  attributes :id, :following, :showing_reblogs, :notifying, :followed_by,
+  attributes :id, :following, :showing_reblogs, :notifying, :languages, :followed_by,
              :blocking, :blocked_by, :muting, :muting_notifications, :requested,
              :domain_blocking, :endorsed, :note
 
@@ -25,6 +25,11 @@ class REST::RelationshipSerializer < ActiveModel::Serializer
       false
   end
 
+  def languages
+    (instance_options[:relationships].following[object.id] || {})[:languages] ||
+      (instance_options[:relationships].requested[object.id] || {})[:languages]
+  end
+
   def followed_by
     instance_options[:relationships].followed_by[object.id] || false
   end
diff --git a/app/serializers/rest/report_serializer.rb b/app/serializers/rest/report_serializer.rb
index de68dfc6d..f4e9af249 100644
--- a/app/serializers/rest/report_serializer.rb
+++ b/app/serializers/rest/report_serializer.rb
@@ -9,4 +9,12 @@ class REST::ReportSerializer < ActiveModel::Serializer
   def id
     object.id.to_s
   end
+
+  def status_ids
+    object&.status_ids&.map(&:to_s)
+  end
+
+  def rule_ids
+    object&.rule_ids&.map(&:to_s)
+  end
 end
diff --git a/app/serializers/rest/translation_serializer.rb b/app/serializers/rest/translation_serializer.rb
new file mode 100644
index 000000000..05ededc95
--- /dev/null
+++ b/app/serializers/rest/translation_serializer.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class REST::TranslationSerializer < ActiveModel::Serializer
+  attributes :content, :detected_source_language, :provider
+
+  def content
+    object.text
+  end
+end
diff --git a/app/serializers/rest/v1/instance_serializer.rb b/app/serializers/rest/v1/instance_serializer.rb
new file mode 100644
index 000000000..389ec7dff
--- /dev/null
+++ b/app/serializers/rest/v1/instance_serializer.rb
@@ -0,0 +1,119 @@
+# frozen_string_literal: true
+
+class REST::V1::InstanceSerializer < ActiveModel::Serializer
+  include RoutingHelper
+
+  attributes :uri, :title, :short_description, :description, :email,
+             :version, :urls, :stats, :thumbnail, :max_toot_chars, :poll_limits,
+             :languages, :registrations, :approval_required, :invites_enabled,
+             :configuration
+
+  has_one :contact_account, serializer: REST::AccountSerializer
+
+  has_many :rules, serializer: REST::RuleSerializer
+
+  def uri
+    object.domain
+  end
+
+  def short_description
+    object.description
+  end
+
+  def description
+    Setting.site_description # Legacy
+  end
+
+  def email
+    object.contact.email
+  end
+
+  def contact_account
+    object.contact.account
+  end
+
+  def thumbnail
+    instance_presenter.thumbnail ? full_asset_url(instance_presenter.thumbnail.file.url(:'@1x')) : full_pack_url('media/images/preview.png')
+  end
+
+  def max_toot_chars
+    StatusLengthValidator::MAX_CHARS
+  end
+
+  def poll_limits
+    {
+      max_options: PollValidator::MAX_OPTIONS,
+      max_option_chars: PollValidator::MAX_OPTION_CHARS,
+      min_expiration: PollValidator::MIN_EXPIRATION,
+      max_expiration: PollValidator::MAX_EXPIRATION,
+    }
+  end
+
+  def stats
+    {
+      user_count: instance_presenter.user_count,
+      status_count: instance_presenter.status_count,
+      domain_count: instance_presenter.domain_count,
+    }
+  end
+
+  def urls
+    { streaming_api: Rails.configuration.x.streaming_api_base_url }
+  end
+
+  def usage
+    {
+      users: {
+        active_month: instance_presenter.active_user_count(4),
+      },
+    }
+  end
+
+  def configuration
+    {
+      accounts: {
+        max_featured_tags: FeaturedTag::LIMIT,
+      },
+
+      statuses: {
+        max_characters: StatusLengthValidator::MAX_CHARS,
+        max_media_attachments: 4,
+        characters_reserved_per_url: StatusLengthValidator::URL_PLACEHOLDER_CHARS,
+      },
+
+      media_attachments: {
+        supported_mime_types: MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES + MediaAttachment::AUDIO_MIME_TYPES,
+        image_size_limit: MediaAttachment::IMAGE_LIMIT,
+        image_matrix_limit: Attachmentable::MAX_MATRIX_LIMIT,
+        video_size_limit: MediaAttachment::VIDEO_LIMIT,
+        video_frame_rate_limit: MediaAttachment::MAX_VIDEO_FRAME_RATE,
+        video_matrix_limit: MediaAttachment::MAX_VIDEO_MATRIX_LIMIT,
+      },
+
+      polls: {
+        max_options: PollValidator::MAX_OPTIONS,
+        max_characters_per_option: PollValidator::MAX_OPTION_CHARS,
+        min_expiration: PollValidator::MIN_EXPIRATION,
+        max_expiration: PollValidator::MAX_EXPIRATION,
+      },
+    }
+  end
+
+  def registrations
+    Setting.registrations_mode != 'none' && !Rails.configuration.x.single_user_mode
+  end
+
+  def approval_required
+    Setting.registrations_mode == 'approved'
+  end
+
+  def invites_enabled
+    UserRole.everyone.can?(:invite_users)
+  end
+
+  private
+
+  def instance_presenter
+    @instance_presenter ||= InstancePresenter.new
+  end
+end