diff --git a/Gemfile b/Gemfile index dcfa1234f..bc5c790e5 100644 --- a/Gemfile +++ b/Gemfile @@ -18,6 +18,7 @@ gem "diaspora_federation-rails", "0.0.3" gem "acts_as_api", "0.4.2" gem "json", "1.8.3" +gem "json-schema", "2.5.1" # Authentication diff --git a/Gemfile.lock b/Gemfile.lock index 37a6b20eb..c8da2359f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -395,6 +395,8 @@ GEM multi_json (>= 1.3) rake json (1.8.3) + json-schema (2.5.1) + addressable (~> 2.3.7) jwt (1.5.0) kaminari (0.16.3) actionpack (>= 3.0.0) @@ -813,6 +815,7 @@ DEPENDENCIES js_image_paths (= 0.0.2) jshintrb (= 0.3.0) json (= 1.8.3) + json-schema (= 2.5.1) logging-rails (= 0.5.0) markerb (= 1.0.2) messagebus_ruby_api (= 1.0.3) @@ -897,4 +900,4 @@ DEPENDENCIES will_paginate (= 3.0.7) BUNDLED WITH - 1.10.5 + 1.10.6 diff --git a/app/assets/stylesheets/_application.scss b/app/assets/stylesheets/_application.scss index 003001755..a215136f7 100644 --- a/app/assets/stylesheets/_application.scss +++ b/app/assets/stylesheets/_application.scss @@ -91,6 +91,6 @@ @import 'highlightjs/github'; /* statistics */ -@import 'new_styles/statistics'; +@import 'statistics'; @import "bootstrap3-switch"; diff --git a/app/assets/stylesheets/new_styles/_statistics.scss b/app/assets/stylesheets/statistics.scss similarity index 93% rename from app/assets/stylesheets/new_styles/_statistics.scss rename to app/assets/stylesheets/statistics.scss index 26bb1f9b7..f2f700c24 100644 --- a/app/assets/stylesheets/new_styles/_statistics.scss +++ b/app/assets/stylesheets/statistics.scss @@ -1,4 +1,4 @@ -.page-statistics { +.page-node_info.action-statistics { h1{ text-align: center; } h3{ diff --git a/app/controllers/node_info_controller.rb b/app/controllers/node_info_controller.rb new file mode 100644 index 000000000..bb0aaa14f --- /dev/null +++ b/app/controllers/node_info_controller.rb @@ -0,0 +1,24 @@ +class NodeInfoController < ApplicationController + respond_to :json + respond_to :html, only: :statistics + + def jrd + render json: NodeInfo.jrd(CGI.unescape(node_info_url("123.123").sub("123.123", "%{version}"))) + end + + def document + if NodeInfo.supported_version?(params[:version]) + document = NodeInfoPresenter.new(params[:version]) + render json: document, content_type: document.content_type + else + head :not_found + end + end + + def statistics + respond_to do |format| + format.json { render json: StatisticsPresenter.new } + format.all { @statistics = NodeInfoPresenter.new("1.0") } + end + end +end diff --git a/app/controllers/statistics_controller.rb b/app/controllers/statistics_controller.rb deleted file mode 100644 index 567a81745..000000000 --- a/app/controllers/statistics_controller.rb +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright (c) 2010-2011, Diaspora Inc. This file is -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. - -class StatisticsController < ApplicationController - respond_to :html, :json - - def statistics - @statistics = StatisticsPresenter.new - respond_to do |format| - format.json { render json: @statistics } - format.all - end - end -end diff --git a/app/presenters/node_info_presenter.rb b/app/presenters/node_info_presenter.rb new file mode 100644 index 000000000..68a4c3c71 --- /dev/null +++ b/app/presenters/node_info_presenter.rb @@ -0,0 +1,104 @@ +class NodeInfoPresenter + delegate :as_json, :content_type, to: :document + + def initialize(version) + @version = version + end + + def document + @document ||= NodeInfo.build do |doc| + doc.version = @version + + add_static_data doc + add_configuration doc + add_user_counts doc.usage.users + add_usage doc.usage + end + end + + def add_configuration(doc) + doc.software.version = version + doc.services = available_services + doc.open_registrations = open_registrations? + doc.metadata["nodeName"] = name + doc.metadata["xmppChat"] = chat_enabled? + end + + def add_static_data(doc) + doc.software.name = "diaspora" + doc.protocols.inbound << "diaspora" + doc.protocols.outbound << "diaspora" + end + + def add_user_counts(doc) + return unless expose_user_counts? + + doc.total = total_users + doc.active_halfyear = halfyear_users + doc.active_month = monthly_users + end + + def add_usage(doc) + doc.local_posts = local_posts if expose_posts_counts? + doc.local_comments = local_comments if expose_comment_counts? + end + + def expose_user_counts? + AppConfig.privacy.statistics.user_counts? + end + + def expose_posts_counts? + AppConfig.privacy.statistics.post_counts? + end + + def expose_comment_counts? + AppConfig.privacy.statistics.comment_counts? + end + + def name + AppConfig.settings.pod_name + end + + def version + AppConfig.version_string + end + + def open_registrations? + AppConfig.settings.enable_registrations? + end + + def chat_enabled? + AppConfig.chat.enabled? + end + + def available_services + Configuration::KNOWN_SERVICES.select {|service| + AppConfig.show_service?(service, nil) + }.map(&:to_s) + end + + def total_users + @total_users ||= User.active.count + end + + def monthly_users + @monthly_users ||= User.monthly_actives.count + end + + def halfyear_users + @halfyear_users ||= User.halfyear_actives.count + end + + def local_posts + @local_posts ||= Post.where(type: "StatusMessage") + .joins(:author) + .where("owner_id IS NOT null") + .count + end + + def local_comments + @local_comments ||= Comment.joins(:author) + .where("owner_id IS NOT null") + .count + end +end diff --git a/app/presenters/statistics_presenter.rb b/app/presenters/statistics_presenter.rb index ba4af719f..e6382850c 100644 --- a/app/presenters/statistics_presenter.rb +++ b/app/presenters/statistics_presenter.rb @@ -2,9 +2,13 @@ # licensed under the Affero General Public License version 3 or later. See # the COPYRIGHT file. -class StatisticsPresenter +# TODO: Drop after 0.6 +class StatisticsPresenter < NodeInfoPresenter + def initialize + super("1.0") + end - def as_json options={} + def as_json(_options={}) base_data.merge(user_counts) .merge(post_counts) .merge(comment_counts) @@ -12,91 +16,34 @@ class StatisticsPresenter def base_data { - 'name' => name, - 'network' => 'Diaspora', - 'version' => version, - 'registrations_open' => open_registrations?, - 'services' => available_services + "name" => name, + "network" => "Diaspora", + "version" => version, + "registrations_open" => open_registrations?, + "services" => available_services } end - def name - AppConfig.settings.pod_name - end - - def version - AppConfig.version_string - end - - def open_registrations? - AppConfig.settings.enable_registrations? - end - def user_counts return {} unless expose_user_counts? { - 'total_users' => total_users, - 'active_users_monthly' => monthly_users, - 'active_users_halfyear' => halfyear_users + "total_users" => total_users, + "active_users_monthly" => monthly_users, + "active_users_halfyear" => halfyear_users } end - def expose_user_counts? - AppConfig.privacy.statistics.user_counts? - end - - def total_users - @total_users ||= User.active.count - end - - def monthly_users - @monthly_users ||= User.monthly_actives.count - end - - def halfyear_users - @halfyear_users ||= User.halfyear_actives.count - end - def post_counts return {} unless expose_posts_counts? { - 'local_posts' => local_posts + "local_posts" => local_posts } end - def local_posts - @local_posts ||= Post.where(type: "StatusMessage") - .joins(:author) - .where("owner_id IS NOT null") - .count - end - - def expose_posts_counts? - AppConfig.privacy.statistics.post_counts? - end - def comment_counts return {} unless expose_comment_counts? { - 'local_comments' => local_comments + "local_comments" => local_comments } end - - def expose_comment_counts? - AppConfig.privacy.statistics.comment_counts? - end - - - def local_comments - @local_comments ||= Comment.joins(:author) - .where("owner_id IS NOT null") - .count - end - - def available_services - Configuration::KNOWN_SERVICES.select {|service| - AppConfig.show_service?(service, nil) - }.map(&:to_s) - end - end diff --git a/app/views/statistics/_statistic.haml b/app/views/node_info/_statistic.haml similarity index 100% rename from app/views/statistics/_statistic.haml rename to app/views/node_info/_statistic.haml diff --git a/app/views/node_info/_statistics.haml b/app/views/node_info/_statistics.haml new file mode 100644 index 000000000..212c19c11 --- /dev/null +++ b/app/views/node_info/_statistics.haml @@ -0,0 +1,21 @@ +-# Copyright (c) 2010-2011, Diaspora Inc. This file is +-# licensed under the Affero General Public License version 3 or later. See +-# the COPYRIGHT file. + +.row + %h1= t("_statistics") + = render "statistic", name: t("statistics.name"), value: @statistics.name, activated: "serv-enabled" + = render "statistic", name: t("statistics.version"), value: @statistics.version, activated: "serv-enabled" + = render "statistic", name: t("statistics.registrations"), value: registrations_status(@statistics), activated: registrations_status_class(@statistics) + - if @statistics.expose_user_counts? + = render "statistic", name: t("statistics.total_users"), value: @statistics.total_users, activated: "serv-enabled" + = render "statistic", name: t("statistics.active_users_halfyear"), value: @statistics.halfyear_users, activated: "serv-enabled" + = render "statistic", name: t("statistics.active_users_monthly"), value: @statistics.monthly_users, activated: "serv-enabled" + - if @statistics.expose_posts_counts? + = render "statistic", name: t("statistics.local_posts"), value: @statistics.local_posts, activated: "serv-enabled" + - if @statistics.expose_comment_counts? + = render "statistic", name: t("statistics.local_comments"), value: @statistics.local_comments, activated: "serv-enabled" +.row + %h1= t("statistics.services") + - Configuration::KNOWN_SERVICES.each do |service| + = render "statistic", name: "#{service.capitalize}", value: service_status(service, @statistics.available_services), activated: service_class(service, @statistics.available_services) diff --git a/app/views/node_info/statistics.html.haml b/app/views/node_info/statistics.html.haml new file mode 100644 index 000000000..ba22e63d8 --- /dev/null +++ b/app/views/node_info/statistics.html.haml @@ -0,0 +1,2 @@ +.container-fluid + = render "statistics" diff --git a/app/views/node_info/statistics.mobile.haml b/app/views/node_info/statistics.mobile.haml new file mode 100644 index 000000000..246fec5d9 --- /dev/null +++ b/app/views/node_info/statistics.mobile.haml @@ -0,0 +1 @@ += render "statistics" diff --git a/app/views/statistics/_statistics.haml b/app/views/statistics/_statistics.haml deleted file mode 100644 index 79fdca056..000000000 --- a/app/views/statistics/_statistics.haml +++ /dev/null @@ -1,23 +0,0 @@ --# Copyright (c) 2010-2011, Diaspora Inc. This file is --# licensed under the Affero General Public License version 3 or later. See --# the COPYRIGHT file. - -.row - %h1 - = t('_statistics') - = render 'statistics/statistic', name: t('statistics.name'), value: @statistics.name, activated: "serv-enabled" - = render 'statistics/statistic', name: t('statistics.version'), value: @statistics.version, activated: "serv-enabled" - = render 'statistics/statistic', name: t('statistics.registrations'), value: registrations_status(@statistics), activated: registrations_status_class(@statistics) - - if @statistics.expose_user_counts? - = render 'statistics/statistic', name: t('statistics.total_users'), value: @statistics.total_users, activated: "serv-enabled" - = render 'statistics/statistic', name: t('statistics.active_users_halfyear'), value: @statistics.halfyear_users, activated: "serv-enabled" - = render 'statistics/statistic', name: t('statistics.active_users_monthly'), value: @statistics.monthly_users, activated: "serv-enabled" - - if @statistics.expose_posts_counts? - = render 'statistics/statistic', name: t('statistics.local_posts'), value: @statistics.local_posts, activated: "serv-enabled" - - if @statistics.expose_comment_counts? - = render 'statistics/statistic', name: t('statistics.local_comments'), value: @statistics.local_comments, activated: "serv-enabled" -.row - %h1 - = t('statistics.services') - - Configuration::KNOWN_SERVICES.each do |service| - = render 'statistics/statistic', name: "#{service.capitalize}", value: service_status(service, @statistics.available_services), activated: service_class(service, @statistics.available_services) diff --git a/app/views/statistics/statistics.html.haml b/app/views/statistics/statistics.html.haml deleted file mode 100644 index 564ae15c7..000000000 --- a/app/views/statistics/statistics.html.haml +++ /dev/null @@ -1,2 +0,0 @@ -.container-fluid - = render('statistics/statistics') diff --git a/app/views/statistics/statistics.mobile.haml b/app/views/statistics/statistics.mobile.haml deleted file mode 100644 index a55da7893..000000000 --- a/app/views/statistics/statistics.mobile.haml +++ /dev/null @@ -1 +0,0 @@ -= render('statistics/statistics') \ No newline at end of file diff --git a/config/load_config.rb b/config/load_config.rb index 5e9985200..e02deb90d 100644 --- a/config/load_config.rb +++ b/config/load_config.rb @@ -26,7 +26,7 @@ AppConfig ||= Configurate::Settings.create do add_provider Configurate::Provider::YAML, File.join(config_dir, "diaspora.yml"), - namespace: rails_env, required: false unless rails_env == "test" + namespace: rails_env, required: false add_provider Configurate::Provider::YAML, File.join(config_dir, "diaspora.yml"), namespace: "configuration", required: false diff --git a/config/routes.rb b/config/routes.rb index 5a179b8e1..6d768a590 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -236,8 +236,10 @@ Diaspora::Application.routes.draw do #Protocol Url get 'protocol' => redirect("http://wiki.diasporafoundation.org/Federation_Protocol_Overview") - #Statistics - get :statistics, controller: :statistics + # NodeInfo + get ".well-known/nodeinfo", to: "node_info#jrd" + get "nodeinfo/:version", to: "node_info#document", as: "node_info", constraints: {version: /\d+\.\d+/} + get "statistics", to: "node_info#statistics" # Terms if AppConfig.settings.terms.enable? diff --git a/lib/node_info.rb b/lib/node_info.rb new file mode 100644 index 000000000..1b53b0991 --- /dev/null +++ b/lib/node_info.rb @@ -0,0 +1,147 @@ +require "pathname" +require "json-schema" + +module NodeInfo + VERSIONS = %w(1.0) + SCHEMAS = {} + private_constant :VERSIONS, :SCHEMAS + + Document = Struct.new(:version, :software, :protocols, :services, :open_registrations, :usage, :metadata) do + Software = Struct.new(:name, :version) do + def initialize(name=nil, version=nil) + super(name, version) + end + + def version_10_hash + { + "name" => name, + "version" => version + } + end + end + + Protocols = Struct.new(:inbound, :outbound) do + def initialize(inbound=[], outbound=[]) + super(inbound, outbound) + end + + def version_10_hash + { + "inbound" => inbound, + "outbound" => outbound + } + end + end + + Usage = Struct.new(:users, :local_posts, :local_comments) do + Users = Struct.new(:total, :active_halfyear, :active_month) do + def initialize(total=nil, active_halfyear=nil, active_month=nil) + super(total, active_halfyear, active_month) + end + + def version_10_hash + { + "total" => total, + "activeHalfyear" => active_halfyear, + "activeMonth" => active_month + } + end + end + + def initialize(local_posts=nil, local_comments=nil) + super(Users.new, local_posts, local_comments) + end + + def version_10_hash + { + "users" => users.version_10_hash, + "localPosts" => local_posts, + "localComments" => local_comments + } + end + end + + def self.build + new.tap do |doc| + yield doc + doc.validate + end + end + + def initialize(version=nil, services=[], open_registrations=nil, metadata={}) + super(version, Software.new, Protocols.new, services, open_registrations, Usage.new, metadata) + end + + def as_json(_options={}) + case version + when "1.0" + version_10_hash + end + end + + def content_type + "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/#{version}#" + end + + def schema + NodeInfo.schema version + end + + def validate + assert NodeInfo.supported_version?(version), "Unknown version #{version}" + JSON::Validator.validate!(schema, as_json) + end + + private + + def assert(condition, message) + raise ArgumentError, message unless condition + end + + def version_10_hash + deep_compact( + "version" => "1.0", + "software" => software.version_10_hash, + "protocols" => protocols.version_10_hash, + "services" => services.empty? ? nil : services, + "openRegistrations" => open_registrations, + "usage" => usage.version_10_hash, + "metadata" => metadata + ) + end + + def deep_compact(hash) + hash.tap do |hash| + hash.reject! {|_, value| + deep_compact value if value.is_a? Hash + value.nil? + } + end + end + end + + def self.schema(version) + SCHEMAS[version] ||= JSON.parse( + Pathname.new(__dir__).join("..", "vendor", "nodeinfo", "schemas", "#{version}.json").expand_path.read + ) + end + + def self.build(&block) + Document.build(&block) + end + + def self.jrd(endpoint) + { + "links" => VERSIONS.map {|version| + { + "rel" => "http://nodeinfo.diaspora.software/ns/schema/#{version}", + "href" => endpoint % {version: version} + } + } + } + end + + def self.supported_version?(version) + VERSIONS.include? version + end +end diff --git a/spec/controllers/node_info_controller_spec.rb b/spec/controllers/node_info_controller_spec.rb new file mode 100644 index 000000000..106a5a1c8 --- /dev/null +++ b/spec/controllers/node_info_controller_spec.rb @@ -0,0 +1,77 @@ +require "spec_helper" + +describe NodeInfoController do + describe "#jrd" do + it "responds to JSON" do + get :jrd, format: :json + + expect(response).to be_success + end + + it "returns a JRD" do + expect(NodeInfo).to receive(:jrd).with(include("%{version}")).and_call_original + + get :jrd, format: :json + + jrd = JSON.parse(response.body) + expect(jrd).to include "links" => [{ + "rel" => "http://nodeinfo.diaspora.software/ns/schema/1.0", + "href" => node_info_url("1.0") + }] + end + end + + describe "#document" do + context "invalid version" do + it "responds with not found" do + get :document, version: "0.0", format: :json + + expect(response.code).to eq "404" + end + end + + context "version 1.0" do + it "responds to JSON" do + get :document, version: "1.0", format: :json + + expect(response).to be_success + end + + it "calls NodeInfoPresenter" do + expect(NodeInfoPresenter).to receive(:new).with("1.0") + .and_return(double(as_json: {}, content_type: "application/json")) + + get :document, version: "1.0", format: :json + end + + it "notes the schema in the content type" do + get :document, version: "1.0", format: :json + + expect(response.content_type).to eq "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/1.0#" + end + end + end + + describe "#statistics" do + it "responds to format json" do + get :statistics, format: "json" + expect(response.code).to eq("200") + end + + it "contains json" do + get :statistics, format: "json" + json = JSON.parse(response.body) + expect(json["name"]).to be_present + end + + it "responds to html" do + get :statistics, format: "html" + expect(response.code).to eq("200") + end + + it "responds to mobile" do + get :statistics, format: "mobile" + expect(response.code).to eq("200") + end + end +end diff --git a/spec/controllers/statistics_controller_spec.rb b/spec/controllers/statistics_controller_spec.rb deleted file mode 100644 index 07abf7370..000000000 --- a/spec/controllers/statistics_controller_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright (c) 2010-2011, Diaspora Inc. This file is -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. - -require "spec_helper" - -describe StatisticsController, type: :controller do - describe "#statistics" do - it "responds to format json" do - get :statistics, format: "json" - expect(response.code).to eq("200") - end - - it "contains json" do - get :statistics, format: "json" - json = JSON.parse(response.body) - expect(json["name"]).to be_present - end - - it "responds to html" do - get :statistics, format: "html" - expect(response.code).to eq("200") - end - - it "responds to mobile" do - get :statistics, format: "mobile" - expect(response.code).to eq("200") - end - end -end diff --git a/spec/presenters/node_info_presenter_spec.rb b/spec/presenters/node_info_presenter_spec.rb new file mode 100644 index 000000000..500793d3b --- /dev/null +++ b/spec/presenters/node_info_presenter_spec.rb @@ -0,0 +1,131 @@ +require "spec_helper" + +describe NodeInfoPresenter do + let(:presenter) { NodeInfoPresenter.new("1.0") } + let(:hash) { presenter.as_json.as_json } + + describe "#as_json" do + it "works" do + expect(hash).to be_present + expect(presenter.to_json).to be_a String + end + end + + describe "node info contents" do + before do + AppConfig.privacy.statistics.user_counts = false + AppConfig.privacy.statistics.post_counts = false + AppConfig.privacy.statistics.comment_counts = false + end + + it "provides generic pod data in json" do + expect(hash).to eq( + "version" => "1.0", + "software" => { + "name" => "diaspora", + "version" => AppConfig.version_string + }, + "protocols" => { + "inbound" => ["diaspora"], + "outbound" => ["diaspora"] + }, + "services" => ["facebook"], + "openRegistrations" => AppConfig.settings.enable_registrations?, + "usage" => { + "users" => {} + }, + "metadata" => { + "nodeName" => AppConfig.settings.pod_name, + "xmppChat" => AppConfig.chat.enabled? + } + ) + end + + context "when services are enabled" do + before do + AppConfig.services = { + "facebook" => { + "enable" => true, + "authorized" => true + }, + "twitter" => {"enable" => true}, + "wordpress" => {"enable" => false}, + "tumblr" => { + "enable" => true, + "authorized" => false + } + } + end + + it "provides services" do + expect(hash).to include "services" => %w(twitter facebook) + end + end + + context "when some services are set to username authorized" do + before do + AppConfig.services = { + "facebook" => { + "enable" => true, + "authorized" => "bob" + }, + "twitter" => {"enable" => true}, + "wordpress" => { + "enable" => true, + "authorized" => "alice" + }, + "tumblr" => { + "enable" => true, + "authorized" => false + } + } + end + + it "it doesn't list those" do + expect(hash).to include "services" => ["twitter"] + end + end + + context "when counts are enabled" do + before do + AppConfig.privacy.statistics.user_counts = true + AppConfig.privacy.statistics.post_counts = true + AppConfig.privacy.statistics.comment_counts = true + end + + it "provides generic pod data and counts in json" do + expect(hash).to include( + "usage" => { + "users" => { + "total" => User.active.count, + "activeHalfyear" => User.halfyear_actives.count, + "activeMonth" => User.monthly_actives.count + }, + "localPosts" => presenter.local_posts, + "localComments" => presenter.local_comments + } + ) + end + end + + context "when registrations are closed" do + before do + AppConfig.settings.enable_registrations = false + end + + it "should mark open_registrations to be false" do + expect(presenter.open_registrations?).to be false + end + end + + context "when chat is enabled" do + before do + AppConfig.chat.enabled = true + end + + it "should mark the xmppChat metadata as true" do + expect(hash).to include "metadata" => include("xmppChat" => true) + end + end + end +end diff --git a/spec/presenters/statistics_presenter_spec.rb b/spec/presenters/statistics_presenter_spec.rb index 53e857715..e1d7f7214 100644 --- a/spec/presenters/statistics_presenter_spec.rb +++ b/spec/presenters/statistics_presenter_spec.rb @@ -108,15 +108,5 @@ describe StatisticsPresenter do ) end end - - context "when registrations are closed" do - before do - AppConfig.settings.enable_registrations = false - end - - it "should mark open_registrations to be false" do - expect(@presenter.open_registrations?).to be false - end - end end end diff --git a/vendor/nodeinfo/schemas/1.0.json b/vendor/nodeinfo/schemas/1.0.json new file mode 100644 index 000000000..de28dcb8e --- /dev/null +++ b/vendor/nodeinfo/schemas/1.0.json @@ -0,0 +1,182 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "http://nodeinfo.diaspora.software/ns/schema/1.0#", + "description": "NodeInfo schema version 1.0.", + "type": "object", + "additionalProperties": false, + "required": [ + "version", + "software", + "protocols", + "openRegistrations", + "usage", + "metadata" + ], + "properties": { + "version": { + "description": "The schema version, must be 1.0.", + "enum": [ + "1.0" + ] + }, + "software": { + "description": "Metadata about server software in use.", + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "version" + ], + "properties": { + "name": { + "description": "The canonical name of this server software.", + "enum": [ + "diaspora", + "friendica", + "redmatrix" + ] + }, + "version": { + "description": "The version of this server software.", + "type": "string" + } + } + }, + "protocols": { + "description": "The protocols supported on this server.", + "type": "object", + "additionalProperties": false, + "required": [ + "inbound", + "outbound" + ], + "properties": { + "inbound": { + "description": "The protocols this server can receive traffic for.", + "type": "array", + "minItems": 1, + "items": { + "enum": [ + "buddycloud", + "diaspora", + "friendica", + "gnusocial", + "libertree", + "mediagoblin", + "pumpio", + "redmatrix", + "smtp", + "tent" + ] + } + }, + "outbound": { + "description": "The protocols this server can generate traffic for.", + "type": "array", + "minItems": 1, + "items": { + "enum": [ + "buddycloud", + "diaspora", + "friendica", + "gnusocial", + "libertree", + "mediagoblin", + "pumpio", + "redmatrix", + "smtp", + "tent" + ] + } + } + } + }, + "services": { + "description": "The third party sites this servers allows to publish messages to.", + "type": "array", + "minItems": 0, + "items": { + "enum": [ + "appnet", + "blogger", + "buddycloud", + "diaspora", + "dreamwidth", + "drupal", + "facebook", + "friendica", + "gnusocial", + "google", + "insanejournal", + "libertree", + "linkedin", + "livejournal", + "mediagoblin", + "myspace", + "pinterest", + "posterous", + "pumpio", + "redmatrix", + "smtp", + "tent", + "tumblr", + "twitter", + "wordpress", + "xmpp" + ] + } + }, + "openRegistrations": { + "description": "Whether this server allows open self-registration.", + "type": "boolean" + }, + "usage": { + "description": "Usage statistics for this server.", + "type": "object", + "additionalProperties": false, + "required": [ + "users" + ], + "properties": { + "users": { + "description": "statistics about the users of this server.", + "type": "object", + "additionalProperties": false, + "properties": { + "total": { + "description": "The total amount of on this server registered users.", + "type": "integer", + "minimum": 0 + }, + "activeHalfyear": { + "description": "The amount of users that signed in at least once in the last 180 days.", + "type": "integer", + "minimum": 0 + }, + "activeMonth": { + "description": "The amount of users that signed in at least once in the last 30 days.", + "type": "integer", + "minimum": 0 + } + } + }, + "localPosts": { + "description": "The amount of posts that were made by users that are registered on this server.", + "type": "integer", + "minimum": 0 + }, + "localComments": { + "description": "The amount of comments that were made by users that are registered on this server.", + "type": "integer", + "minimum": 0 + } + } + }, + "metadata": { + "description": "Free form key value pairs for software specific values. Clients should not rely on any specific key present.", + "type": "object", + "minProperties": 0, + "additionalProperties": true + } + } +}