From eee6587a90d69b5a3fac6109f285f955facd21e6 Mon Sep 17 00:00:00 2001 From: gled Date: Fri, 12 Jul 2019 12:35:50 -0700 Subject: [PATCH] Implement https://github.com/florence-social/mastodon-fork/pull/134 --- .../well_known/node_info_controller.rb | 17 ++++ app/presenters/instance_presenter.rb | 4 + app/serializers/node_discovery_serializer.rb | 20 +++++ app/serializers/node_info_serializer.rb | 85 +++++++++++++++++++ config/routes.rb | 5 +- .../well_known/node_info_controller_spec.rb | 40 +++++++++ 6 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 app/controllers/well_known/node_info_controller.rb create mode 100644 app/serializers/node_discovery_serializer.rb create mode 100644 app/serializers/node_info_serializer.rb create mode 100644 spec/controllers/well_known/node_info_controller_spec.rb diff --git a/app/controllers/well_known/node_info_controller.rb b/app/controllers/well_known/node_info_controller.rb new file mode 100644 index 00000000000000..e44a08cbc2fd06 --- /dev/null +++ b/app/controllers/well_known/node_info_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module WellKnown + class NodeInfoController < ActionController::Base + include RoutingHelper + + before_action { response.headers['Vary'] = 'Accept' } + + def index + render json: ActiveModelSerializers::SerializableResource.new({}, serializer: NodeDiscoverySerializer) + end + + def show + render json: ActiveModelSerializers::SerializableResource.new({}, serializer: NodeInfoSerializer, version: "2.#{ params[:format] }") + end + end +end diff --git a/app/presenters/instance_presenter.rb b/app/presenters/instance_presenter.rb index f3a73209afe566..8179fb1724bfc1 100644 --- a/app/presenters/instance_presenter.rb +++ b/app/presenters/instance_presenter.rb @@ -12,6 +12,10 @@ class InstancePresenter to: Setting ) + def active_count(timespan: Time.zone.now - 1.month..Time.zone.now) + Status.select('distinct (account_id)').where(local: true, created_at: timespan).count + end + def contact_account Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, '')) end diff --git a/app/serializers/node_discovery_serializer.rb b/app/serializers/node_discovery_serializer.rb new file mode 100644 index 00000000000000..d6e4529cfa1cc7 --- /dev/null +++ b/app/serializers/node_discovery_serializer.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class NodeDiscoverySerializer < ActiveModel::Serializer + include RoutingHelper + + attributes :links + + def links + [ + { + rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0', + href: node_info_schema_url('2.0'), + }, + { + rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1', + href: node_info_schema_url('2.1'), + }, + ] + end +end diff --git a/app/serializers/node_info_serializer.rb b/app/serializers/node_info_serializer.rb new file mode 100644 index 00000000000000..fd261d5c931e9a --- /dev/null +++ b/app/serializers/node_info_serializer.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +class NodeInfoSerializer < ActiveModel::Serializer + include RoutingHelper + + attributes :version, :usage, :software, :services, + :protocols, :openRegistrations, :metadata + + def version + object.adapter.serializer.instance_options[:version] + end + + def usage + { + users: { + total: instance_presenter.user_count, + activeHalfyear: instance_presenter.active_count(timespan: Time.zone.now - 6.months..Time.zone.now), + activeMonth: instance_presenter.active_count, + }, + localPosts: instance_presenter.status_count, + } + end + + def software + sw = { + version: Mastodon::Version.to_s, + name: 'mastodon' + } + sw[:repository] = Mastodon::Version.source_base_url if version == '2.1' + sw + end + + def services + { + outbound: [], + inbound: [], + } + end + + def protocols + %w(ostatus activitypub) + end + + def openRegistrations + Setting.open_registrations + end + + def metadata + { + nodeName: instance_presenter.site_title, + nodeDescription: instance_presenter.site_description, + nodeTerms: instance_presenter.site_terms, + siteContactEmail: instance_presenter.site_contact_email, + domain_count: instance_presenter.domain_count, + features: features, + invitesEnabled: Setting.min_invite_role != 'admin', + federation: federation, + } + end + + def features + %w(mastodon_api mastodon_api_streaming) + end + + def federation + domains = DomainBlock.all + feds = { + reject_media: [], + reject_reports: [], + } + domains.each do |domain| + feds[domain.severity] = [] unless feds.keys.include?(domain.severity) + feds[domain.severity] << domain.domain + feds[:reject_media] << domain.domain if domain.reject_media + feds[:reject_reports] << domain.domain if domain.reject_reports + end + feds + end + + private + + def instance_presenter + @instance_presenter ||= InstancePresenter.new + end +end diff --git a/config/routes.rb b/config/routes.rb index e00d3dfac3a667..3fc520dbcd3906 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -19,7 +19,10 @@ tokens: 'oauth/tokens' end - get '.well-known/host-meta', to: 'well_known/host_meta#show', as: :host_meta, defaults: { format: 'xml' } + #get '.well-known/host-meta', to: 'well_known/host_meta#show', as: :host_meta, defaults: { format: 'xml' } + get '.well-known/host-meta', to: 'well_known/host_meta#show', as: :host_meta, defaults: { format: 'xml' } + get '.well-known/nodeinfo', to: 'well_known/node_info#index', as: :node_info, defaults: { format: 'json' } + get 'nodeinfo/:version_number', to: 'well_known/node_info#show', as: :node_info_schema, defaults: { format: 'json' } get '.well-known/webfinger', to: 'well_known/webfinger#show', as: :webfinger get '.well-known/change-password', to: redirect('/auth/edit') get '.well-known/keybase-proof-config', to: 'well_known/keybase_proof_config#show' diff --git a/spec/controllers/well_known/node_info_controller_spec.rb b/spec/controllers/well_known/node_info_controller_spec.rb new file mode 100644 index 00000000000000..36f4f88b5914aa --- /dev/null +++ b/spec/controllers/well_known/node_info_controller_spec.rb @@ -0,0 +1,40 @@ +require 'rails_helper' + +describe WellKnown::NodeInfoController, type: :controller do + render_views + + describe 'GET #index' do + it 'returns json document pointing to node info' do + get :index + + json_response = JSON.parse(response.body) + + expect(response).to have_http_status(200) + expect(response.content_type).to eq 'application/json' + expect(json_response.keys.include?('links')).to be true + expect(json_response['links'][0]['rel']).to eq 'http://nodeinfo.diaspora.software/ns/schema/2.0' + expect(json_response['links'][0]['href'].include?('nodeinfo/2.0')).to be true + expect(json_response['links'][0]['rel']).to eq 'http://nodeinfo.diaspora.software/ns/schema/2.1' + expect(json_response['links'][0]['href'].include?('nodeinfo/2.1')).to be true + end + end + + describe 'GET #show' do + it 'returns json document pointing to node info' do + get :show, params: { version_number: 2 } + + json_response = JSON.parse(response.body) + + expect(response).to have_http_status(200) + expect(response.content_type).to eq 'application/json' + expect(json_response.keys.include?('version')).to be true + expect(json_response.keys.include?('usage')).to be true + expect(json_response.keys.include?('software')).to be true + expect(json_response.keys.include?('services')).to be true + expect(json_response.keys.include?('protocols')).to be true + expect(json_response.keys.include?('openRegistrations')).to be true + expect(json_response.keys.include?('usage')).to be true + expect(json_response.keys.include?('metaData')).to be true + end + end +end