From 1b57d3e36e4e13bc13c2235731be78de51b9b4a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ciar=C3=A1n=20Ainsworth?= Date: Sun, 18 Jun 2023 14:46:01 +0200 Subject: [PATCH] docs: Add NodeInfo 2.1 specification and schema Part-of: --- changes/changelog.d/nodeinfo.doc | 1 + docs/conf.py | 10 +- docs/index.md | 11 + docs/specs/nodeinfo21/index.md | 218 +++++++++++ docs/specs/nodeinfo21/schema.yml | 599 +++++++++++++++++++++++++++++++ 5 files changed, 837 insertions(+), 2 deletions(-) create mode 100644 changes/changelog.d/nodeinfo.doc create mode 100644 docs/specs/nodeinfo21/index.md create mode 100644 docs/specs/nodeinfo21/schema.yml diff --git a/changes/changelog.d/nodeinfo.doc b/changes/changelog.d/nodeinfo.doc new file mode 100644 index 000000000..de897bfc3 --- /dev/null +++ b/changes/changelog.d/nodeinfo.doc @@ -0,0 +1 @@ +Added NodeInfo 2.1 specification diff --git a/docs/conf.py b/docs/conf.py index 964b52f2a..819d9a708 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -69,13 +69,19 @@ templates_path = ["_templates"] # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = ".rst" +source_suffix = ".md" # The root toctree document. root_doc = "index" # Enable colon fences -myst_enable_extensions = ["colon_fence", "attrs_block"] +myst_enable_extensions = [ + "colon_fence", + "attrs_block", + "tasklist", + "fieldlist", + "deflist", +] # Autogenerate anchors diff --git a/docs/index.md b/docs/index.md index 99f2fc0eb..a4e8ce972 100644 --- a/docs/index.md +++ b/docs/index.md @@ -94,6 +94,17 @@ contributor/translation ``` +```{toctree} +--- +maxdepth: 1 +caption: Specifications +hidden: true +--- + +specs/nodeinfo21/index + +``` + ```{toctree} --- caption: Reference diff --git a/docs/specs/nodeinfo21/index.md b/docs/specs/nodeinfo21/index.md new file mode 100644 index 000000000..32ed498aa --- /dev/null +++ b/docs/specs/nodeinfo21/index.md @@ -0,0 +1,218 @@ +# NodeInfo 2.1 + +{bdg-secondary}`In progress` + +## The issue + +Servers need to communicate their capabilities in a network of communicating nodes to negotiate a common protocol. + +## Proposed solution + +Use [NodeInfo](http://nodeinfo.diaspora.software), a well-defined standard for exactly this purpose that's widely used within the Fediverse. + +## Feature behavior + +The NodeInfo endpoint is used to communicate the features and capabilities of a server. It presents details about: + +- Implemented protocols +- Enabled features +- Usage statistics +- Content metadata + +:::{seealso} +Read [the NodeInfo specification for more information](https://nodeinfo.diaspora.software/docson/index.html#/ns/schema/2.1#$$expand). +::: + +The NodeInfo endpoint must contain all mandatory elements listed in the specification. In addition to this, Funkwhale's implementation should list additional details about the instance. + +`actorId` (URL) +: The URL of the pod service actor + +`private` (Boolean) +: Whether the pod is private + +`shortDescription` (String) +: A short description of the pod + +`longDescription` (String) +: A longer description of the pod + +`rules` (String) +: A collection of rules users of the pod must abide by + +`contactEmail` (Email address) +: The email address of the pod administrator + +`terms` (String) +: The terms of use associated with the pod + +`nodeName`(String) +: The name of the pod + +`banner` (URL) +: The URL of the banner image + +`defaultUploadQuota` (Number) +: The default upload quota (in megabytes) allowed for new users + +`library.federationEnabled` (Boolean) +: Whether federation is enabled + +`library.anonymousCanListen` (Boolean) +: Whether public endpoints require authentication + +`supportedUploadExtensions` (Array\) +: A list of file extensions enabled for upload + +`allowlist.enabled` (Boolean) +: Whether the pod admin has enabled allow-listing + +`allowlist.domains` (Array\) +: A list of allowed domains + +`funkwhaleSupportMessageEnabled` (Boolean) +: Whether the admin has enabled the Funkwhale project support message + +`instanceSupportMessage` (String) +: The support message associated with the instance + +`content.top_music_categories` (Array\) +: The top three music genres and the number of uploads tagged with them + +`content.top_podcast_categories` (Array\) +: The top three podcast categories and the number of uploads tagged with them + +`instance_policy.moderation_policy` (String) +: The moderation policy of the pod + +`instance_policy.terms_of_service` (String) +: The terms of service of the pod + +`instance_policy.languages` (Array\) +: The languages spoken by the pod administrators + +`instance_policy.location` (String) +: The country the pod is located in + +`federation.follows_instances` (Number) +: The number of Funkwhale pods that the target pod follows + +`federation.following_instances` (Number) +: The number of Funkwhale pods that publicly follow the target pod + +`features` (Array\) +: A list of enabled features + +### Backend + +A new NodeInfo endpoint will be created that sits alongside the existing `v1` endpoint for backwards-compatibility. + +```text +/api/v2/instance/nodeinfo/2.1 +``` + +This endpoint supports only `GET` requests and responds with the information outlined in the NodeInfo specification. + +Example response: + +```json +{ + "version": "2.1", + "software": { + "name": "Funkwhale", + "version": "1.4.0", + "repository": "https://dev.funkwhale.audio/funkwhale/funkwhale", + "homepage": "https://funkwhale.audio" + }, + "protocols": ["activitypub"], + "services": { + "inbound": ["atom1.0"], + "outbound": ["atom1.0"] + }, + "openRegistrations": true, + "usage": { + "users": { + "total": 0, + "activeHalfYear": 0, + "activeMonth": 0 + }, + "localPosts": 0, + "localComments": 0 + }, + "metadata": { + "actorId": "string", + "private": false, + "shortDescription": "string", + "longDescription": "string", + "rules": "string", + "contactEmail": "user@example.com", + "terms": "string", + "nodeName": "string", + "banner": "string", + "defaultUploadQuota": 0, + "library": { + "federationEnabled": true, + "anonymousCanListen": true + }, + "supportedUploadExtensions": ["string"], + "allowList": { + "enabled": true, + "domains": ["string"] + }, + "funkwhaleSupportMessageEnabled": true, + "instanceSupportMessage": "string", + "instance_policy": { + "moderation_policy": "string", + "terms_of_service": "string", + "languages": ["string"], + "location": "string" + }, + "content": { + "top_music_categories": [ + { + "rock": 1256 + }, + { + "jazz": 604 + }, + { + "classical": 308 + } + ], + "top_podcast_categories": [ + { + "comedy": 12 + }, + { + "politics": 4 + }, + { + "nature": 1 + } + ], + "federation": { + "followed_instances": 0, + "following_instances": 0 + } + }, + "features": ["channels", "podcasts", "collections", "audiobooks"] + } +} +``` + +## Availability + +- [ ] Admin panel +- [ ] App frontend +- [ ] CLI +- [x] API + +## Responsible parties + +Since the actual endpoint is already standardized, Backend developers need only to agree on an implementation. The Frontend group needs to check to see if changing the location of `/.well-known/nodeinfo` has an impact on the web app. + +The NodeInfo endpoint MUST be accompanied by a {download}`full OpenAPI schema file ` for ease of reference and implementation. + +## Open questions + +- [ ] Does changing `/.well-known/nodeinfo` have any implications on the Frontend? diff --git a/docs/specs/nodeinfo21/schema.yml b/docs/specs/nodeinfo21/schema.yml new file mode 100644 index 000000000..6f85665c7 --- /dev/null +++ b/docs/specs/nodeinfo21/schema.yml @@ -0,0 +1,599 @@ +openapi: "3.0.3" +info: + description: "Interactive documentation for [Funkwhale](https://funkwhale.audio) API." + version: "2.0.0" + title: "Funkwhale API" + +servers: + - url: "https://demo.funkwhale.audio" + description: "Demo server" + - url: "https://open.audio" + description: "Real server with real content" + - url: "https://{domain}" + description: "Custom server" + variables: + domain: + default: yourdomain + description: "Your Funkwhale Domain" + protocol: + enum: + - "http" + - "https" + default: "https" +tags: + - name: Instance + description: Information about the server + - name: Content + description: Information about content on the server +paths: + /api/v2/instance/nodeinfo/2.1: + get: + tags: + - Instance + summary: Retrieve nodeinfo data + description: Retrieve details about a Funkwhale server using the Nodeinfo standard + operationId: getNodeinfo + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Nodeinfo" + application/xml: + schema: + $ref: "#/components/schemas/Nodeinfo" + "401": + $ref: "#/components/responses/Unauthorized" + /api/v2/tags/podcasts: + get: + tags: + - Content + summary: Retrieve podcast categories + description: Retrieve a list of podcast categories and the number of uploads tagged with those categories + operationId: getTagsPodcasts + parameters: + - name: q + in: query + required: false + description: A free text field to filter category names + schema: + type: string + - name: page + in: query + required: false + description: The number of the result page you want to return + schema: + type: number + - name: page_size + in: query + required: false + description: The number of results to return on each page. Defaults to 50. + schema: + type: number + - name: ordering + in: query + required: false + description: | + The order in which results are presented. Preface with `-` to return items in descending order. + schema: + type: string + enum: + - "name" + - "creation_date" + - "tagged_items" + - "-name" + - "-creation_date" + - "-tagged_items" + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Categories" + application/xml: + schema: + $ref: "#/components/schemas/Categories" + "401": + $ref: "#/components/responses/Unauthorized" + /api/v2/tags/podcasts/{category}: + get: + tags: + - Content + summary: Retrieve podcast categories + description: Retrieve a list of podcast categories and the number of uploads tagged with those categories + operationId: getTagPodcasts + parameters: + - name: category + in: path + required: true + description: The category you want to return information about + schema: + type: string + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Category" + application/xml: + schema: + $ref: "#/components/schemas/Category" + "401": + $ref: "#/components/responses/Unauthorized" + /api/v2/tags/music: + get: + tags: + - Content + summary: Retrieve music genres + description: Retrieve a list of music genres and the number of uploads tagged with those categories + operationId: getTagsMusic + parameters: + - name: q + in: query + required: false + description: A free text field to filter genre names + schema: + type: string + - name: page + in: query + required: false + description: The number of the result page you want to return + schema: + type: number + - name: page_size + in: query + required: false + description: The number of results to return on each page. Defaults to 50. + schema: + type: number + - name: ordering + in: query + required: false + description: | + The order in which results are presented. Preface with `-` to return items in descending order. + schema: + type: string + enum: + - "name" + - "creation_date" + - "tagged_items" + - "-name" + - "-creation_date" + - "-tagged_items" + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Genres" + application/xml: + schema: + $ref: "#/components/schemas/Genres" + "401": + $ref: "#/components/responses/Unauthorized" + /api/v2/tags/music/{genre}: + get: + tags: + - Content + summary: Retrieve podcast categories + description: Retrieve a list of podcast categories and the number of uploads tagged with those categories + operationId: getTagMusic + parameters: + - name: genre + in: path + required: true + description: The genre you want to return information about + schema: + type: string + responses: + "200": + description: Successful operation + content: + application/json: + schema: + $ref: "#/components/schemas/Genre" + application/xml: + schema: + $ref: "#/components/schemas/Genre" + "401": + $ref: "#/components/responses/Unauthorized" +components: + responses: + Unauthorized: + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + example: + code: 401 + message: User not authorized + application/xml: + schema: + $ref: "#/components/schemas/Error" + example: + code: 401 + message: User not authorized + schemas: + Categories: + type: object + properties: + total: + type: number + next: + type: string + format: url + previous: + type: string + format: url + results: + type: array + items: + $ref: "#/components/schemas/Category" + example: + total: 5 + next: https://demo.funkwhale.audio/api/v2/categories?page=2&page_size=2&q=crime + previous: null + results: + - category: "True Crime" + created_date: "2020-01-01T00:00:00.000Z" + tagged_items: 5 + results_page: "https://demo.funkwhale.audio/library/categories/True%20Crime" + - category: "True Stories" + created_date: "2023-12-15T23:32:52.000Z" + tagged_items: 200 + results_page: "https://demo.funkwhale.audio/library/categories/True%20Stories" + Category: + type: object + properties: + category: + type: string + created_date: + type: string + format: date-time + tagged_items: + type: number + results_page: + type: string + format: url + example: + category: "True Crime" + created_date: "2020-01-01T00:00:00.000Z" + tagged_items: 5 + results_page: "https://demo.funkwhale.audio/library/categories/True%20Crime" + Genres: + type: object + properties: + total: + type: number + next: + type: string + format: url + previous: + type: string + format: url + results: + type: array + items: + $ref: "#/components/schemas/Genre" + example: + total: 5 + next: https://demo.funkwhale.audio/api/v2/categories?page=2&page_size=2&q=rock + previous: null + results: + - genre: "Acoustic Rock" + created_date: "2020-01-01T00:00:00.000Z" + tagged_items: 5 + results_page: "https://demo.funkwhale.audio/library/categories/Acoustic%20Rock" + - genre: "Surf Rock" + created_date: "2023-12-15T23:32:52.000Z" + tagged_items: 200 + results_page: "https://demo.funkwhale.audio/library/categories/Surf%20Rock" + Genre: + type: object + properties: + genre: + type: string + created_date: + type: string + format: date-time + tagged_items: + type: number + results_page: + type: string + format: url + example: + genre: "Acoustic Rock" + created_date: "2020-01-01T00:00:00.000Z" + tagged_items: 5 + results_page: "https://demo.funkwhale.audio/library/categories/Acoustic%20Rock" + Nodeinfo: + type: object + required: + - version + - software + - protocols + - services + - openRegistrations + - usage + - metadata + properties: + version: + type: string + enum: + - "2.1" + software: + type: object + required: + - name + - version + properties: + name: + type: string + enum: + - "Funkwhale" + version: + type: string + example: "1.4.0" + repository: + type: string + format: url + enum: + - "https://dev.funkwhale.audio/funkwhale/funkwhale" + homepage: + type: string + format: url + enum: + - "https://funkwhale.audio" + protocols: + type: array + minItems: 1 + items: + type: string + enum: + - "activitypub" + - "buddycloud" + - "dfrn" + - "diaspora" + - "libertree" + - "ostatus" + - "pumpio" + - "tent" + - "xmpp" + - "zot" + example: + - "activitypub" + services: + type: object + required: + - inbound + - outbound + properties: + inbound: + type: array + items: + type: string + enum: + - "atom1.0" + - "gnusocial" + - "imap" + - "pnut" + - "pop3" + - "pumpio" + - "rss2.0" + - "twitter" + outbound: + type: array + items: + type: string + enum: + - "atom1.0" + - "blogger" + - "buddycloud" + - "diaspora" + - "dreamwidth" + - "drupal" + - "facebook" + - "friendica" + - "gnusocial" + - "google" + - "insanejournal" + - "libertree" + - "linkedin" + - "livejournal" + - "mediagoblin" + - "myspace" + - "pinterest" + - "pnut" + - "posterous" + - "pumpio" + - "redmatrix" + - "rss2.0" + - "smtp" + - "tent" + - "tumblr" + - "twitter" + - "wordpress" + - "xmpp" + openRegistrations: + type: boolean + usage: + type: object + required: + - users + properties: + users: + type: object + properties: + total: + type: integer + minimum: 0 + activeHalfYear: + type: integer + minimum: 0 + activeMonth: + type: integer + minimum: 0 + localPosts: + type: integer + minimum: 0 + localComments: + type: integer + minimum: 0 + metadata: + type: object + properties: + actorId: + type: string + format: url + private: + type: boolean + shortDescription: + type: string + longDescription: + type: string + rules: + type: string + contactEmail: + type: string + format: email + terms: + type: string + nodeName: + type: string + banner: + type: string + format: url + defaultUploadQuota: + type: integer + library: + type: object + properties: + federationEnabled: + type: boolean + anonymousCanListen: + type: boolean + supportedUploadExtensions: + type: array + items: + type: string + allowList: + type: object + properties: + enabled: + type: boolean + domains: + type: array + items: + type: string + funkwhaleSupportMessageEnabled: + type: boolean + instanceSupportMessage: + type: string + instance_policy: + type: object + properties: + moderation_policy: + type: string + format: url + terms_of_service: + type: string + format: url + languages: + type: array + items: + type: string + location: + type: string + content: + type: object + properties: + top_music_categories: + type: array + items: + type: object + additionalProperties: + type: integer + example: + - "rock": 1256 + - "jazz": 604 + - "classical": 308 + top_podcast_categories: + type: array + items: + type: object + additionalProperties: + type: integer + example: + - "comedy": 12 + - "politics": 4 + - "nature": 1 + federation: + type: object + properties: + followed_instances: + type: integer + following_instances: + type: integer + features: + type: array + items: + type: string + example: + - "channels" + - "podcasts" + - "collections" + - "audiobooks" + Error: + type: object + properties: + code: + type: string + message: + type: string + required: + - code + - message + securitySchemes: + oauth2: + type: oauth2 + description: This API uses OAuth 2 with the Authorization Code flow. You can register an app using the /oauth/apps/ endpoint. + flows: + authorizationCode: + authorizationUrl: /authorize + tokenUrl: /api/v1/oauth/token/ + refreshUrl: /api/v1/oauth/token/ + scopes: + "read": "Read-only access to all user data" + "write": "Write-only access on all user data" + "read:edits": "Read-only access to edits" + "write:edits": "Write-only access to edits" + "read:favorites": "Read-only access to favorites" + "write:favorites": "Write-only access to favorits" + "read:filters": "Read-only to to content filters" + "write:filters": "Write-only access to content-filters" + "read:follows": "Read-only to follows" + "write:follows": "Write-only access to follows" + "read:libraries": "Read-only access to library and uploads" + "write:libraries": "Write-only access to libraries" + "read:listenings": "Read-only access to listening history" + "write:listenings": "Write-only access to listening history" + "read:notifications": "Read-only access to notifications" + "write:notifications": "Write-only access to notifications" + "read:playlists": "Read-only access to playlists" + "write:playlists": "Write-only access to playlists" + "read:profile": "Read-only access to profile data" + "write:profile": "Write-only access to profile data" + "read:radios": "Read-only access to radios" + "write:radios": "Write-only access to radios" + "read:reports": "Read-only access to reports" + "write:reports": "Write-only access to reports" + "read:security": "Read-only access security settings" + "write:security": "write-only access security settings" + +security: + - oauth2: []