From c4777532ebe88bf393578f2d5aeb246afced6ef9 Mon Sep 17 00:00:00 2001
From: Eliot Berriot <contact@eliotberriot.com>
Date: Thu, 26 Apr 2018 18:12:08 +0200
Subject: [PATCH] Bundle swagger docs with sphinx docs

---
 .gitignore            |   1 +
 .gitlab-ci.yml        |   6 +-
 api/docs/swagger.yml  | 825 ------------------------------------------
 docs/build_docs.sh    |   5 +
 docs/build_swagger.sh |   9 +
 docs/swagger.yml      | 186 ++++++++++
 6 files changed, 205 insertions(+), 827 deletions(-)
 delete mode 100644 api/docs/swagger.yml
 create mode 100755 docs/build_docs.sh
 create mode 100755 docs/build_swagger.sh
 create mode 100644 docs/swagger.yml

diff --git a/.gitignore b/.gitignore
index 548cfd7b..25b08873 100644
--- a/.gitignore
+++ b/.gitignore
@@ -89,3 +89,4 @@ data/
 .env
 
 po/*.po
+docs/swagger
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6a0f4b9d..5f65e60d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -92,12 +92,14 @@ build_front:
 
 pages:
   stage: test
-  image: python:3.6-alpine
+  image: python:3.6
+  variables:
+    BUILD_PATH: "../public"
   before_script:
     - cd docs
   script:
     - pip install sphinx
-    - python -m sphinx . ../public
+    - ./build_docs.sh
   artifacts:
     paths:
       - public
diff --git a/api/docs/swagger.yml b/api/docs/swagger.yml
deleted file mode 100644
index 160ef56b..00000000
--- a/api/docs/swagger.yml
+++ /dev/null
@@ -1,825 +0,0 @@
-swagger: "2.0"
-info:
-  description: "Documentation for [Funkwhale](https://funkwhale.audio) API. The API is **not** stable yet."
-  version: "1.0.0"
-  title: "Funkwhale API"
-host: "demo.funkwhale.audio"
-basePath: "/api/v1"
-tags:
-- name: "artists"
-  description: "Artists data"
-- name: "store"
-  description: "Access to Petstore orders"
-- name: "user"
-  description: "Operations about user"
-
-schemes:
-- "http"
-paths:
-  /artists/:
-    get:
-      tags:
-        - "artists"
-      parameters:
-        - name: "q"
-          in: "query"
-          description: "Search query used to filter artists"
-          required: false
-          type: "string"
-          example: "carpenter"
-        - name: "listenable"
-          in: "query"
-          description: "Filter/exclude artists with listenable tracks"
-          required: false
-          type: "boolean"
-      responses:
-        200:
-          schema:
-            type: "object"
-            properties:
-              count:
-                $ref: "#/properties/resultsCount"
-              results:
-                type: "array"
-                items:
-                  $ref: "#/definitions/ArtistNested"
-  /pet:
-    post:
-      tags:
-      - "pet"
-      summary: "Add a new pet to the store"
-      description: ""
-      operationId: "addPet"
-      consumes:
-      - "application/json"
-      - "application/xml"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters:
-      - in: "body"
-        name: "body"
-        description: "Pet object that needs to be added to the store"
-        required: true
-        schema:
-          $ref: "#/definitions/Pet"
-      responses:
-        405:
-          description: "Invalid input"
-      security:
-      - petstore_auth:
-        - "write:pets"
-        - "read:pets"
-    put:
-      tags:
-      - "pet"
-      summary: "Update an existing pet"
-      description: ""
-      operationId: "updatePet"
-      consumes:
-      - "application/json"
-      - "application/xml"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters:
-      - in: "body"
-        name: "body"
-        description: "Pet object that needs to be added to the store"
-        required: true
-        schema:
-          $ref: "#/definitions/Pet"
-      responses:
-        400:
-          description: "Invalid ID supplied"
-        404:
-          description: "Pet not found"
-        405:
-          description: "Validation exception"
-      security:
-      - petstore_auth:
-        - "write:pets"
-        - "read:pets"
-  /pet/findByStatus:
-    get:
-      tags:
-      - "pet"
-      summary: "Finds Pets by status"
-      description: "Multiple status values can be provided with comma separated strings"
-      operationId: "findPetsByStatus"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters:
-      - name: "status"
-        in: "query"
-        description: "Status values that need to be considered for filter"
-        required: true
-        type: "array"
-        items:
-          type: "string"
-          enum:
-          - "available"
-          - "pending"
-          - "sold"
-          default: "available"
-        collectionFormat: "multi"
-      responses:
-        200:
-          description: "successful operation"
-          schema:
-            type: "array"
-            items:
-              $ref: "#/definitions/Pet"
-        400:
-          description: "Invalid status value"
-      security:
-      - petstore_auth:
-        - "write:pets"
-        - "read:pets"
-  /pet/findByTags:
-    get:
-      tags:
-      - "pet"
-      summary: "Finds Pets by tags"
-      description: "Muliple tags can be provided with comma separated strings. Use         tag1, tag2, tag3 for testing."
-      operationId: "findPetsByTags"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters:
-      - name: "tags"
-        in: "query"
-        description: "Tags to filter by"
-        required: true
-        type: "array"
-        items:
-          type: "string"
-        collectionFormat: "multi"
-      responses:
-        200:
-          description: "successful operation"
-          schema:
-            type: "array"
-            items:
-              $ref: "#/definitions/Pet"
-        400:
-          description: "Invalid tag value"
-      security:
-      - petstore_auth:
-        - "write:pets"
-        - "read:pets"
-      deprecated: true
-  /pet/{petId}:
-    get:
-      tags:
-      - "pet"
-      summary: "Find pet by ID"
-      description: "Returns a single pet"
-      operationId: "getPetById"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters:
-      - name: "petId"
-        in: "path"
-        description: "ID of pet to return"
-        required: true
-        type: "integer"
-        format: "int64"
-      responses:
-        200:
-          description: "successful operation"
-          schema:
-            $ref: "#/definitions/Pet"
-        400:
-          description: "Invalid ID supplied"
-        404:
-          description: "Pet not found"
-      security:
-      - api_key: []
-    post:
-      tags:
-      - "pet"
-      summary: "Updates a pet in the store with form data"
-      description: ""
-      operationId: "updatePetWithForm"
-      consumes:
-      - "application/x-www-form-urlencoded"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters:
-      - name: "petId"
-        in: "path"
-        description: "ID of pet that needs to be updated"
-        required: true
-        type: "integer"
-        format: "int64"
-      - name: "name"
-        in: "formData"
-        description: "Updated name of the pet"
-        required: false
-        type: "string"
-      - name: "status"
-        in: "formData"
-        description: "Updated status of the pet"
-        required: false
-        type: "string"
-      responses:
-        405:
-          description: "Invalid input"
-      security:
-      - petstore_auth:
-        - "write:pets"
-        - "read:pets"
-    delete:
-      tags:
-      - "pet"
-      summary: "Deletes a pet"
-      description: ""
-      operationId: "deletePet"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters:
-      - name: "api_key"
-        in: "header"
-        required: false
-        type: "string"
-      - name: "petId"
-        in: "path"
-        description: "Pet id to delete"
-        required: true
-        type: "integer"
-        format: "int64"
-      responses:
-        400:
-          description: "Invalid ID supplied"
-        404:
-          description: "Pet not found"
-      security:
-      - petstore_auth:
-        - "write:pets"
-        - "read:pets"
-  /pet/{petId}/uploadImage:
-    post:
-      tags:
-      - "pet"
-      summary: "uploads an image"
-      description: ""
-      operationId: "uploadFile"
-      consumes:
-      - "multipart/form-data"
-      produces:
-      - "application/json"
-      parameters:
-      - name: "petId"
-        in: "path"
-        description: "ID of pet to update"
-        required: true
-        type: "integer"
-        format: "int64"
-      - name: "additionalMetadata"
-        in: "formData"
-        description: "Additional data to pass to server"
-        required: false
-        type: "string"
-      - name: "file"
-        in: "formData"
-        description: "file to upload"
-        required: false
-        type: "file"
-      responses:
-        200:
-          description: "successful operation"
-          schema:
-            $ref: "#/definitions/ApiResponse"
-      security:
-      - petstore_auth:
-        - "write:pets"
-        - "read:pets"
-  /store/inventory:
-    get:
-      tags:
-      - "store"
-      summary: "Returns pet inventories by status"
-      description: "Returns a map of status codes to quantities"
-      operationId: "getInventory"
-      produces:
-      - "application/json"
-      parameters: []
-      responses:
-        200:
-          description: "successful operation"
-          schema:
-            type: "object"
-            additionalProperties:
-              type: "integer"
-              format: "int32"
-      security:
-      - api_key: []
-  /store/order:
-    post:
-      tags:
-      - "store"
-      summary: "Place an order for a pet"
-      description: ""
-      operationId: "placeOrder"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters:
-      - in: "body"
-        name: "body"
-        description: "order placed for purchasing the pet"
-        required: true
-        schema:
-          $ref: "#/definitions/Order"
-      responses:
-        200:
-          description: "successful operation"
-          schema:
-            $ref: "#/definitions/Order"
-        400:
-          description: "Invalid Order"
-  /store/order/{orderId}:
-    get:
-      tags:
-      - "store"
-      summary: "Find purchase order by ID"
-      description: "For valid response try integer IDs with value >= 1 and <= 10.         Other values will generated exceptions"
-      operationId: "getOrderById"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters:
-      - name: "orderId"
-        in: "path"
-        description: "ID of pet that needs to be fetched"
-        required: true
-        type: "integer"
-        maximum: 10.0
-        minimum: 1.0
-        format: "int64"
-      responses:
-        200:
-          description: "successful operation"
-          schema:
-            $ref: "#/definitions/Order"
-        400:
-          description: "Invalid ID supplied"
-        404:
-          description: "Order not found"
-    delete:
-      tags:
-      - "store"
-      summary: "Delete purchase order by ID"
-      description: "For valid response try integer IDs with positive integer value.         Negative or non-integer values will generate API errors"
-      operationId: "deleteOrder"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters:
-      - name: "orderId"
-        in: "path"
-        description: "ID of the order that needs to be deleted"
-        required: true
-        type: "integer"
-        minimum: 1.0
-        format: "int64"
-      responses:
-        400:
-          description: "Invalid ID supplied"
-        404:
-          description: "Order not found"
-  /user:
-    post:
-      tags:
-      - "user"
-      summary: "Create user"
-      description: "This can only be done by the logged in user."
-      operationId: "createUser"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters:
-      - in: "body"
-        name: "body"
-        description: "Created user object"
-        required: true
-        schema:
-          $ref: "#/definitions/User"
-      responses:
-        default:
-          description: "successful operation"
-  /user/createWithArray:
-    post:
-      tags:
-      - "user"
-      summary: "Creates list of users with given input array"
-      description: ""
-      operationId: "createUsersWithArrayInput"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters:
-      - in: "body"
-        name: "body"
-        description: "List of user object"
-        required: true
-        schema:
-          type: "array"
-          items:
-            $ref: "#/definitions/User"
-      responses:
-        default:
-          description: "successful operation"
-  /user/createWithList:
-    post:
-      tags:
-      - "user"
-      summary: "Creates list of users with given input array"
-      description: ""
-      operationId: "createUsersWithListInput"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters:
-      - in: "body"
-        name: "body"
-        description: "List of user object"
-        required: true
-        schema:
-          type: "array"
-          items:
-            $ref: "#/definitions/User"
-      responses:
-        default:
-          description: "successful operation"
-  /user/login:
-    get:
-      tags:
-      - "user"
-      summary: "Logs user into the system"
-      description: ""
-      operationId: "loginUser"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters:
-      - name: "username"
-        in: "query"
-        description: "The user name for login"
-        required: true
-        type: "string"
-      - name: "password"
-        in: "query"
-        description: "The password for login in clear text"
-        required: true
-        type: "string"
-      responses:
-        200:
-          description: "successful operation"
-          schema:
-            type: "string"
-          headers:
-            X-Rate-Limit:
-              type: "integer"
-              format: "int32"
-              description: "calls per hour allowed by the user"
-            X-Expires-After:
-              type: "string"
-              format: "date-time"
-              description: "date in UTC when token expires"
-        400:
-          description: "Invalid username/password supplied"
-  /user/logout:
-    get:
-      tags:
-      - "user"
-      summary: "Logs out current logged in user session"
-      description: ""
-      operationId: "logoutUser"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters: []
-      responses:
-        default:
-          description: "successful operation"
-  /user/{username}:
-    get:
-      tags:
-      - "user"
-      summary: "Get user by user name"
-      description: ""
-      operationId: "getUserByName"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters:
-      - name: "username"
-        in: "path"
-        description: "The name that needs to be fetched. Use user1 for testing. "
-        required: true
-        type: "string"
-      responses:
-        200:
-          description: "successful operation"
-          schema:
-            $ref: "#/definitions/User"
-        400:
-          description: "Invalid username supplied"
-        404:
-          description: "User not found"
-    put:
-      tags:
-      - "user"
-      summary: "Updated user"
-      description: "This can only be done by the logged in user."
-      operationId: "updateUser"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters:
-      - name: "username"
-        in: "path"
-        description: "name that need to be updated"
-        required: true
-        type: "string"
-      - in: "body"
-        name: "body"
-        description: "Updated user object"
-        required: true
-        schema:
-          $ref: "#/definitions/User"
-      responses:
-        400:
-          description: "Invalid user supplied"
-        404:
-          description: "User not found"
-    delete:
-      tags:
-      - "user"
-      summary: "Delete user"
-      description: "This can only be done by the logged in user."
-      operationId: "deleteUser"
-      produces:
-      - "application/xml"
-      - "application/json"
-      parameters:
-      - name: "username"
-        in: "path"
-        description: "The name that needs to be deleted"
-        required: true
-        type: "string"
-      responses:
-        400:
-          description: "Invalid username supplied"
-        404:
-          description: "User not found"
-securityDefinitions:
-  components:
-  securitySchemes:
-    bearerAuth:            # arbitrary name for the security scheme
-      type: http
-      scheme: bearer
-      bearerFormat: JWT    # optional, arbitrary value for documentation purposes
-  petstore_auth:
-    type: "oauth2"
-    authorizationUrl: "http://petstore.swagger.io/oauth/dialog"
-    flow: "implicit"
-    scopes:
-      write:pets: "modify pets in your account"
-      read:pets: "read your pets"
-  api_key:
-    type: "apiKey"
-    name: "api_key"
-    in: "header"
-properties:
-  resultsCount:
-    type: "integer"
-    format: "int64"
-    description: "The total number of resources matching the request"
-  mbid:
-    type: "string"
-    formats: "uuid"
-    description: "A musicbrainz ID"
-definitions:
-  Artist:
-    type: "object"
-    properties:
-      mbid:
-        required: false
-        $ref: "#/properties/mbid"
-      id:
-        type: "integer"
-        format: "int64"
-        example: 42
-      name:
-        type: "string"
-        example: "System of a Down"
-      creation_date:
-        type: "string"
-        format: "date-time"
-  ArtistNested:
-    type: "object"
-    allOf:
-      - $ref: "#/definitions/Artist"
-      - type: "object"
-        properties:
-          albums:
-            type: "array"
-            items:
-              $ref: "#/definitions/AlbumNested"
-
-  Album:
-    type: "object"
-    properties:
-      mbid:
-        required: false
-        $ref: "#/properties/mbid"
-      id:
-        type: "integer"
-        format: "int64"
-        example: 16
-      artist:
-        type: "integer"
-        format: "int64"
-        example: 42
-      title:
-        type: "string"
-        example: "Toxicity"
-      creation_date:
-        type: "string"
-        format: "date-time"
-      release_date:
-        type: "string"
-        required: false
-        format: "date"
-        example: "2001-01-01"
-
-  AlbumNested:
-    type: "object"
-    allOf:
-      - $ref: "#/definitions/Album"
-      - type: "object"
-        properties:
-          tracks:
-            type: "array"
-            items:
-              $ref: "#/definitions/Track"
-
-  Track:
-    type: "object"
-    properties:
-      mbid:
-        required: false
-        $ref: "#/properties/mbid"
-      id:
-        type: "integer"
-        format: "int64"
-        example: 66
-      artist:
-        type: "integer"
-        format: "int64"
-        example: 42
-      album:
-        type: "integer"
-        format: "int64"
-        example: 16
-      title:
-        type: "string"
-        example: "Chop Suey!"
-      position:
-        required: false
-        description: "Position of the track in the album"
-        type: "number"
-        minimum: 1
-        example: 1
-      creation_date:
-        type: "string"
-        format: "date-time"
-
-  Order:
-    type: "object"
-    properties:
-      id:
-        type: "integer"
-        format: "int64"
-      petId:
-        type: "integer"
-        format: "int64"
-      quantity:
-        type: "integer"
-        format: "int32"
-      shipDate:
-        type: "string"
-        format: "date-time"
-      status:
-        type: "string"
-        description: "Order Status"
-        enum:
-        - "placed"
-        - "approved"
-        - "delivered"
-      complete:
-        type: "boolean"
-        default: false
-    xml:
-      name: "Order"
-  Category:
-    type: "object"
-    properties:
-      id:
-        type: "integer"
-        format: "int64"
-      name:
-        type: "string"
-    xml:
-      name: "Category"
-  User:
-    type: "object"
-    properties:
-      id:
-        type: "integer"
-        format: "int64"
-      username:
-        type: "string"
-      firstName:
-        type: "string"
-      lastName:
-        type: "string"
-      email:
-        type: "string"
-      password:
-        type: "string"
-      phone:
-        type: "string"
-      userStatus:
-        type: "integer"
-        format: "int32"
-        description: "User Status"
-    xml:
-      name: "User"
-  Tag:
-    type: "object"
-    properties:
-      id:
-        type: "integer"
-        format: "int64"
-      name:
-        type: "string"
-    xml:
-      name: "Tag"
-  Pet:
-    type: "object"
-    required:
-    - "name"
-    - "photoUrls"
-    properties:
-      id:
-        type: "integer"
-        format: "int64"
-      category:
-        $ref: "#/definitions/Category"
-      name:
-        type: "string"
-        example: "doggie"
-      photoUrls:
-        type: "array"
-        xml:
-          name: "photoUrl"
-          wrapped: true
-        items:
-          type: "string"
-      tags:
-        type: "array"
-        xml:
-          name: "tag"
-          wrapped: true
-        items:
-          $ref: "#/definitions/Tag"
-      status:
-        type: "string"
-        description: "pet status in the store"
-        enum:
-        - "available"
-        - "pending"
-        - "sold"
-    xml:
-      name: "Pet"
-  ApiResponse:
-    type: "object"
-    properties:
-      code:
-        type: "integer"
-        format: "int32"
-      type:
-        type: "string"
-      message:
-        type: "string"
-externalDocs:
-  description: "Find out more about Funkwhale"
-  url: "https://docs.funkwhale.audio"
diff --git a/docs/build_docs.sh b/docs/build_docs.sh
new file mode 100755
index 00000000..fbf2036a
--- /dev/null
+++ b/docs/build_docs.sh
@@ -0,0 +1,5 @@
+#!/bin/bash -eux
+# Building sphinx and swagger docs
+
+python -m sphinx . $BUILD_PATH
+TARGET_PATH="$BUILD_PATH/swagger" ./build_swagger.sh
diff --git a/docs/build_swagger.sh b/docs/build_swagger.sh
new file mode 100755
index 00000000..13ae21b0
--- /dev/null
+++ b/docs/build_swagger.sh
@@ -0,0 +1,9 @@
+#!/bin/bash -eux
+
+SWAGGER_VERSION="3.13.6"
+TARGET_PATH=${TARGET_PATH-"swagger"}
+rm -rf $TARGET_PATH /tmp/swagger-ui
+git clone --branch="v$SWAGGER_VERSION" --depth=1 "https://github.com/swagger-api/swagger-ui.git" /tmp/swagger-ui
+mv /tmp/swagger-ui/dist $TARGET_PATH
+cp swagger.yml $TARGET_PATH
+sed -i "s,http://petstore.swagger.io/v2/swagger.json,swagger.yml,g" $TARGET_PATH/index.html
diff --git a/docs/swagger.yml b/docs/swagger.yml
new file mode 100644
index 00000000..7735a8f2
--- /dev/null
+++ b/docs/swagger.yml
@@ -0,0 +1,186 @@
+openapi: "3.0"
+info:
+  description: "Documentation for [Funkwhale](https://funkwhale.audio) API. The API is **not** stable yet."
+  version: "1.0.0"
+  title: "Funkwhale API"
+
+servers:
+  - url: https://demo.funkwhale.audio/api/v1
+    description: Demo server
+  - url: https://node1.funkwhale.test/api/v1
+    description: Node 1 (local)
+
+components:
+  securitySchemes:
+    jwt:
+      type: http
+      scheme: bearer
+      bearerFormat: JWT
+      description: "You can get a token by using the /token endpoint"
+
+security:
+  - jwt: []
+
+paths:
+  /token/:
+    post:
+      tags:
+        - "auth"
+      description:
+        Obtain a JWT token you can use for authenticating your next requests.
+      security: []
+      responses:
+        '200':
+          description: Successfull auth
+        '400':
+          description: Invalid credentials
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              type: "object"
+              properties:
+                username:
+                  type: "string"
+                  example: "demo"
+                password:
+                  type: "string"
+                  example: "demo"
+
+  /artists/:
+    get:
+      tags:
+        - "artists"
+      parameters:
+        - name: "q"
+          in: "query"
+          description: "Search query used to filter artists"
+          schema:
+            required: false
+            type: "string"
+            example: "carpenter"
+        - name: "listenable"
+          in: "query"
+          description: "Filter/exclude artists with listenable tracks"
+          schema:
+            required: false
+            type: "boolean"
+      responses:
+        200:
+          content:
+            application/json:
+              schema:
+                type: "object"
+                properties:
+                  count:
+                    $ref: "#/properties/resultsCount"
+                  results:
+                    type: "array"
+                    items:
+                      $ref: "#/definitions/ArtistNested"
+
+properties:
+  resultsCount:
+    type: "integer"
+    format: "int64"
+    description: "The total number of resources matching the request"
+  mbid:
+    type: "string"
+    formats: "uuid"
+    description: "A musicbrainz ID"
+definitions:
+  Artist:
+    type: "object"
+    properties:
+      mbid:
+        required: false
+        $ref: "#/properties/mbid"
+      id:
+        type: "integer"
+        format: "int64"
+        example: 42
+      name:
+        type: "string"
+        example: "System of a Down"
+      creation_date:
+        type: "string"
+        format: "date-time"
+  ArtistNested:
+    type: "object"
+    allOf:
+      - $ref: "#/definitions/Artist"
+      - type: "object"
+        properties:
+          albums:
+            type: "array"
+            items:
+              $ref: "#/definitions/AlbumNested"
+
+  Album:
+    type: "object"
+    properties:
+      mbid:
+        required: false
+        $ref: "#/properties/mbid"
+      id:
+        type: "integer"
+        format: "int64"
+        example: 16
+      artist:
+        type: "integer"
+        format: "int64"
+        example: 42
+      title:
+        type: "string"
+        example: "Toxicity"
+      creation_date:
+        type: "string"
+        format: "date-time"
+      release_date:
+        type: "string"
+        required: false
+        format: "date"
+        example: "2001-01-01"
+
+  AlbumNested:
+    type: "object"
+    allOf:
+      - $ref: "#/definitions/Album"
+      - type: "object"
+        properties:
+          tracks:
+            type: "array"
+            items:
+              $ref: "#/definitions/Track"
+
+  Track:
+    type: "object"
+    properties:
+      mbid:
+        required: false
+        $ref: "#/properties/mbid"
+      id:
+        type: "integer"
+        format: "int64"
+        example: 66
+      artist:
+        type: "integer"
+        format: "int64"
+        example: 42
+      album:
+        type: "integer"
+        format: "int64"
+        example: 16
+      title:
+        type: "string"
+        example: "Chop Suey!"
+      position:
+        required: false
+        description: "Position of the track in the album"
+        type: "number"
+        minimum: 1
+        example: 1
+      creation_date:
+        type: "string"
+        format: "date-time"
-- 
GitLab