From afe40be957eda7d7679f69e78ff0c16f162d2af9 Mon Sep 17 00:00:00 2001
From: Nicholas Wallace <nicholaslwallace@gmail.com>
Date: Sat, 30 Mar 2024 23:47:13 +0000
Subject: [PATCH 1/7] Initial large file

---
 docs/spec.yaml | 972 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 972 insertions(+)
 create mode 100644 docs/spec.yaml

diff --git a/docs/spec.yaml b/docs/spec.yaml
new file mode 100644
index 0000000000..dfe95b6945
--- /dev/null
+++ b/docs/spec.yaml
@@ -0,0 +1,972 @@
+openapi: 3.0.0
+info:
+  title: Audiobookshelf API
+  version: 0.1.0
+  description: Audiobookshelf API with autogenerated OpenAPI doc
+
+servers:
+  - url: http://localhost:3000
+    description: Development server
+
+components:
+  schemas:
+    mediaType:
+      type: string
+      description: The type of media, will be book or podcast.
+      enum: [book, podcast]
+    mediaMinified:
+      description: The minified media of the library item.
+      oneOf:
+        - $ref: '#/components/schemas/bookMinified'
+    bookCoverPath:
+      description: The absolute path on the server of the cover file. Will be null if there is no cover.
+      type: string
+      nullable: true
+      example: /audiobooks/Terry Goodkind/Sword of Truth/Wizards First Rule/cover.jpg
+    bookBase:
+      type: object
+      description: Base book schema
+      properties:
+        libraryItemId:
+          $ref: '#/components/schemas/libraryItemId'
+        coverPath:
+          $ref: '#/components/schemas/bookCoverPath'
+        tags:
+          $ref: '#/components/schemas/tags'
+        audioFiles:
+          type: array
+          items:
+            $ref: '#/components/schemas/audioFile'
+        chapters:
+          type: array
+          items:
+            $ref: '#/components/schemas/bookChapter'
+        missingParts:
+          description: Any parts missing from the book by track index.
+          type: array
+          items:
+            type: integer
+        ebookFile:
+          $ref: '#/components/schemas/ebookFile'
+    bookMinified:
+      type: object
+      description: Minified book schema. Does not depend on `bookBase` because there's pretty much no overlap.
+      properties:
+        metadata:
+          $ref: '#/components/schemas/bookMetadataMinified'
+        coverPath:
+          $ref: '#/components/schemas/bookCoverPath'
+        tags:
+          $ref: '#/components/schemas/tags'
+        numTracks:
+          description: The number of tracks the book's audio files have.
+          type: integer
+          example: 1
+        numAudioFiles:
+          description: The number of audio files the book has.
+          type: integer
+          example: 1
+        numChapters:
+          description: The number of chapters the book has.
+          type: integer
+          example: 1
+        numMissingParts:
+          description: The total number of missing parts the book has.
+          type: integer
+          example: 0
+        numInvalidAudioFiles:
+          description: The number of invalid audio files the book has.
+          type: integer
+          example: 0
+        duration:
+          $ref: '#/components/schemas/durationSec'
+        size:
+          $ref: '#/components/schemas/size'
+        ebookFormat:
+          description: The format of ebook of the book. Will be null if the book is an audiobook.
+          type: string
+          nullable: true
+    narrators:
+      description: The narrators of the audiobook.
+      type: array
+      items:
+        type: string
+        example: Sam Tsoutsouvas
+    bookMetadataBase:
+      type: object
+      description: The base book metadata object for minified, normal, and extended schemas to inherit from.
+      properties:
+        title:
+          description: The title of the book. Will be null if unknown.
+          type: string
+          nullable: true
+          example: Wizards First Rule
+        subtitle:
+          description: The subtitle of the book. Will be null if there is no subtitle.
+          type: string
+          nullable: true
+        genres:
+          description: The genres of the book.
+          type: array
+          items:
+            type: string
+          example: ["Fantasy", "Sci-Fi", "Nonfiction: History"]
+        publishedYear:
+          description: The year the book was published. Will be null if unknown.
+          type: string
+          nullable: true
+          example: '2008'
+        publishedDate:
+          description: The date the book was published. Will be null if unknown.
+          type: string
+          nullable: true
+        publisher:
+          description: The publisher of the book. Will be null if unknown.
+          type: string
+          nullable: true
+          example: Brilliance Audio
+        description:
+          description: A description for the book. Will be null if empty.
+          type: string
+          nullable: true
+          example: >-
+              The masterpiece that started Terry Goodkind's New York Times bestselling
+              epic Sword of Truth In the aftermath of the brutal murder of his father,
+              a mysterious woman, Kahlan Amnell, appears in Richard Cypher's forest
+              sanctuary seeking help...and more. His world, his very beliefs, are
+              shattered when ancient debts come due with thundering violence. In a
+              dark age it takes courage to live, and more than mere courage to
+              challenge those who hold dominion, Richard and Kahlan must take up that
+              challenge or become the next victims. Beyond awaits a bewitching land
+              where even the best of their hearts could betray them. Yet, Richard
+              fears nothing so much as what secrets his sword might reveal about his
+              own soul. Falling in love would destroy them - for reasons Richard can't
+              imagine and Kahlan dare not say. In their darkest hour, hunted
+              relentlessly, tormented by treachery and loss, Kahlan calls upon Richard
+              to reach beyond his sword - to invoke within himself something more
+              noble. Neither knows that the rules of battle have just changed...or
+              that their time has run out. Wizard's First Rule is the beginning. One
+              book. One Rule. Witness the birth of a legend.
+        isbn:
+          description: The ISBN of the book. Will be null if unknown.
+          type: string
+          nullable: true
+        asin:
+          description: The ASIN of the book. Will be null if unknown.
+          type: string
+          nullable: true
+          example: B002V0QK4C
+        language:
+          description: The language of the book. Will be null if unknown.
+          type: string
+          nullable: true
+        explicit:
+          description: Whether the book has been marked as explicit.
+          type: boolean
+          example: false
+    bookMetadataMinified:
+      type: object
+      description: The minified metadata for a book in the database.
+      allOf:
+        - $ref : '#/components/schemas/bookMetadataBase'
+        - type: object
+          properties:
+            titleIgnorePrefix:
+              description: The title of the book with any prefix moved to the end.
+              type: string
+            authorName:
+              description: The name of the book's author(s).
+              type: string
+              example: Terry Goodkind
+            authorNameLF:
+              description: The name of the book's author(s) with last names first.
+              type: string
+              example: Goodkind, Terry
+            narratorName:
+              description: The name of the audiobook's narrator(s).
+              type: string
+              example: Sam Tsoutsouvas
+            seriesName:
+              description: The name of the book's series.
+              type: string
+              example: Sword of Truth
+    bookChapter:
+      type: object
+      description: A book chapter. Includes the title and timestamps.
+      properties:
+        id:
+          description: The ID of the book chapter.
+          type: integer
+          example: 0
+        start:
+          description: When in the book (in seconds) the chapter starts.
+          type: integer
+          example: 0
+        end:
+          description: When in the book (in seconds) the chapter ends.
+          type: number
+          example: 6004.6675
+        title:
+          description: The title of the chapter.
+          type: string
+          example: Wizards First Rule 01 Chapter 1
+    audioFile:
+      type: object
+      description: An audio file for a book. Includes audio metadata and track numbers.
+      properties:
+        index:
+          description: The index of the audio file.
+          type: integer
+          example: 1
+        ino:
+          $ref: '#/components/schemas/inode'
+        metadata:
+          $ref: '#/components/schemas/fileMetadata'
+        addedAt:
+          $ref: '#/components/schemas/addedAt'
+        updatedAt:
+          $ref: '#/components/schemas/updatedAt'
+        trackNumFromMeta:
+          description: The track number of the audio file as pulled from the file's metadata. Will be null if unknown.
+          type: integer
+          nullable: true
+          example: 1
+        discNumFromMeta:
+          description: The disc number of the audio file as pulled from the file's metadata. Will be null if unknown.
+          type: string
+          nullable: true
+        trackNumFromFilename:
+          description: The track number of the audio file as determined from the file's name. Will be null if unknown.
+          type: integer
+          nullable: true
+          example: 1
+        discNumFromFilename:
+          description: The disc number of the audio file as determined from the file's name. Will be null if unknown.
+          type: string
+          nullable: true
+        manuallyVerified:
+          description: Whether the audio file has been manually verified by a user.
+          type: boolean
+        invalid:
+          description: Whether the audio file is missing from the server.
+          type: boolean
+        exclude:
+          description: Whether the audio file has been marked for exclusion.
+          type: boolean
+        error:
+          description: Any error with the audio file. Will be null if there is none.
+          type: string
+          nullable: true
+        format:
+          description: The format of the audio file.
+          type: string
+          example: MP2/3 (MPEG audio layer 2/3)
+        duration:
+          $ref: '#/components/schemas/durationSec'
+        bitRate:
+          description: The bit rate (in bit/s) of the audio file.
+          type: integer
+          example: 64000
+        language:
+          description: The language of the audio file.
+          type: string
+          nullable: true
+        codec:
+          description: The codec of the audio file.
+          type: string
+          example: mp3
+        timeBase:
+          description: The time base of the audio file.
+          type: string
+          example: 1/14112000
+        channels:
+          description: The number of channels the audio file has.
+          type: integer
+          example: 2
+        channelLayout:
+          description: The layout of the audio file's channels.
+          type: string
+          example: stereo
+        chapters:
+          description: If the audio file is part of an audiobook, the chapters the file contains.
+          type: array
+          items:
+            $ref: '#/components/schemas/bookChapter'
+        embeddedCoverArt:
+          description: The type of embedded cover art in the audio file. Will be null if none exists.
+          type: string
+          nullable: true
+        metaTags:
+          $ref: '#/components/schemas/audioMetaTags'
+        mimeType:
+          description: The MIME type of the audio file.
+          type: string
+          example: audio/mpeg
+    ebookFile:
+      type: object
+      description: An ebook file for a library item.
+      nullable: true
+      properties:
+        ino:
+          $ref: '#/components/schemas/inode'
+        metadata:
+          $ref: '#/components/schemas/fileMetadata'
+        ebookFormat:
+          description: The ebook format of the ebook file.
+          type: string
+          example: epub
+        addedAt:
+          $ref: '#/components/schemas/addedAt'
+        updatedAt:
+          $ref: '#/components/schemas/updatedAt'
+    fileMetadata:
+      type: object
+      description: The metadata for a file, including the path, size, and unix timestamps of the file.
+      nullable: true
+      properties:
+        filename:
+          description: The filename of the file.
+          type: string
+          example: Wizards First Rule 01.mp3
+        ext:
+          description: The file extension of the file.
+          type: string
+          example: .mp3
+        path:
+          description: The absolute path on the server of the file.
+          type: string
+          example: >-
+              /audiobooks/Terry Goodkind/Sword of Truth/Wizards First Rule/Terry
+              Goodkind - SOT Bk01 - Wizards First Rule 01.mp3
+        relPath:
+          description: The path of the file, relative to the book's or podcast's folder.
+          type: string
+          example: Wizards First Rule 01.mp3
+        size:
+          $ref: '#/components/schemas/size'
+        mtimeMs:
+          description: The time (in ms since POSIX epoch) when the file was last modified on disk.
+          type: integer
+          example: 1632223180278
+        ctimeMs:
+          description: The time (in ms since POSIX epoch) when the file status was changed on disk.
+          type: integer
+          example: 1645978261001
+        birthtimeMs:
+          description: The time (in ms since POSIX epoch) when the file was created on disk. Will be 0 if unknown.
+          type: integer
+          example: 0
+    audioMetaTags:
+      description: ID3 metadata tags pulled from the audio file on import. Only non-null tags will be returned in requests.
+      type: object
+      properties:
+        tagAlbum:
+          type: string
+          nullable: true
+          example: SOT Bk01
+        tagArtist:
+          type: string
+          nullable: true
+          example: Terry Goodkind
+        tagGenre:
+          type: string
+          nullable: true
+          example: Audiobook Fantasy
+        tagTitle:
+          type: string
+          nullable: true
+          example: Wizards First Rule 01
+        tagSeries:
+          type: string
+          nullable: true
+        tagSeriesPart:
+          type: string
+          nullable: true
+        tagTrack:
+          type: string
+          nullable: true
+          example: 01/20
+        tagDisc:
+          type: string
+          nullable: true
+        tagSubtitle:
+          type: string
+          nullable: true
+        tagAlbumArtist:
+          type: string
+          nullable: true
+          example: Terry Goodkind
+        tagDate:
+          type: string
+          nullable: true
+        tagComposer:
+          type: string
+          nullable: true
+          example: Terry Goodkind
+        tagPublisher:
+          type: string
+          nullable: true
+        tagComment:
+          type: string
+          nullable: true
+        tagDescription:
+          type: string
+          nullable: true
+        tagEncoder:
+          type: string
+          nullable: true
+        tagEncodedBy:
+          type: string
+          nullable: true
+        tagIsbn:
+          type: string
+          nullable: true
+        tagLanguage:
+          type: string
+          nullable: true
+        tagASIN:
+          type: string
+          nullable: true
+        tagOverdriveMediaMarker:
+          type: string
+          nullable: true
+        tagOriginalYear:
+          type: string
+          nullable: true
+        tagReleaseCountry:
+          type: string
+          nullable: true
+        tagReleaseType:
+          type: string
+          nullable: true
+        tagReleaseStatus:
+          type: string
+          nullable: true
+        tagISRC:
+          type: string
+          nullable: true
+        tagMusicBrainzTrackId:
+          type: string
+          nullable: true
+        tagMusicBrainzAlbumId:
+          type: string
+          nullable: true
+        tagMusicBrainzAlbumArtistId:
+          type: string
+          nullable: true
+        tagMusicBrainzArtistId:
+          type: string
+          nullable: true
+    addedAt:
+      type: integer
+      description: The time (in ms since POSIX epoch) when added to the server.
+      example: 1633522963509
+    createdAt:
+      type: integer
+      description: The time (in ms since POSIX epoch) when was created.
+      example: 1633522963509
+    updatedAt:
+      type: integer
+      description: The time (in ms since POSIX epoch) when last updated.
+      example: 1633522963509
+    size:
+      description: The total size (in bytes) of the item or file.
+      type: integer
+      example: 268824228
+    durationSec:
+      description: The total length (in seconds) of the item or file.
+      type: number
+      example: 33854.905
+    tags:
+      description: Tags applied to items.
+      type: array
+      items:
+        type: string
+      example: ["To Be Read", "Genre: Nonfiction"]
+    inode:
+      description: The inode of the item in the file system.
+      type: string
+      format: "[0-9]*"
+      example: '649644248522215260'
+    seriesId:
+      type: string
+      description: The ID of the series.
+      format: uuid
+      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
+    seriesName:
+      description: The name of the series.
+      type: string
+      example: Sword of Truth
+    folderId:
+      type: string
+      description: The ID of the folder.
+      format: uuid
+      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
+    folder:
+      type: object
+      description: Folder used in library
+      properties:
+        id:
+          $ref: '#/components/schemas/folderId'
+        fullPath:
+          description: The path on the server for the folder. (Read Only)
+          type: string
+          example: /podcasts
+        libraryId:
+          $ref: '#/components/schemas/libraryId'
+        addedAt:
+          $ref: '#/components/schemas/addedAt'
+    authorUpdated:
+      description: Whether the author was updated without errors. Will not exist if author was merged.
+      type: boolean
+      nullable: true
+    authorId:
+      type: string
+      description: The ID of the author.
+      format: uuid
+      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
+    authorASIN:
+      type: string
+      description: The Audible identifier (ASIN) of the author. Will be null if unknown. Not the Amazon identifier.
+      nullable: true
+      example: B000APZOQA
+    authorName:
+      description: The name of the author.
+      type: string
+      example: Terry Goodkind
+    authorSeries:
+      type: object
+      description: Series and the included library items that an author has written.
+      properties:
+        id:
+          $ref: '#/components/schemas/seriesId'
+        name:
+          $ref: '#/components/schemas/seriesName'
+        items:
+          description: The items in the series. Each library item's media's metadata will have a `series` attribute, a `Series Sequence`, which is the matching series.
+          type: array
+          items:
+            $ref: '#/components/schemas/libraryItemMinified'
+    author:
+      description: An author object which includes a description and image path.
+      type: object
+      properties:
+        id:
+          $ref: '#/components/schemas/authorId'
+        asin:
+          $ref: '#/components/schemas/authorASIN'
+        name:
+          $ref: '#/components/schemas/authorName'
+        description:
+          description: A description of the author. Will be null if there is none.
+          type: string
+          nullable: true
+          example: |
+            Terry Goodkind is a #1 New York Times Bestselling Author and creator of the critically acclaimed masterwork,
+            ‘The Sword of Truth’. He has written 30+ major, bestselling novels, has been published in more than 20
+            languages world-wide, and has sold more than 26 Million books. ‘The Sword of Truth’ is a revered literary
+            tour de force, comprised of 17 volumes, borne from over 25 years of dedicated writing. Terry Goodkind's
+            brilliant books are character-driven stories, with a focus on the complexity of the human psyche. Goodkind
+            has an uncanny grasp for crafting compelling stories about people like you and me, trapped in terrifying
+            situations.
+        imagePath:
+          description: The absolute path for the author image located in the `metadata/` directory. Will be null if there is no image.
+          type: string
+          nullable: true
+          example: /metadata/authors/aut_bxxbyjiptmgb56yzoz.jpg
+        addedAt:
+          $ref: '#/components/schemas/addedAt'
+        updatedAt:
+          $ref: '#/components/schemas/updatedAt'
+    authorWithItems:
+      type: object
+      description: The author schema with an array of items they are associated with.
+      allOf:
+        - $ref: '#/components/schemas/author'
+        - type: object
+          properties:
+            libraryItems:
+              description: The items associated with the author
+              type: array
+              items:
+                $ref: '#/components/schemas/libraryItemMinified'
+    authorWithSeries:
+      type: object
+      description: The author schema with an array of items and series they are associated with.
+      allOf:
+        - $ref: '#/components/schemas/authorWithItems'
+        - type: object
+          properties:
+            series:
+              description: The series associated with the author
+              type: array
+              items:
+                $ref: '#/components/schemas/authorSeries'
+    authorMinified:
+      type: object
+      description: Minified author object which only contains the author name and ID.
+      properties:
+        id:
+          $ref: '#/components/schemas/authorId'
+        name:
+          $ref: '#/components/schemas/authorName'
+    authorExpanded:
+      type: object
+      description: The author schema with the total number of books in the library.
+      allOf:
+        - $ref: '#/components/schemas/author'
+        - type: object
+          properties:
+            numBooks:
+              description: The number of books associated with the author in the library.
+              type: integer
+              example: 1
+    oldLibraryId:
+      type: string
+      description: The ID of the libraries created on server version 2.2.23 and before.
+      format: "lib_[a-z0-9]{18}"
+      example: lib_o78uaoeuh78h6aoeif
+    libraryId:
+      type: string
+      description: The ID of the library.
+      format: uuid
+      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
+    oldLibraryItemId:
+      description: The ID of library items on server version 2.2.23 and before.
+      type: string
+      nullable: true
+      format: "li_[a-z0-9]{18}"
+      example: li_o78uaoeuh78h6aoeif
+    libraryItemId:
+      type: string
+      description: The ID of library items after 2.3.0.
+      format: uuid
+      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
+    libraryItemBase:
+      type: object
+      description: Base library item schema
+      properties:
+        id:
+          $ref: '#/components/schemas/libraryItemId'
+        oldLibraryItemId:
+          $ref: '#/components/schemas/oldLibraryItemId'
+        ino:
+          $ref: '#/components/schemas/inode'
+        libraryId:
+          $ref: '#/components/schemas/libraryId'
+        folderId:
+          $ref: '#/components/schemas/folderId'
+        path:
+          description: The path of the library item on the server.
+          type: string
+        relPath:
+          description: The path, relative to the library folder, of the library item.
+          type: string
+        isFile:
+          description: Whether the library item is a single file in the root of the library folder.
+          type: boolean
+        mtimeMs:
+          description: The time (in ms since POSIX epoch) when the library item was last modified on disk.
+          type: integer
+        ctimeMs:
+          description: The time (in ms since POSIX epoch) when the library item status was changed on disk.
+          type: integer
+        birthtimeMs:
+          description: The time (in ms since POSIX epoch) when the library item was created on disk. Will be 0 if unknown.
+          type: integer
+        addedAt:
+          $ref: '#/components/schemas/addedAt'
+        updatedAt:
+          $ref: '#/components/schemas/updatedAt'
+        isMissing:
+          description: Whether the library item was scanned and no longer exists.
+          type: boolean
+        isInvalid:
+          description: Whether the library item was scanned and no longer has media files.
+          type: boolean
+        mediaType:
+          $ref: '#/components/schemas/mediaType'
+    libraryItemMinified:
+      type: object
+      description: A single item on the server, like a book or podcast. Minified media format.
+      allOf:
+        - $ref : '#/components/schemas/libraryItemBase'
+        - type: object
+          properties:
+            media:
+              $ref: '#/components/schemas/mediaMinified'
+  parameters:
+    authorID:
+      name: id
+      in: path
+      description: Author ID
+      required: true
+      schema:
+        $ref: '#/components/schemas/authorId'
+    authorInclude:
+      name: include
+      in: query
+      description: A comma separated list of what to include with the author. The options are `items` and `series`. `series` will only have an effect if `items` is included.
+      required: false
+      schema:
+        type: string
+        example: "items"
+      examples:
+        empty:
+          summary: Do not return library items
+          value: ""
+        itemOnly:
+          summary: Only return library items
+          value: "items"
+        itemsAndSeries:
+          summary: Return library items and series
+          value: "items,series"
+    authorLibraryId:
+      name: library
+      in: query
+      description: The ID of the library to to include filter included items from.
+      required: false
+      schema:
+        $ref: '#/components/schemas/libraryId'
+    asin:
+      name: asin
+      in: query
+      description: The Audible Identifier (ASIN).
+      required: false
+      schema:
+        $ref: '#/components/schemas/authorASIN'
+    authorSearchName:
+      name: q
+      in: query
+      description: The name of the author to use for searching.
+      required: false
+      schema:
+        type: string
+        example: Terry Goodkind
+    authorName:
+      name: name
+      in: query
+      description: The new name of the author.
+      required: false
+      schema:
+        $ref: '#/components/schemas/authorName'
+    authorDescription:
+      name: description
+      in: query
+      description: The new description of the author.
+      required: false
+      schema:
+        type: string
+        nullable: true
+        example: Terry Goodkind is a #1 New York Times Bestselling Author and creator of the critically acclaimed masterwork, ‘The Sword of Truth’. He has written 30+ major, bestselling novels, has been published in more than 20 languages world-wide, and has sold more than 26 Million books. ‘The Sword of Truth’ is a revered literary tour de force, comprised of 17 volumes, borne from over 25 years of dedicated writing.
+    authorImagePath:
+      name: imagePath
+      in: query
+      description: The new absolute path for the author image.
+      required: false
+      schema:
+        type: string
+        nullable: true
+        example: /metadata/authors/aut_z3leimgybl7uf3y4ab.jpg
+    imageURL:
+      name: url
+      in: query
+      description: The URL of the image to add to the server
+      required: true
+      schema:
+        type: string
+        format: uri
+        example: https://images-na.ssl-images-amazon.com/images/I/51NoQTm33OL.__01_SX120_CR0,0,120,120__.jpg
+    imageWidth:
+      name: width
+      in: query
+      description: The requested width of image in pixels.
+      schema:
+        type: integer
+        default: 400
+        example: 400
+      example: 400
+    imageHeight:
+      name: height
+      in: query
+      description: The requested height of image in pixels. If `null`, the height is scaled to maintain aspect ratio based on the requested width.
+      schema:
+        type: integer
+        nullable: true
+        default: null
+        example: 600
+      examples:
+        scaleHeight:
+          summary: Scale height with width
+          value: null
+        fixedHeight:
+          summary: Force height of image
+          value: 600
+    imageFormat:
+      name: format
+      in: query
+      description: The requested output format.
+      schema:
+        type: string
+        default: jpeg
+        example: webp
+    imageRaw:
+      name: raw
+      in: query
+      description: Return the raw image without scaling if true.
+      schema:
+        type: boolean
+        default: false
+  responses: 
+    ok200:
+      description: OK
+    author404:
+      description: Author not found.
+      content:
+        text/html:
+          schema:
+            type: string
+            example: Not found
+  paths:
+    /api/authors/{id}:
+      get:
+        operationId: getAuthorByID
+        summary: Get a single author by ID on server
+        tags:
+          - Authors
+        parameters:
+          - $ref: '#/components/parameters/authorID'
+          - $ref: '#/components/parameters/authorInclude'
+          - $ref: '#/components/parameters/authorLibraryId'
+        responses:
+          200:
+            description: getAuthorByID OK
+            content:
+              application/json:
+                schema:
+                  oneOf:
+                    - $ref: '#/components/schemas/author'
+                    - $ref: '#/components/schemas/authorWithItems'
+                    - $ref: '#/components/schemas/authorWithSeries'
+          404:
+            $ref: '#/components/responses/author404'
+    /api/authors/{id}:
+      patch:
+        operationId: updateAuthorByID
+        summary: Update a single author by ID on server. This endpoint will merge two authors if the new author name matches another author in the database.
+        tags:
+          - Authors
+        parameters:
+          - $ref: '#/components/parameters/authorID'
+          - $ref: '#/components/parameters/asin'
+          - $ref: '#/components/parameters/authorName'
+          - $ref: '#/components/parameters/authorDescription'
+          - $ref: '#/components/parameters/authorImagePath'
+        responses:
+          200:
+            description: updateAuthorByID OK
+            content:
+              application/json:
+                schema:
+                  allOf:
+                    - $ref: '#/components/schemas/author'
+                    - $ref: '#/components/schemas/authorUpdated'
+                    - type: object
+                      properties:
+                        merged:
+                          description: Will only exist and be `true` if the author was merged with another author
+                          type: boolean
+                          nullable: true
+          404:
+            $ref: '#/components/responses/author404'
+    /api/authors/{id}:
+      delete:
+        operationId: deleteAuthorByID
+        summary: Delete a single author by ID on server and remove author from all books.
+        tags:
+          - Authors
+        parameters:
+          - $ref: '#/components/parameters/authorID'
+        responses:
+          200:
+            $ref: '#/components/responses/ok200'
+          404:
+            $ref: '#/components/responses/author404'
+    /api/authors/{id}/image:
+      post:
+        operationId: setAuthorImageByID
+        summary: Set an author image using a provided URL.
+        tags:
+          - Authors
+        parameters:
+          - $ref: '#/components/parameters/authorID'
+          - $ref: '#/components/parameters/imageURL'
+        responses:
+          200:
+            description: setAuthorImageByID OK
+            content:
+              application/json:
+                schema:
+                  oneOf:
+                    - $ref: '#/components/schemas/author'
+          404:
+            $ref: '#/components/responses/author404'
+    /api/authors/{id}/image:
+      delete:
+        operationId: deleteAuthorImageByID
+        summary: Delete an author image from the server and remove the image from the database.
+        tags:
+          - Authors
+        parameters:
+          - $ref: '#/components/parameters/authorID'
+        responses:
+          200:
+            $ref: '#/components/responses/ok200'
+          404:
+            $ref: '#/components/responses/author404'
+    /api/authors/{id}/match:
+      post:
+        operationId: matchAuthorByID
+        summary: Match the author against Audible using quick match. Quick match updates the author's description and image (if no image already existed) with information from audible. Either `asin` or `q` must be provided, with `asin` taking priority if both are provided.
+        tags:
+          - Authors
+        parameters:
+          - $ref: '#/components/parameters/authorID'
+          - $ref: '#/components/parameters/asin'
+          - $ref: '#/components/parameters/authorSearchName'
+        responses:
+          200:
+            description: matchAuthorByID OK
+            content:
+              application/json:
+                schema:
+                  allOf:
+                    - $ref: '#/components/schemas/author'
+                    - $ref: '#/components/schemas/authorUpdated'
+          404:
+            $ref: '#/components/responses/author404'
+    /api/authors/{id}/image:
+      patch:
+        operationId: getAuthorImageByID
+        summary: Return the author image by author ID.
+        tags:
+          - Authors
+        parameters:
+          - $ref: '#/components/parameters/authorID'
+          - $ref: '#/components/parameters/imageWidth'
+          - $ref: '#/components/parameters/imageHeight'
+          - $ref: '#/components/parameters/imageFormat'
+          - $ref: '#/components/parameters/imageRaw'
+        responses:
+          200:
+            description: getAuthorImageByID OK
+            content:
+              image/*:
+                schema:
+                  type: string
+                  format: binary
+          404:
+            $ref: '#/components/responses/author404'
+tags:
+  - name: Authors
+    description: Author endpoints

From c7ac12a67a3b3bf9fbd4b64b333d1f8563bb4434 Mon Sep 17 00:00:00 2001
From: Nicholas Wallace <nicholaslwallace@gmail.com>
Date: Sun, 31 Mar 2024 22:47:14 +0000
Subject: [PATCH 2/7] Split schema to sub files

---
 docs/controllers/AuthorController.yaml   |  139 +++
 docs/objects/Folder.yaml                 |   21 +
 docs/objects/Library.yaml                |   12 +
 docs/objects/LibraryItem.yaml            |   66 ++
 docs/objects/entities/Author.yaml        |  104 ++
 docs/objects/entities/Series.yaml        |   11 +
 docs/objects/files/AudioFile.yaml        |   94 ++
 docs/objects/mediaTypes/Book.yaml        |   70 ++
 docs/objects/mediaTypes/media.yaml       |   10 +
 docs/objects/metadata/AudioMetaTags.yaml |  103 ++
 docs/objects/metadata/BookMetadata.yaml  |  126 +++
 docs/objects/metadata/FileMetadata.yaml  |   39 +
 docs/schemas.yaml                        |   33 +
 docs/spec.yaml                           | 1093 +++-------------------
 14 files changed, 964 insertions(+), 957 deletions(-)
 create mode 100644 docs/controllers/AuthorController.yaml
 create mode 100644 docs/objects/Folder.yaml
 create mode 100644 docs/objects/Library.yaml
 create mode 100644 docs/objects/LibraryItem.yaml
 create mode 100644 docs/objects/entities/Author.yaml
 create mode 100644 docs/objects/entities/Series.yaml
 create mode 100644 docs/objects/files/AudioFile.yaml
 create mode 100644 docs/objects/mediaTypes/Book.yaml
 create mode 100644 docs/objects/mediaTypes/media.yaml
 create mode 100644 docs/objects/metadata/AudioMetaTags.yaml
 create mode 100644 docs/objects/metadata/BookMetadata.yaml
 create mode 100644 docs/objects/metadata/FileMetadata.yaml
 create mode 100644 docs/schemas.yaml

diff --git a/docs/controllers/AuthorController.yaml b/docs/controllers/AuthorController.yaml
new file mode 100644
index 0000000000..4e576af786
--- /dev/null
+++ b/docs/controllers/AuthorController.yaml
@@ -0,0 +1,139 @@
+components:
+  schemas:
+    authorUpdated:
+      description: Whether the author was updated without errors. Will not exist if author was merged.
+      type: boolean
+      nullable: true
+  parameters:
+    authorID:
+      name: id
+      in: path
+      description: Author ID
+      required: true
+      schema:
+        $ref: '../objects/entities/Author.yaml#/components/schemas/authorId'
+    authorInclude:
+      name: include
+      in: query
+      description: A comma separated list of what to include with the author. The options are `items` and `series`. `series` will only have an effect if `items` is included.
+      required: false
+      schema:
+        type: string
+        example: "items"
+      examples:
+        empty:
+          summary: Do not return library items
+          value: ""
+        itemOnly:
+          summary: Only return library items
+          value: "items"
+        itemsAndSeries:
+          summary: Return library items and series
+          value: "items,series"
+    authorLibraryId:
+      name: library
+      in: query
+      description: The ID of the library to to include filter included items from.
+      required: false
+      schema:
+        $ref: '../objects/Library.yaml#/components/schemas/libraryId'
+    asin:
+      name: asin
+      in: query
+      description: The Audible Identifier (ASIN).
+      required: false
+      schema:
+        $ref: '../objects/entities/Author.yaml#/components/schemas/authorASIN'
+    authorSearchName:
+      name: q
+      in: query
+      description: The name of the author to use for searching.
+      required: false
+      schema:
+        type: string
+        example: Terry Goodkind
+    authorName:
+      name: name
+      in: query
+      description: The new name of the author.
+      required: false
+      schema:
+        $ref: '../objects/entities/Author.yaml#/components/schemas/authorName'
+    authorDescription:
+      name: description
+      in: query
+      description: The new description of the author.
+      required: false
+      schema:
+        type: string
+        nullable: true
+        example: Terry Goodkind is a #1 New York Times Bestselling Author and creator of the critically acclaimed masterwork, ‘The Sword of Truth’. He has written 30+ major, bestselling novels, has been published in more than 20 languages world-wide, and has sold more than 26 Million books. ‘The Sword of Truth’ is a revered literary tour de force, comprised of 17 volumes, borne from over 25 years of dedicated writing.
+    authorImagePath:
+      name: imagePath
+      in: query
+      description: The new absolute path for the author image.
+      required: false
+      schema:
+        type: string
+        nullable: true
+        example: /metadata/authors/aut_z3leimgybl7uf3y4ab.jpg
+    imageURL:
+      name: url
+      in: query
+      description: The URL of the image to add to the server
+      required: true
+      schema:
+        type: string
+        format: uri
+        example: https://images-na.ssl-images-amazon.com/images/I/51NoQTm33OL.__01_SX120_CR0,0,120,120__.jpg
+    imageWidth:
+      name: width
+      in: query
+      description: The requested width of image in pixels.
+      schema:
+        type: integer
+        default: 400
+        example: 400
+      example: 400
+    imageHeight:
+      name: height
+      in: query
+      description: The requested height of image in pixels. If `null`, the height is scaled to maintain aspect ratio based on the requested width.
+      schema:
+        type: integer
+        nullable: true
+        default: null
+        example: 600
+      examples:
+        scaleHeight:
+          summary: Scale height with width
+          value: null
+        fixedHeight:
+          summary: Force height of image
+          value: 600
+    imageFormat:
+      name: format
+      in: query
+      description: The requested output format.
+      schema:
+        type: string
+        default: jpeg
+        example: webp
+    imageRaw:
+      name: raw
+      in: query
+      description: Return the raw image without scaling if true.
+      schema:
+        type: boolean
+        default: false
+  responses: 
+    author404:
+      description: Author not found.
+      content:
+        text/html:
+          schema:
+            type: string
+            example: Not found
+tags:
+  - name: Authors
+    description: Author endpoints
diff --git a/docs/objects/Folder.yaml b/docs/objects/Folder.yaml
new file mode 100644
index 0000000000..5c477b35a7
--- /dev/null
+++ b/docs/objects/Folder.yaml
@@ -0,0 +1,21 @@
+components:
+  schemas:
+    folderId:
+      type: string
+      description: The ID of the folder.
+      format: uuid
+      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
+    folder:
+      type: object
+      description: Folder used in library
+      properties:
+        id:
+          $ref: '#/components/schemas/folderId'
+        fullPath:
+          description: The path on the server for the folder. (Read Only)
+          type: string
+          example: /podcasts
+        libraryId:
+          $ref: './Library.yaml#/components/schemas/libraryId'
+        addedAt:
+          $ref: '../schemas.yaml#/components/schemas/addedAt'
diff --git a/docs/objects/Library.yaml b/docs/objects/Library.yaml
new file mode 100644
index 0000000000..7d2a7833c9
--- /dev/null
+++ b/docs/objects/Library.yaml
@@ -0,0 +1,12 @@
+components:
+  schemas:
+    oldLibraryId:
+      type: string
+      description: The ID of the libraries created on server version 2.2.23 and before.
+      format: "lib_[a-z0-9]{18}"
+      example: lib_o78uaoeuh78h6aoeif
+    libraryId:
+      type: string
+      description: The ID of the library.
+      format: uuid
+      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
diff --git a/docs/objects/LibraryItem.yaml b/docs/objects/LibraryItem.yaml
new file mode 100644
index 0000000000..5d2f2b3dae
--- /dev/null
+++ b/docs/objects/LibraryItem.yaml
@@ -0,0 +1,66 @@
+components:
+  schemas:
+    oldLibraryItemId:
+      description: The ID of library items on server version 2.2.23 and before.
+      type: string
+      nullable: true
+      format: "li_[a-z0-9]{18}"
+      example: li_o78uaoeuh78h6aoeif
+    libraryItemId:
+      type: string
+      description: The ID of library items after 2.3.0.
+      format: uuid
+      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
+    libraryItemBase:
+      type: object
+      description: Base library item schema
+      properties:
+        id:
+          $ref: '#/components/schemas/libraryItemId'
+        oldLibraryItemId:
+          $ref: '#/components/schemas/oldLibraryItemId'
+        ino:
+          $ref: '../schemas.yaml#/components/schemas/inode'
+        libraryId:
+          $ref: './Library.yaml#/components/schemas/libraryId'
+        folderId:
+          $ref: './Folder.yaml#/components/schemas/folderId'
+        path:
+          description: The path of the library item on the server.
+          type: string
+        relPath:
+          description: The path, relative to the library folder, of the library item.
+          type: string
+        isFile:
+          description: Whether the library item is a single file in the root of the library folder.
+          type: boolean
+        mtimeMs:
+          description: The time (in ms since POSIX epoch) when the library item was last modified on disk.
+          type: integer
+        ctimeMs:
+          description: The time (in ms since POSIX epoch) when the library item status was changed on disk.
+          type: integer
+        birthtimeMs:
+          description: The time (in ms since POSIX epoch) when the library item was created on disk. Will be 0 if unknown.
+          type: integer
+        addedAt:
+          $ref: '../schemas.yaml#/components/schemas/addedAt'
+        updatedAt:
+          $ref: '../schemas.yaml#/components/schemas/updatedAt'
+        isMissing:
+          description: Whether the library item was scanned and no longer exists.
+          type: boolean
+        isInvalid:
+          description: Whether the library item was scanned and no longer has media files.
+          type: boolean
+        mediaType:
+          $ref: './mediaTypes/media.yaml#/components/schemas/mediaType'
+    libraryItemMinified:
+      type: object
+      description: A single item on the server, like a book or podcast. Minified media format.
+      allOf:
+        - $ref : '#/components/schemas/libraryItemBase'
+        - type: object
+          properties:
+            media:
+              $ref: './mediaTypes/media.yaml#/components/schemas/mediaMinified'
diff --git a/docs/objects/entities/Author.yaml b/docs/objects/entities/Author.yaml
new file mode 100644
index 0000000000..d2b173fc54
--- /dev/null
+++ b/docs/objects/entities/Author.yaml
@@ -0,0 +1,104 @@
+components:
+  schemas:
+    authorId:
+      type: string
+      description: The ID of the author.
+      format: uuid
+      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
+    authorASIN:
+      type: string
+      description: The Audible identifier (ASIN) of the author. Will be null if unknown. Not the Amazon identifier.
+      nullable: true
+      example: B000APZOQA
+    authorName:
+      description: The name of the author.
+      type: string
+      example: Terry Goodkind
+    authorSeries:
+      type: object
+      description: Series and the included library items that an author has written.
+      properties:
+        id:
+          $ref: './Series.yaml#/components/schemas/seriesId'
+        name:
+          $ref: './Series.yaml#/components/schemas/seriesName'
+        items:
+          description: The items in the series. Each library item's media's metadata will have a `series` attribute, a `Series Sequence`, which is the matching series.
+          type: array
+          items:
+            ref: '../LibraryItem.yaml#/components/schemas/libraryItemMinified'
+    author:
+      description: An author object which includes a description and image path.
+      type: object
+      properties:
+        id:
+          $ref: '#/components/schemas/authorId'
+        asin:
+          $ref: '#/components/schemas/authorASIN'
+        name:
+          $ref: '#/components/schemas/authorName'
+        description:
+          description: A description of the author. Will be null if there is none.
+          type: string
+          nullable: true
+          example: |
+            Terry Goodkind is a #1 New York Times Bestselling Author and creator of the critically acclaimed masterwork,
+            ‘The Sword of Truth’. He has written 30+ major, bestselling novels, has been published in more than 20
+            languages world-wide, and has sold more than 26 Million books. ‘The Sword of Truth’ is a revered literary
+            tour de force, comprised of 17 volumes, borne from over 25 years of dedicated writing. Terry Goodkind's
+            brilliant books are character-driven stories, with a focus on the complexity of the human psyche. Goodkind
+            has an uncanny grasp for crafting compelling stories about people like you and me, trapped in terrifying
+            situations.
+        imagePath:
+          description: The absolute path for the author image located in the `metadata/` directory. Will be null if there is no image.
+          type: string
+          nullable: true
+          example: /metadata/authors/aut_bxxbyjiptmgb56yzoz.jpg
+        addedAt:
+          $ref: '../../schemas.yaml#/components/schemas/addedAt'
+        updatedAt:
+          $ref: '../../schemas.yaml#/components/schemas/updatedAt'
+    authorWithItems:
+      type: object
+      description: The author schema with an array of items they are associated with.
+      allOf:
+        - $ref: '#/components/schemas/author'
+        - type: object
+          properties:
+            libraryItems:
+              description: The items associated with the author
+              type: string
+              type: array
+              items:
+                $ref: '../LibraryItem.yaml#/components/schemas/libraryItemMinified'
+    authorWithSeries:
+      type: object
+      description: The author schema with an array of items and series they are associated with.
+      allOf:
+        - $ref: '#/components/schemas/authorWithItems'
+        - type: object
+          properties:
+            series:
+              description: The series associated with the author
+              type: array
+              items:
+                $ref: '#/components/schemas/authorSeries'
+    authorMinified:
+      type: object
+      description: Minified author object which only contains the author name and ID.
+      properties:
+        id:
+          $ref: '#/components/schemas/authorId'
+        name:
+          $ref: '#/components/schemas/authorName'
+    authorExpanded:
+      type: object
+      description: The author schema with the total number of books in the library.
+      allOf:
+        - $ref: '#/components/schemas/author'
+        - type: object
+          properties:
+            numBooks:
+              description: The number of books associated with the author in the library.
+              type: integer
+              example: 1
\ No newline at end of file
diff --git a/docs/objects/entities/Series.yaml b/docs/objects/entities/Series.yaml
new file mode 100644
index 0000000000..af818477c0
--- /dev/null
+++ b/docs/objects/entities/Series.yaml
@@ -0,0 +1,11 @@
+components:
+  schemas:
+    seriesId:
+      type: string
+      description: The ID of the series.
+      format: uuid
+      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
+    seriesName:
+      description: The name of the series.
+      type: string
+      example: Sword of Truth
\ No newline at end of file
diff --git a/docs/objects/files/AudioFile.yaml b/docs/objects/files/AudioFile.yaml
new file mode 100644
index 0000000000..06a9c80ccb
--- /dev/null
+++ b/docs/objects/files/AudioFile.yaml
@@ -0,0 +1,94 @@
+components:
+  schemas:
+    audioFile:
+      type: object
+      description: An audio file for a book. Includes audio metadata and track numbers.
+      properties:
+        index:
+          description: The index of the audio file.
+          type: integer
+          example: 1
+        ino:
+          $ref: '../../schemas.yaml#/components/schemas/inode'
+        metadata:
+          $ref: '../metadata/FileMetadata.yaml#/components/schemas/fileMetadata'
+        addedAt:
+          $ref: '../../schemas.yaml#/components/schemas/addedAt'
+        updatedAt:
+          $ref: '../../schemas.yaml#/components/schemas/updatedAt'
+        trackNumFromMeta:
+          description: The track number of the audio file as pulled from the file's metadata. Will be null if unknown.
+          type: integer
+          nullable: true
+          example: 1
+        discNumFromMeta:
+          description: The disc number of the audio file as pulled from the file's metadata. Will be null if unknown.
+          type: string
+          nullable: true
+        trackNumFromFilename:
+          description: The track number of the audio file as determined from the file's name. Will be null if unknown.
+          type: integer
+          nullable: true
+          example: 1
+        discNumFromFilename:
+          description: The disc number of the audio file as determined from the file's name. Will be null if unknown.
+          type: string
+          nullable: true
+        manuallyVerified:
+          description: Whether the audio file has been manually verified by a user.
+          type: boolean
+        invalid:
+          description: Whether the audio file is missing from the server.
+          type: boolean
+        exclude:
+          description: Whether the audio file has been marked for exclusion.
+          type: boolean
+        error:
+          description: Any error with the audio file. Will be null if there is none.
+          type: string
+          nullable: true
+        format:
+          description: The format of the audio file.
+          type: string
+          example: MP2/3 (MPEG audio layer 2/3)
+        duration:
+          $ref: '#/components/schemas/durationSec'
+        bitRate:
+          description: The bit rate (in bit/s) of the audio file.
+          type: integer
+          example: 64000
+        language:
+          description: The language of the audio file.
+          type: string
+          nullable: true
+        codec:
+          description: The codec of the audio file.
+          type: string
+          example: mp3
+        timeBase:
+          description: The time base of the audio file.
+          type: string
+          example: 1/14112000
+        channels:
+          description: The number of channels the audio file has.
+          type: integer
+          example: 2
+        channelLayout:
+          description: The layout of the audio file's channels.
+          type: string
+          example: stereo
+        chapters:
+          description: If the audio file is part of an audiobook, the chapters the file contains.
+          type: array
+          items:
+            $ref: '../metadata/BookMetadata.yaml#/components/schemas/bookChapter'
+        embeddedCoverArt:
+          description: The type of embedded cover art in the audio file. Will be null if none exists.
+          type: string
+          nullable: true
+        metaTags:
+          $ref: '../metadata/AudioMetaTags.yaml#/components/schemas/audioMetaTags'
+        mimeType:
+          description: The MIME type of the audio file.
+          type: string
+          example: audio/mpeg
diff --git a/docs/objects/mediaTypes/Book.yaml b/docs/objects/mediaTypes/Book.yaml
new file mode 100644
index 0000000000..44f3552e57
--- /dev/null
+++ b/docs/objects/mediaTypes/Book.yaml
@@ -0,0 +1,70 @@
+components:
+  schemas:
+    bookCoverPath:
+      description: The absolute path on the server of the cover file. Will be null if there is no cover.
+      type: string
+      nullable: true
+      example: /audiobooks/Terry Goodkind/Sword of Truth/Wizards First Rule/cover.jpg
+    bookBase:
+      type: object
+      description: Base book schema
+      properties:
+        libraryItemId:
+          $ref: '../LibraryItem.yaml#/components/schemas/libraryItemId'
+        coverPath:
+          $ref: '#/components/schemas/bookCoverPath'
+        tags:
+          $ref: '../../schemas.yaml#/components/schemas/tags'
+        audioFiles:
+          type: array
+          items:
+            $ref: '#/components/schemas/audioFile'
+        chapters:
+          type: array
+          items:
+            $ref: '#/components/schemas/bookChapter'
+        missingParts:
+          description: Any parts missing from the book by track index.
+          type: array
+          items:
+            type: integer
+        ebookFile:
+          $ref: '#/components/schemas/ebookFile'
+    bookMinified:
+      type: object
+      description: Minified book schema. Does not depend on `bookBase` because there's pretty much no overlap.
+      properties:
+        metadata:
+          $ref: '../metadata/BookMetadata.yaml#/components/schemas/bookMetadataMinified'
+        coverPath:
+          $ref: '#/components/schemas/bookCoverPath'
+        tags:
+          $ref: '../../schemas.yaml#/components/schemas/tags'
+        numTracks:
+          description: The number of tracks the book's audio files have.
+          type: integer
+          example: 1
+        numAudioFiles:
+          description: The number of audio files the book has.
+          type: integer
+          example: 1
+        numChapters:
+          description: The number of chapters the book has.
+          type: integer
+          example: 1
+        numMissingParts:
+          description: The total number of missing parts the book has.
+          type: integer
+          example: 0
+        numInvalidAudioFiles:
+          description: The number of invalid audio files the book has.
+          type: integer
+          example: 0
+        duration:
+          $ref: '../../schemas.yaml#/components/schemas/durationSec'
+        size:
+          $ref: '../../schemas.yaml#/components/schemas/size'
+        ebookFormat:
+          description: The format of ebook of the book. Will be null if the book is an audiobook.
+          type: string
+          nullable: true
diff --git a/docs/objects/mediaTypes/media.yaml b/docs/objects/mediaTypes/media.yaml
new file mode 100644
index 0000000000..a7d4b08eff
--- /dev/null
+++ b/docs/objects/mediaTypes/media.yaml
@@ -0,0 +1,10 @@
+components:
+  schemas:
+    mediaType:
+      type: string
+      description: The type of media, will be book or podcast.
+      enum: [book, podcast]
+    mediaMinified:
+      description: The minified media of the library item.
+      oneOf:
+        - $ref: './Book.yaml#/components/schemas/bookMinified'
diff --git a/docs/objects/metadata/AudioMetaTags.yaml b/docs/objects/metadata/AudioMetaTags.yaml
new file mode 100644
index 0000000000..ef4058a82b
--- /dev/null
+++ b/docs/objects/metadata/AudioMetaTags.yaml
@@ -0,0 +1,103 @@
+components:
+  schemas:
+    audioMetaTags:
+      description: ID3 metadata tags pulled from the audio file on import. Only non-null tags will be returned in requests.
+      type: object
+      properties:
+        tagAlbum:
+          type: string
+          nullable: true
+          example: SOT Bk01
+        tagArtist:
+          type: string
+          nullable: true
+          example: Terry Goodkind
+        tagGenre:
+          type: string
+          nullable: true
+          example: Audiobook Fantasy
+        tagTitle:
+          type: string
+          nullable: true
+          example: Wizards First Rule 01
+        tagSeries:
+          type: string
+          nullable: true
+        tagSeriesPart:
+          type: string
+          nullable: true
+        tagTrack:
+          type: string
+          nullable: true
+          example: 01/20
+        tagDisc:
+          type: string
+          nullable: true
+        tagSubtitle:
+          type: string
+          nullable: true
+        tagAlbumArtist:
+          type: string
+          nullable: true
+          example: Terry Goodkind
+        tagDate:
+          type: string
+          nullable: true
+        tagComposer:
+          type: string
+          nullable: true
+          example: Terry Goodkind
+        tagPublisher:
+          type: string
+          nullable: true
+        tagComment:
+          type: string
+          nullable: true
+        tagDescription:
+          type: string
+          nullable: true
+        tagEncoder:
+          type: string
+          nullable: true
+        tagEncodedBy:
+          type: string
+          nullable: true
+        tagIsbn:
+          type: string
+          nullable: true
+        tagLanguage:
+          type: string
+          nullable: true
+        tagASIN:
+          type: string
+          nullable: true
+        tagOverdriveMediaMarker:
+          type: string
+          nullable: true
+        tagOriginalYear:
+          type: string
+          nullable: true
+        tagReleaseCountry:
+          type: string
+          nullable: true
+        tagReleaseType:
+          type: string
+          nullable: true
+        tagReleaseStatus:
+          type: string
+          nullable: true
+        tagISRC:
+          type: string
+          nullable: true
+        tagMusicBrainzTrackId:
+          type: string
+          nullable: true
+        tagMusicBrainzAlbumId:
+          type: string
+          nullable: true
+        tagMusicBrainzAlbumArtistId:
+          type: string
+          nullable: true
+        tagMusicBrainzArtistId:
+          type: string
+          nullable: true
diff --git a/docs/objects/metadata/BookMetadata.yaml b/docs/objects/metadata/BookMetadata.yaml
new file mode 100644
index 0000000000..faa054ac3c
--- /dev/null
+++ b/docs/objects/metadata/BookMetadata.yaml
@@ -0,0 +1,126 @@
+components:
+  schemas:
+    narrators:
+      description: The narrators of the audiobook.
+      type: array
+      items:
+        type: string
+        example: Sam Tsoutsouvas
+    bookMetadataBase:
+      type: object
+      description: The base book metadata object for minified, normal, and extended schemas to inherit from.
+      properties:
+        title:
+          description: The title of the book. Will be null if unknown.
+          type: string
+          nullable: true
+          example: Wizards First Rule
+        subtitle:
+          description: The subtitle of the book. Will be null if there is no subtitle.
+          type: string
+          nullable: true
+        genres:
+          description: The genres of the book.
+          type: array
+          items:
+            type: string
+          example: ["Fantasy", "Sci-Fi", "Nonfiction: History"]
+        publishedYear:
+          description: The year the book was published. Will be null if unknown.
+          type: string
+          nullable: true
+          example: '2008'
+        publishedDate:
+          description: The date the book was published. Will be null if unknown.
+          type: string
+          nullable: true
+        publisher:
+          description: The publisher of the book. Will be null if unknown.
+          type: string
+          nullable: true
+          example: Brilliance Audio
+        description:
+          description: A description for the book. Will be null if empty.
+          type: string
+          nullable: true
+          example: >-
+              The masterpiece that started Terry Goodkind's New York Times bestselling
+              epic Sword of Truth In the aftermath of the brutal murder of his father,
+              a mysterious woman, Kahlan Amnell, appears in Richard Cypher's forest
+              sanctuary seeking help...and more. His world, his very beliefs, are
+              shattered when ancient debts come due with thundering violence. In a
+              dark age it takes courage to live, and more than mere courage to
+              challenge those who hold dominion, Richard and Kahlan must take up that
+              challenge or become the next victims. Beyond awaits a bewitching land
+              where even the best of their hearts could betray them. Yet, Richard
+              fears nothing so much as what secrets his sword might reveal about his
+              own soul. Falling in love would destroy them - for reasons Richard can't
+              imagine and Kahlan dare not say. In their darkest hour, hunted
+              relentlessly, tormented by treachery and loss, Kahlan calls upon Richard
+              to reach beyond his sword - to invoke within himself something more
+              noble. Neither knows that the rules of battle have just changed...or
+              that their time has run out. Wizard's First Rule is the beginning. One
+              book. One Rule. Witness the birth of a legend.
+        isbn:
+          description: The ISBN of the book. Will be null if unknown.
+          type: string
+          nullable: true
+        asin:
+          description: The ASIN of the book. Will be null if unknown.
+          type: string
+          nullable: true
+          example: B002V0QK4C
+        language:
+          description: The language of the book. Will be null if unknown.
+          type: string
+          nullable: true
+        explicit:
+          description: Whether the book has been marked as explicit.
+          type: boolean
+          example: false
+    bookMetadataMinified:
+      type: object
+      description: The minified metadata for a book in the database.
+      allOf:
+        - $ref : '#/components/schemas/bookMetadataBase'
+        - type: object
+          properties:
+            titleIgnorePrefix:
+              description: The title of the book with any prefix moved to the end.
+              type: string
+            authorName:
+              description: The name of the book's author(s).
+              type: string
+              example: Terry Goodkind
+            authorNameLF:
+              description: The name of the book's author(s) with last names first.
+              type: string
+              example: Goodkind, Terry
+            narratorName:
+              description: The name of the audiobook's narrator(s).
+              type: string
+              example: Sam Tsoutsouvas
+            seriesName:
+              description: The name of the book's series.
+              type: string
+              example: Sword of Truth
+    bookChapter:
+      type: object
+      description: A book chapter. Includes the title and timestamps.
+      properties:
+        id:
+          description: The ID of the book chapter.
+          type: integer
+          example: 0
+        start:
+          description: When in the book (in seconds) the chapter starts.
+          type: integer
+          example: 0
+        end:
+          description: When in the book (in seconds) the chapter ends.
+          type: number
+          example: 6004.6675
+        title:
+          description: The title of the chapter.
+          type: string
+          example: Wizards First Rule 01 Chapter 1
diff --git a/docs/objects/metadata/FileMetadata.yaml b/docs/objects/metadata/FileMetadata.yaml
new file mode 100644
index 0000000000..13e15e8bf8
--- /dev/null
+++ b/docs/objects/metadata/FileMetadata.yaml
@@ -0,0 +1,39 @@
+components:
+  schemas:
+    fileMetadata:
+      type: object
+      description: The metadata for a file, including the path, size, and unix timestamps of the file.
+      nullable: true
+      properties:
+        filename:
+          description: The filename of the file.
+          type: string
+          example: Wizards First Rule 01.mp3
+        ext:
+          description: The file extension of the file.
+          type: string
+          example: .mp3
+        path:
+          description: The absolute path on the server of the file.
+          type: string
+          example: >-
+              /audiobooks/Terry Goodkind/Sword of Truth/Wizards First Rule/Terry
+              Goodkind - SOT Bk01 - Wizards First Rule 01.mp3
+        relPath:
+          description: The path of the file, relative to the book's or podcast's folder.
+          type: string
+          example: Wizards First Rule 01.mp3
+        size:
+          $ref: '../../schemas.yaml#/components/schemas/size'
+        mtimeMs:
+          description: The time (in ms since POSIX epoch) when the file was last modified on disk.
+          type: integer
+          example: 1632223180278
+        ctimeMs:
+          description: The time (in ms since POSIX epoch) when the file status was changed on disk.
+          type: integer
+          example: 1645978261001
+        birthtimeMs:
+          description: The time (in ms since POSIX epoch) when the file was created on disk. Will be 0 if unknown.
+          type: integer
+          example: 0
diff --git a/docs/schemas.yaml b/docs/schemas.yaml
new file mode 100644
index 0000000000..de506d4609
--- /dev/null
+++ b/docs/schemas.yaml
@@ -0,0 +1,33 @@
+components:
+  schemas:
+    addedAt:
+      type: integer
+      description: The time (in ms since POSIX epoch) when added to the server.
+      example: 1633522963509
+    createdAt:
+      type: integer
+      description: The time (in ms since POSIX epoch) when was created.
+      example: 1633522963509
+    updatedAt:
+      type: integer
+      description: The time (in ms since POSIX epoch) when last updated.
+      example: 1633522963509
+    size:
+      description: The total size (in bytes) of the item or file.
+      type: integer
+      example: 268824228
+    durationSec:
+      description: The total length (in seconds) of the item or file.
+      type: number
+      example: 33854.905
+    tags:
+      description: Tags applied to items.
+      type: array
+      items:
+        type: string
+      example: ["To Be Read", "Genre: Nonfiction"]
+    inode:
+      description: The inode of the item in the file system.
+      type: string
+      format: "[0-9]*"
+      example: '649644248522215260'
\ No newline at end of file
diff --git a/docs/spec.yaml b/docs/spec.yaml
index dfe95b6945..fdab0125ae 100644
--- a/docs/spec.yaml
+++ b/docs/spec.yaml
@@ -3,970 +3,149 @@ info:
   title: Audiobookshelf API
   version: 0.1.0
   description: Audiobookshelf API with autogenerated OpenAPI doc
-
 servers:
   - url: http://localhost:3000
     description: Development server
-
 components:
-  schemas:
-    mediaType:
-      type: string
-      description: The type of media, will be book or podcast.
-      enum: [book, podcast]
-    mediaMinified:
-      description: The minified media of the library item.
-      oneOf:
-        - $ref: '#/components/schemas/bookMinified'
-    bookCoverPath:
-      description: The absolute path on the server of the cover file. Will be null if there is no cover.
-      type: string
-      nullable: true
-      example: /audiobooks/Terry Goodkind/Sword of Truth/Wizards First Rule/cover.jpg
-    bookBase:
-      type: object
-      description: Base book schema
-      properties:
-        libraryItemId:
-          $ref: '#/components/schemas/libraryItemId'
-        coverPath:
-          $ref: '#/components/schemas/bookCoverPath'
-        tags:
-          $ref: '#/components/schemas/tags'
-        audioFiles:
-          type: array
-          items:
-            $ref: '#/components/schemas/audioFile'
-        chapters:
-          type: array
-          items:
-            $ref: '#/components/schemas/bookChapter'
-        missingParts:
-          description: Any parts missing from the book by track index.
-          type: array
-          items:
-            type: integer
-        ebookFile:
-          $ref: '#/components/schemas/ebookFile'
-    bookMinified:
-      type: object
-      description: Minified book schema. Does not depend on `bookBase` because there's pretty much no overlap.
-      properties:
-        metadata:
-          $ref: '#/components/schemas/bookMetadataMinified'
-        coverPath:
-          $ref: '#/components/schemas/bookCoverPath'
-        tags:
-          $ref: '#/components/schemas/tags'
-        numTracks:
-          description: The number of tracks the book's audio files have.
-          type: integer
-          example: 1
-        numAudioFiles:
-          description: The number of audio files the book has.
-          type: integer
-          example: 1
-        numChapters:
-          description: The number of chapters the book has.
-          type: integer
-          example: 1
-        numMissingParts:
-          description: The total number of missing parts the book has.
-          type: integer
-          example: 0
-        numInvalidAudioFiles:
-          description: The number of invalid audio files the book has.
-          type: integer
-          example: 0
-        duration:
-          $ref: '#/components/schemas/durationSec'
-        size:
-          $ref: '#/components/schemas/size'
-        ebookFormat:
-          description: The format of ebook of the book. Will be null if the book is an audiobook.
-          type: string
-          nullable: true
-    narrators:
-      description: The narrators of the audiobook.
-      type: array
-      items:
-        type: string
-        example: Sam Tsoutsouvas
-    bookMetadataBase:
-      type: object
-      description: The base book metadata object for minified, normal, and extended schemas to inherit from.
-      properties:
-        title:
-          description: The title of the book. Will be null if unknown.
-          type: string
-          nullable: true
-          example: Wizards First Rule
-        subtitle:
-          description: The subtitle of the book. Will be null if there is no subtitle.
-          type: string
-          nullable: true
-        genres:
-          description: The genres of the book.
-          type: array
-          items:
-            type: string
-          example: ["Fantasy", "Sci-Fi", "Nonfiction: History"]
-        publishedYear:
-          description: The year the book was published. Will be null if unknown.
-          type: string
-          nullable: true
-          example: '2008'
-        publishedDate:
-          description: The date the book was published. Will be null if unknown.
-          type: string
-          nullable: true
-        publisher:
-          description: The publisher of the book. Will be null if unknown.
-          type: string
-          nullable: true
-          example: Brilliance Audio
-        description:
-          description: A description for the book. Will be null if empty.
-          type: string
-          nullable: true
-          example: >-
-              The masterpiece that started Terry Goodkind's New York Times bestselling
-              epic Sword of Truth In the aftermath of the brutal murder of his father,
-              a mysterious woman, Kahlan Amnell, appears in Richard Cypher's forest
-              sanctuary seeking help...and more. His world, his very beliefs, are
-              shattered when ancient debts come due with thundering violence. In a
-              dark age it takes courage to live, and more than mere courage to
-              challenge those who hold dominion, Richard and Kahlan must take up that
-              challenge or become the next victims. Beyond awaits a bewitching land
-              where even the best of their hearts could betray them. Yet, Richard
-              fears nothing so much as what secrets his sword might reveal about his
-              own soul. Falling in love would destroy them - for reasons Richard can't
-              imagine and Kahlan dare not say. In their darkest hour, hunted
-              relentlessly, tormented by treachery and loss, Kahlan calls upon Richard
-              to reach beyond his sword - to invoke within himself something more
-              noble. Neither knows that the rules of battle have just changed...or
-              that their time has run out. Wizard's First Rule is the beginning. One
-              book. One Rule. Witness the birth of a legend.
-        isbn:
-          description: The ISBN of the book. Will be null if unknown.
-          type: string
-          nullable: true
-        asin:
-          description: The ASIN of the book. Will be null if unknown.
-          type: string
-          nullable: true
-          example: B002V0QK4C
-        language:
-          description: The language of the book. Will be null if unknown.
-          type: string
-          nullable: true
-        explicit:
-          description: Whether the book has been marked as explicit.
-          type: boolean
-          example: false
-    bookMetadataMinified:
-      type: object
-      description: The minified metadata for a book in the database.
-      allOf:
-        - $ref : '#/components/schemas/bookMetadataBase'
-        - type: object
-          properties:
-            titleIgnorePrefix:
-              description: The title of the book with any prefix moved to the end.
-              type: string
-            authorName:
-              description: The name of the book's author(s).
-              type: string
-              example: Terry Goodkind
-            authorNameLF:
-              description: The name of the book's author(s) with last names first.
-              type: string
-              example: Goodkind, Terry
-            narratorName:
-              description: The name of the audiobook's narrator(s).
-              type: string
-              example: Sam Tsoutsouvas
-            seriesName:
-              description: The name of the book's series.
-              type: string
-              example: Sword of Truth
-    bookChapter:
-      type: object
-      description: A book chapter. Includes the title and timestamps.
-      properties:
-        id:
-          description: The ID of the book chapter.
-          type: integer
-          example: 0
-        start:
-          description: When in the book (in seconds) the chapter starts.
-          type: integer
-          example: 0
-        end:
-          description: When in the book (in seconds) the chapter ends.
-          type: number
-          example: 6004.6675
-        title:
-          description: The title of the chapter.
-          type: string
-          example: Wizards First Rule 01 Chapter 1
-    audioFile:
-      type: object
-      description: An audio file for a book. Includes audio metadata and track numbers.
-      properties:
-        index:
-          description: The index of the audio file.
-          type: integer
-          example: 1
-        ino:
-          $ref: '#/components/schemas/inode'
-        metadata:
-          $ref: '#/components/schemas/fileMetadata'
-        addedAt:
-          $ref: '#/components/schemas/addedAt'
-        updatedAt:
-          $ref: '#/components/schemas/updatedAt'
-        trackNumFromMeta:
-          description: The track number of the audio file as pulled from the file's metadata. Will be null if unknown.
-          type: integer
-          nullable: true
-          example: 1
-        discNumFromMeta:
-          description: The disc number of the audio file as pulled from the file's metadata. Will be null if unknown.
-          type: string
-          nullable: true
-        trackNumFromFilename:
-          description: The track number of the audio file as determined from the file's name. Will be null if unknown.
-          type: integer
-          nullable: true
-          example: 1
-        discNumFromFilename:
-          description: The disc number of the audio file as determined from the file's name. Will be null if unknown.
-          type: string
-          nullable: true
-        manuallyVerified:
-          description: Whether the audio file has been manually verified by a user.
-          type: boolean
-        invalid:
-          description: Whether the audio file is missing from the server.
-          type: boolean
-        exclude:
-          description: Whether the audio file has been marked for exclusion.
-          type: boolean
-        error:
-          description: Any error with the audio file. Will be null if there is none.
-          type: string
-          nullable: true
-        format:
-          description: The format of the audio file.
-          type: string
-          example: MP2/3 (MPEG audio layer 2/3)
-        duration:
-          $ref: '#/components/schemas/durationSec'
-        bitRate:
-          description: The bit rate (in bit/s) of the audio file.
-          type: integer
-          example: 64000
-        language:
-          description: The language of the audio file.
-          type: string
-          nullable: true
-        codec:
-          description: The codec of the audio file.
-          type: string
-          example: mp3
-        timeBase:
-          description: The time base of the audio file.
-          type: string
-          example: 1/14112000
-        channels:
-          description: The number of channels the audio file has.
-          type: integer
-          example: 2
-        channelLayout:
-          description: The layout of the audio file's channels.
-          type: string
-          example: stereo
-        chapters:
-          description: If the audio file is part of an audiobook, the chapters the file contains.
-          type: array
-          items:
-            $ref: '#/components/schemas/bookChapter'
-        embeddedCoverArt:
-          description: The type of embedded cover art in the audio file. Will be null if none exists.
-          type: string
-          nullable: true
-        metaTags:
-          $ref: '#/components/schemas/audioMetaTags'
-        mimeType:
-          description: The MIME type of the audio file.
-          type: string
-          example: audio/mpeg
-    ebookFile:
-      type: object
-      description: An ebook file for a library item.
-      nullable: true
-      properties:
-        ino:
-          $ref: '#/components/schemas/inode'
-        metadata:
-          $ref: '#/components/schemas/fileMetadata'
-        ebookFormat:
-          description: The ebook format of the ebook file.
-          type: string
-          example: epub
-        addedAt:
-          $ref: '#/components/schemas/addedAt'
-        updatedAt:
-          $ref: '#/components/schemas/updatedAt'
-    fileMetadata:
-      type: object
-      description: The metadata for a file, including the path, size, and unix timestamps of the file.
-      nullable: true
-      properties:
-        filename:
-          description: The filename of the file.
-          type: string
-          example: Wizards First Rule 01.mp3
-        ext:
-          description: The file extension of the file.
-          type: string
-          example: .mp3
-        path:
-          description: The absolute path on the server of the file.
-          type: string
-          example: >-
-              /audiobooks/Terry Goodkind/Sword of Truth/Wizards First Rule/Terry
-              Goodkind - SOT Bk01 - Wizards First Rule 01.mp3
-        relPath:
-          description: The path of the file, relative to the book's or podcast's folder.
-          type: string
-          example: Wizards First Rule 01.mp3
-        size:
-          $ref: '#/components/schemas/size'
-        mtimeMs:
-          description: The time (in ms since POSIX epoch) when the file was last modified on disk.
-          type: integer
-          example: 1632223180278
-        ctimeMs:
-          description: The time (in ms since POSIX epoch) when the file status was changed on disk.
-          type: integer
-          example: 1645978261001
-        birthtimeMs:
-          description: The time (in ms since POSIX epoch) when the file was created on disk. Will be 0 if unknown.
-          type: integer
-          example: 0
-    audioMetaTags:
-      description: ID3 metadata tags pulled from the audio file on import. Only non-null tags will be returned in requests.
-      type: object
-      properties:
-        tagAlbum:
-          type: string
-          nullable: true
-          example: SOT Bk01
-        tagArtist:
-          type: string
-          nullable: true
-          example: Terry Goodkind
-        tagGenre:
-          type: string
-          nullable: true
-          example: Audiobook Fantasy
-        tagTitle:
-          type: string
-          nullable: true
-          example: Wizards First Rule 01
-        tagSeries:
-          type: string
-          nullable: true
-        tagSeriesPart:
-          type: string
-          nullable: true
-        tagTrack:
-          type: string
-          nullable: true
-          example: 01/20
-        tagDisc:
-          type: string
-          nullable: true
-        tagSubtitle:
-          type: string
-          nullable: true
-        tagAlbumArtist:
-          type: string
-          nullable: true
-          example: Terry Goodkind
-        tagDate:
-          type: string
-          nullable: true
-        tagComposer:
-          type: string
-          nullable: true
-          example: Terry Goodkind
-        tagPublisher:
-          type: string
-          nullable: true
-        tagComment:
-          type: string
-          nullable: true
-        tagDescription:
-          type: string
-          nullable: true
-        tagEncoder:
-          type: string
-          nullable: true
-        tagEncodedBy:
-          type: string
-          nullable: true
-        tagIsbn:
-          type: string
-          nullable: true
-        tagLanguage:
-          type: string
-          nullable: true
-        tagASIN:
-          type: string
-          nullable: true
-        tagOverdriveMediaMarker:
-          type: string
-          nullable: true
-        tagOriginalYear:
-          type: string
-          nullable: true
-        tagReleaseCountry:
-          type: string
-          nullable: true
-        tagReleaseType:
-          type: string
-          nullable: true
-        tagReleaseStatus:
-          type: string
-          nullable: true
-        tagISRC:
-          type: string
-          nullable: true
-        tagMusicBrainzTrackId:
-          type: string
-          nullable: true
-        tagMusicBrainzAlbumId:
-          type: string
-          nullable: true
-        tagMusicBrainzAlbumArtistId:
-          type: string
-          nullable: true
-        tagMusicBrainzArtistId:
-          type: string
-          nullable: true
-    addedAt:
-      type: integer
-      description: The time (in ms since POSIX epoch) when added to the server.
-      example: 1633522963509
-    createdAt:
-      type: integer
-      description: The time (in ms since POSIX epoch) when was created.
-      example: 1633522963509
-    updatedAt:
-      type: integer
-      description: The time (in ms since POSIX epoch) when last updated.
-      example: 1633522963509
-    size:
-      description: The total size (in bytes) of the item or file.
-      type: integer
-      example: 268824228
-    durationSec:
-      description: The total length (in seconds) of the item or file.
-      type: number
-      example: 33854.905
-    tags:
-      description: Tags applied to items.
-      type: array
-      items:
-        type: string
-      example: ["To Be Read", "Genre: Nonfiction"]
-    inode:
-      description: The inode of the item in the file system.
-      type: string
-      format: "[0-9]*"
-      example: '649644248522215260'
-    seriesId:
-      type: string
-      description: The ID of the series.
-      format: uuid
-      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
-    seriesName:
-      description: The name of the series.
-      type: string
-      example: Sword of Truth
-    folderId:
-      type: string
-      description: The ID of the folder.
-      format: uuid
-      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
-    folder:
-      type: object
-      description: Folder used in library
-      properties:
-        id:
-          $ref: '#/components/schemas/folderId'
-        fullPath:
-          description: The path on the server for the folder. (Read Only)
-          type: string
-          example: /podcasts
-        libraryId:
-          $ref: '#/components/schemas/libraryId'
-        addedAt:
-          $ref: '#/components/schemas/addedAt'
-    authorUpdated:
-      description: Whether the author was updated without errors. Will not exist if author was merged.
-      type: boolean
-      nullable: true
-    authorId:
-      type: string
-      description: The ID of the author.
-      format: uuid
-      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
-    authorASIN:
-      type: string
-      description: The Audible identifier (ASIN) of the author. Will be null if unknown. Not the Amazon identifier.
-      nullable: true
-      example: B000APZOQA
-    authorName:
-      description: The name of the author.
-      type: string
-      example: Terry Goodkind
-    authorSeries:
-      type: object
-      description: Series and the included library items that an author has written.
-      properties:
-        id:
-          $ref: '#/components/schemas/seriesId'
-        name:
-          $ref: '#/components/schemas/seriesName'
-        items:
-          description: The items in the series. Each library item's media's metadata will have a `series` attribute, a `Series Sequence`, which is the matching series.
-          type: array
-          items:
-            $ref: '#/components/schemas/libraryItemMinified'
-    author:
-      description: An author object which includes a description and image path.
-      type: object
-      properties:
-        id:
-          $ref: '#/components/schemas/authorId'
-        asin:
-          $ref: '#/components/schemas/authorASIN'
-        name:
-          $ref: '#/components/schemas/authorName'
-        description:
-          description: A description of the author. Will be null if there is none.
-          type: string
-          nullable: true
-          example: |
-            Terry Goodkind is a #1 New York Times Bestselling Author and creator of the critically acclaimed masterwork,
-            ‘The Sword of Truth’. He has written 30+ major, bestselling novels, has been published in more than 20
-            languages world-wide, and has sold more than 26 Million books. ‘The Sword of Truth’ is a revered literary
-            tour de force, comprised of 17 volumes, borne from over 25 years of dedicated writing. Terry Goodkind's
-            brilliant books are character-driven stories, with a focus on the complexity of the human psyche. Goodkind
-            has an uncanny grasp for crafting compelling stories about people like you and me, trapped in terrifying
-            situations.
-        imagePath:
-          description: The absolute path for the author image located in the `metadata/` directory. Will be null if there is no image.
-          type: string
-          nullable: true
-          example: /metadata/authors/aut_bxxbyjiptmgb56yzoz.jpg
-        addedAt:
-          $ref: '#/components/schemas/addedAt'
-        updatedAt:
-          $ref: '#/components/schemas/updatedAt'
-    authorWithItems:
-      type: object
-      description: The author schema with an array of items they are associated with.
-      allOf:
-        - $ref: '#/components/schemas/author'
-        - type: object
-          properties:
-            libraryItems:
-              description: The items associated with the author
-              type: array
-              items:
-                $ref: '#/components/schemas/libraryItemMinified'
-    authorWithSeries:
-      type: object
-      description: The author schema with an array of items and series they are associated with.
-      allOf:
-        - $ref: '#/components/schemas/authorWithItems'
-        - type: object
-          properties:
-            series:
-              description: The series associated with the author
-              type: array
-              items:
-                $ref: '#/components/schemas/authorSeries'
-    authorMinified:
-      type: object
-      description: Minified author object which only contains the author name and ID.
-      properties:
-        id:
-          $ref: '#/components/schemas/authorId'
-        name:
-          $ref: '#/components/schemas/authorName'
-    authorExpanded:
-      type: object
-      description: The author schema with the total number of books in the library.
-      allOf:
-        - $ref: '#/components/schemas/author'
-        - type: object
-          properties:
-            numBooks:
-              description: The number of books associated with the author in the library.
-              type: integer
-              example: 1
-    oldLibraryId:
-      type: string
-      description: The ID of the libraries created on server version 2.2.23 and before.
-      format: "lib_[a-z0-9]{18}"
-      example: lib_o78uaoeuh78h6aoeif
-    libraryId:
-      type: string
-      description: The ID of the library.
-      format: uuid
-      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
-    oldLibraryItemId:
-      description: The ID of library items on server version 2.2.23 and before.
-      type: string
-      nullable: true
-      format: "li_[a-z0-9]{18}"
-      example: li_o78uaoeuh78h6aoeif
-    libraryItemId:
-      type: string
-      description: The ID of library items after 2.3.0.
-      format: uuid
-      example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
-    libraryItemBase:
-      type: object
-      description: Base library item schema
-      properties:
-        id:
-          $ref: '#/components/schemas/libraryItemId'
-        oldLibraryItemId:
-          $ref: '#/components/schemas/oldLibraryItemId'
-        ino:
-          $ref: '#/components/schemas/inode'
-        libraryId:
-          $ref: '#/components/schemas/libraryId'
-        folderId:
-          $ref: '#/components/schemas/folderId'
-        path:
-          description: The path of the library item on the server.
-          type: string
-        relPath:
-          description: The path, relative to the library folder, of the library item.
-          type: string
-        isFile:
-          description: Whether the library item is a single file in the root of the library folder.
-          type: boolean
-        mtimeMs:
-          description: The time (in ms since POSIX epoch) when the library item was last modified on disk.
-          type: integer
-        ctimeMs:
-          description: The time (in ms since POSIX epoch) when the library item status was changed on disk.
-          type: integer
-        birthtimeMs:
-          description: The time (in ms since POSIX epoch) when the library item was created on disk. Will be 0 if unknown.
-          type: integer
-        addedAt:
-          $ref: '#/components/schemas/addedAt'
-        updatedAt:
-          $ref: '#/components/schemas/updatedAt'
-        isMissing:
-          description: Whether the library item was scanned and no longer exists.
-          type: boolean
-        isInvalid:
-          description: Whether the library item was scanned and no longer has media files.
-          type: boolean
-        mediaType:
-          $ref: '#/components/schemas/mediaType'
-    libraryItemMinified:
-      type: object
-      description: A single item on the server, like a book or podcast. Minified media format.
-      allOf:
-        - $ref : '#/components/schemas/libraryItemBase'
-        - type: object
-          properties:
-            media:
-              $ref: '#/components/schemas/mediaMinified'
-  parameters:
-    authorID:
-      name: id
-      in: path
-      description: Author ID
-      required: true
-      schema:
-        $ref: '#/components/schemas/authorId'
-    authorInclude:
-      name: include
-      in: query
-      description: A comma separated list of what to include with the author. The options are `items` and `series`. `series` will only have an effect if `items` is included.
-      required: false
-      schema:
-        type: string
-        example: "items"
-      examples:
-        empty:
-          summary: Do not return library items
-          value: ""
-        itemOnly:
-          summary: Only return library items
-          value: "items"
-        itemsAndSeries:
-          summary: Return library items and series
-          value: "items,series"
-    authorLibraryId:
-      name: library
-      in: query
-      description: The ID of the library to to include filter included items from.
-      required: false
-      schema:
-        $ref: '#/components/schemas/libraryId'
-    asin:
-      name: asin
-      in: query
-      description: The Audible Identifier (ASIN).
-      required: false
-      schema:
-        $ref: '#/components/schemas/authorASIN'
-    authorSearchName:
-      name: q
-      in: query
-      description: The name of the author to use for searching.
-      required: false
-      schema:
-        type: string
-        example: Terry Goodkind
-    authorName:
-      name: name
-      in: query
-      description: The new name of the author.
-      required: false
-      schema:
-        $ref: '#/components/schemas/authorName'
-    authorDescription:
-      name: description
-      in: query
-      description: The new description of the author.
-      required: false
-      schema:
-        type: string
-        nullable: true
-        example: Terry Goodkind is a #1 New York Times Bestselling Author and creator of the critically acclaimed masterwork, ‘The Sword of Truth’. He has written 30+ major, bestselling novels, has been published in more than 20 languages world-wide, and has sold more than 26 Million books. ‘The Sword of Truth’ is a revered literary tour de force, comprised of 17 volumes, borne from over 25 years of dedicated writing.
-    authorImagePath:
-      name: imagePath
-      in: query
-      description: The new absolute path for the author image.
-      required: false
-      schema:
-        type: string
-        nullable: true
-        example: /metadata/authors/aut_z3leimgybl7uf3y4ab.jpg
-    imageURL:
-      name: url
-      in: query
-      description: The URL of the image to add to the server
-      required: true
-      schema:
-        type: string
-        format: uri
-        example: https://images-na.ssl-images-amazon.com/images/I/51NoQTm33OL.__01_SX120_CR0,0,120,120__.jpg
-    imageWidth:
-      name: width
-      in: query
-      description: The requested width of image in pixels.
-      schema:
-        type: integer
-        default: 400
-        example: 400
-      example: 400
-    imageHeight:
-      name: height
-      in: query
-      description: The requested height of image in pixels. If `null`, the height is scaled to maintain aspect ratio based on the requested width.
-      schema:
-        type: integer
-        nullable: true
-        default: null
-        example: 600
-      examples:
-        scaleHeight:
-          summary: Scale height with width
-          value: null
-        fixedHeight:
-          summary: Force height of image
-          value: 600
-    imageFormat:
-      name: format
-      in: query
-      description: The requested output format.
-      schema:
-        type: string
-        default: jpeg
-        example: webp
-    imageRaw:
-      name: raw
-      in: query
-      description: Return the raw image without scaling if true.
-      schema:
-        type: boolean
-        default: false
   responses: 
     ok200:
       description: OK
-    author404:
-      description: Author not found.
-      content:
-        text/html:
-          schema:
-            type: string
-            example: Not found
-  paths:
-    /api/authors/{id}:
-      get:
-        operationId: getAuthorByID
-        summary: Get a single author by ID on server
-        tags:
-          - Authors
-        parameters:
-          - $ref: '#/components/parameters/authorID'
-          - $ref: '#/components/parameters/authorInclude'
-          - $ref: '#/components/parameters/authorLibraryId'
-        responses:
-          200:
-            description: getAuthorByID OK
-            content:
-              application/json:
-                schema:
-                  oneOf:
-                    - $ref: '#/components/schemas/author'
-                    - $ref: '#/components/schemas/authorWithItems'
-                    - $ref: '#/components/schemas/authorWithSeries'
-          404:
-            $ref: '#/components/responses/author404'
-    /api/authors/{id}:
-      patch:
-        operationId: updateAuthorByID
-        summary: Update a single author by ID on server. This endpoint will merge two authors if the new author name matches another author in the database.
-        tags:
-          - Authors
-        parameters:
-          - $ref: '#/components/parameters/authorID'
-          - $ref: '#/components/parameters/asin'
-          - $ref: '#/components/parameters/authorName'
-          - $ref: '#/components/parameters/authorDescription'
-          - $ref: '#/components/parameters/authorImagePath'
-        responses:
-          200:
-            description: updateAuthorByID OK
-            content:
-              application/json:
-                schema:
-                  allOf:
-                    - $ref: '#/components/schemas/author'
-                    - $ref: '#/components/schemas/authorUpdated'
-                    - type: object
-                      properties:
-                        merged:
-                          description: Will only exist and be `true` if the author was merged with another author
-                          type: boolean
-                          nullable: true
-          404:
-            $ref: '#/components/responses/author404'
-    /api/authors/{id}:
-      delete:
-        operationId: deleteAuthorByID
-        summary: Delete a single author by ID on server and remove author from all books.
-        tags:
-          - Authors
-        parameters:
-          - $ref: '#/components/parameters/authorID'
-        responses:
-          200:
-            $ref: '#/components/responses/ok200'
-          404:
-            $ref: '#/components/responses/author404'
-    /api/authors/{id}/image:
-      post:
-        operationId: setAuthorImageByID
-        summary: Set an author image using a provided URL.
-        tags:
-          - Authors
-        parameters:
-          - $ref: '#/components/parameters/authorID'
-          - $ref: '#/components/parameters/imageURL'
-        responses:
-          200:
-            description: setAuthorImageByID OK
-            content:
-              application/json:
-                schema:
-                  oneOf:
-                    - $ref: '#/components/schemas/author'
-          404:
-            $ref: '#/components/responses/author404'
-    /api/authors/{id}/image:
-      delete:
-        operationId: deleteAuthorImageByID
-        summary: Delete an author image from the server and remove the image from the database.
-        tags:
-          - Authors
-        parameters:
-          - $ref: '#/components/parameters/authorID'
-        responses:
-          200:
-            $ref: '#/components/responses/ok200'
-          404:
-            $ref: '#/components/responses/author404'
-    /api/authors/{id}/match:
-      post:
-        operationId: matchAuthorByID
-        summary: Match the author against Audible using quick match. Quick match updates the author's description and image (if no image already existed) with information from audible. Either `asin` or `q` must be provided, with `asin` taking priority if both are provided.
-        tags:
-          - Authors
-        parameters:
-          - $ref: '#/components/parameters/authorID'
-          - $ref: '#/components/parameters/asin'
-          - $ref: '#/components/parameters/authorSearchName'
-        responses:
-          200:
-            description: matchAuthorByID OK
-            content:
-              application/json:
-                schema:
-                  allOf:
-                    - $ref: '#/components/schemas/author'
-                    - $ref: '#/components/schemas/authorUpdated'
-          404:
-            $ref: '#/components/responses/author404'
-    /api/authors/{id}/image:
-      patch:
-        operationId: getAuthorImageByID
-        summary: Return the author image by author ID.
-        tags:
-          - Authors
-        parameters:
-          - $ref: '#/components/parameters/authorID'
-          - $ref: '#/components/parameters/imageWidth'
-          - $ref: '#/components/parameters/imageHeight'
-          - $ref: '#/components/parameters/imageFormat'
-          - $ref: '#/components/parameters/imageRaw'
-        responses:
-          200:
-            description: getAuthorImageByID OK
-            content:
-              image/*:
-                schema:
-                  type: string
-                  format: binary
-          404:
-            $ref: '#/components/responses/author404'
+paths:
+  /api/authors/{id}:
+    get:
+      operationId: getAuthorByID
+      summary: Get a single author by ID on server
+      tags:
+        - Authors
+      parameters:
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorID'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorInclude'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorLibraryId'
+      responses:
+        200:
+          description: getAuthorByID OK
+          content:
+            application/json:
+              schema:
+                oneOf:
+                  - $ref: './objects/entities/Author.yaml#/components/schemas/author'
+                  - $ref: './objects/entities/Author.yaml#/components/schemas/authorWithItems'
+                  - $ref: './objects/entities/Author.yaml#/components/schemas/authorWithSeries'
+        404:
+          $ref: './controllers/AuthorController.yaml#/components/responses/author404'
+    patch:
+      operationId: updateAuthorByID
+      summary: Update a single author by ID on server. This endpoint will merge two authors if the new author name matches another author in the database.
+      tags:
+        - Authors
+      parameters:
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorID'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/asin'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorName'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorDescription'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorImagePath'
+      responses:
+        200:
+          description: updateAuthorByID OK
+          content:
+            application/json:
+              schema:
+                allOf:
+                  - $ref: './objects/entities/Author.yaml#/components/schemas/author'
+                  - $ref: './controllers/AuthorController.yaml#/components/schemas/authorUpdated'
+                  - type: object
+                    properties:
+                      merged:
+                        description: Will only exist and be `true` if the author was merged with another author
+                        type: boolean
+                        nullable: true
+        404:
+          $ref: './controllers/AuthorController.yaml#/components/responses/author404'
+    delete:
+      operationId: deleteAuthorByID
+      summary: Delete a single author by ID on server and remove author from all books.
+      tags:
+        - Authors
+      parameters:
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorID'
+      responses:
+        200:
+          $ref: '#/components/responses/ok200'
+        404:
+          $ref: './controllers/AuthorController.yaml#/components/responses/author404'
+  /api/authors/{id}/image:
+    post:
+      operationId: setAuthorImageByID
+      summary: Set an author image using a provided URL.
+      tags:
+        - Authors
+      parameters:
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorID'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/imageURL'
+      responses:
+        200:
+          description: setAuthorImageByID OK
+          content:
+            application/json:
+              schema:
+                oneOf:
+                  - $ref: './objects/entities/Author.yaml#/components/schemas/author'
+        404:
+          $ref: './controllers/AuthorController.yaml#/components/responses/author404'
+    delete:
+      operationId: deleteAuthorImageByID
+      summary: Delete an author image from the server and remove the image from the database.
+      tags:
+        - Authors
+      parameters:
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorID'
+      responses:
+        200:
+          $ref: '#/components/responses/ok200'
+        404:
+          $ref: './controllers/AuthorController.yaml#/components/responses/author404'
+    patch:
+      operationId: getAuthorImageByID
+      summary: Return the author image by author ID.
+      tags:
+        - Authors
+      parameters:
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorID'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/imageWidth'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/imageHeight'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/imageFormat'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/imageRaw'
+      responses:
+        200:
+          description: getAuthorImageByID OK
+          content:
+            image/*:
+              schema:
+                type: string
+                format: binary
+        404:
+          $ref: './controllers/AuthorController.yaml#/components/responses/author404'
+  /api/authors/{id}/match:
+    post:
+      operationId: matchAuthorByID
+      summary: Match the author against Audible using quick match. Quick match updates the author's description and image (if no image already existed) with information from audible. Either `asin` or `q` must be provided, with `asin` taking priority if both are provided.
+      tags:
+        - Authors
+      parameters:
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorID'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/asin'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorSearchName'
+      responses:
+        200:
+          description: matchAuthorByID OK
+          content:
+            application/json:
+              schema:
+                allOf:
+                  - $ref: './objects/entities/Author.yaml#/components/schemas/author'
+                  - $ref: './controllers/AuthorController.yaml#/components/schemas/authorUpdated'
+        404:
+          $ref: './controllers/AuthorController.yaml#/components/responses/author404'
 tags:
   - name: Authors
     description: Author endpoints

From 7b856474afcf33cae2c0c9adccd1fa3fd1026593 Mon Sep 17 00:00:00 2001
From: Nicholas Wallace <nicholaslwallace@gmail.com>
Date: Sun, 31 Mar 2024 22:48:58 +0000
Subject: [PATCH 3/7] Rename base document

---
 docs/{spec.yaml => root.yaml} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename docs/{spec.yaml => root.yaml} (100%)

diff --git a/docs/spec.yaml b/docs/root.yaml
similarity index 100%
rename from docs/spec.yaml
rename to docs/root.yaml

From 74dd24febf216edd9a9eda2f7778d943d39fbaf5 Mon Sep 17 00:00:00 2001
From: Nicholas Wallace <nicholaslwallace@gmail.com>
Date: Mon, 1 Apr 2024 00:26:55 +0000
Subject: [PATCH 4/7] Bundled spec

---
 docs/openapi.json | 856 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 856 insertions(+)
 create mode 100644 docs/openapi.json

diff --git a/docs/openapi.json b/docs/openapi.json
new file mode 100644
index 0000000000..8b47584b5e
--- /dev/null
+++ b/docs/openapi.json
@@ -0,0 +1,856 @@
+{
+  "openapi": "3.0.0",
+  "info": {
+    "title": "Audiobookshelf API",
+    "version": "0.1.0",
+    "description": "Audiobookshelf API with autogenerated OpenAPI doc"
+  },
+  "servers": [
+    {
+      "url": "http://localhost:3000",
+      "description": "Development server"
+    }
+  ],
+  "components": {
+    "responses": {
+      "ok200": {
+        "description": "OK"
+      }
+    }
+  },
+  "paths": {
+    "/api/authors/{id}": {
+      "get": {
+        "operationId": "getAuthorByID",
+        "summary": "Get a single author by ID on server",
+        "tags": [
+          "Authors"
+        ],
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "description": "Author ID",
+            "required": true,
+            "schema": {
+              "type": "string",
+              "description": "The ID of the author.",
+              "format": "uuid",
+              "example": "e4bb1afb-4a4f-4dd6-8be0-e615d233185b"
+            }
+          },
+          {
+            "name": "include",
+            "in": "query",
+            "description": "A comma separated list of what to include with the author. The options are `items` and `series`. `series` will only have an effect if `items` is included.",
+            "required": false,
+            "schema": {
+              "type": "string",
+              "example": "items"
+            },
+            "examples": {
+              "empty": {
+                "summary": "Do not return library items",
+                "value": ""
+              },
+              "itemOnly": {
+                "summary": "Only return library items",
+                "value": "items"
+              },
+              "itemsAndSeries": {
+                "summary": "Return library items and series",
+                "value": "items,series"
+              }
+            }
+          },
+          {
+            "name": "library",
+            "in": "query",
+            "description": "The ID of the library to to include filter included items from.",
+            "required": false,
+            "schema": {
+              "type": "string",
+              "description": "The ID of the library.",
+              "format": "uuid",
+              "example": "e4bb1afb-4a4f-4dd6-8be0-e615d233185b"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "getAuthorByID OK",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "oneOf": [
+                    {
+                      "description": "An author object which includes a description and image path.",
+                      "type": "object",
+                      "properties": {
+                        "id": {
+                          "type": "string",
+                          "description": "The ID of the author.",
+                          "format": "uuid",
+                          "example": "e4bb1afb-4a4f-4dd6-8be0-e615d233185b"
+                        },
+                        "asin": {
+                          "type": "string",
+                          "description": "The Audible identifier (ASIN) of the author. Will be null if unknown. Not the Amazon identifier.",
+                          "nullable": true,
+                          "example": "B000APZOQA"
+                        },
+                        "name": {
+                          "description": "The name of the author.",
+                          "type": "string",
+                          "example": "Terry Goodkind"
+                        },
+                        "description": {
+                          "description": "A description of the author. Will be null if there is none.",
+                          "type": "string",
+                          "nullable": true,
+                          "example": "Terry Goodkind is a #1 New York Times Bestselling Author and creator of the critically acclaimed masterwork,\n‘The Sword of Truth’. He has written 30+ major, bestselling novels, has been published in more than 20\nlanguages world-wide, and has sold more than 26 Million books. ‘The Sword of Truth’ is a revered literary\ntour de force, comprised of 17 volumes, borne from over 25 years of dedicated writing. Terry Goodkind's\nbrilliant books are character-driven stories, with a focus on the complexity of the human psyche. Goodkind\nhas an uncanny grasp for crafting compelling stories about people like you and me, trapped in terrifying\nsituations.\n"
+                        },
+                        "imagePath": {
+                          "description": "The absolute path for the author image located in the `metadata/` directory. Will be null if there is no image.",
+                          "type": "string",
+                          "nullable": true,
+                          "example": "/metadata/authors/aut_bxxbyjiptmgb56yzoz.jpg"
+                        },
+                        "addedAt": {
+                          "type": "integer",
+                          "description": "The time (in ms since POSIX epoch) when added to the server.",
+                          "example": 1633522963509
+                        },
+                        "updatedAt": {
+                          "type": "integer",
+                          "description": "The time (in ms since POSIX epoch) when last updated.",
+                          "example": 1633522963509
+                        }
+                      }
+                    },
+                    {
+                      "type": "object",
+                      "description": "The author schema with an array of items they are associated with.",
+                      "allOf": [
+                        {
+                          "description": "An author object which includes a description and image path.",
+                          "type": "object",
+                          "properties": {
+                            "id": {
+                              "type": "string",
+                              "description": "The ID of the author.",
+                              "format": "uuid",
+                              "example": "e4bb1afb-4a4f-4dd6-8be0-e615d233185b"
+                            },
+                            "asin": {
+                              "type": "string",
+                              "description": "The Audible identifier (ASIN) of the author. Will be null if unknown. Not the Amazon identifier.",
+                              "nullable": true,
+                              "example": "B000APZOQA"
+                            },
+                            "name": {
+                              "description": "The name of the author.",
+                              "type": "string",
+                              "example": "Terry Goodkind"
+                            },
+                            "description": {
+                              "description": "A description of the author. Will be null if there is none.",
+                              "type": "string",
+                              "nullable": true,
+                              "example": "Terry Goodkind is a #1 New York Times Bestselling Author and creator of the critically acclaimed masterwork,\n‘The Sword of Truth’. He has written 30+ major, bestselling novels, has been published in more than 20\nlanguages world-wide, and has sold more than 26 Million books. ‘The Sword of Truth’ is a revered literary\ntour de force, comprised of 17 volumes, borne from over 25 years of dedicated writing. Terry Goodkind's\nbrilliant books are character-driven stories, with a focus on the complexity of the human psyche. Goodkind\nhas an uncanny grasp for crafting compelling stories about people like you and me, trapped in terrifying\nsituations.\n"
+                            },
+                            "imagePath": {
+                              "description": "The absolute path for the author image located in the `metadata/` directory. Will be null if there is no image.",
+                              "type": "string",
+                              "nullable": true,
+                              "example": "/metadata/authors/aut_bxxbyjiptmgb56yzoz.jpg"
+                            },
+                            "addedAt": {
+                              "type": "integer",
+                              "description": "The time (in ms since POSIX epoch) when added to the server.",
+                              "example": 1633522963509
+                            },
+                            "updatedAt": {
+                              "type": "integer",
+                              "description": "The time (in ms since POSIX epoch) when last updated.",
+                              "example": 1633522963509
+                            }
+                          }
+                        },
+                        {
+                          "type": "object",
+                          "properties": {
+                            "libraryItems": {
+                              "description": "The items associated with the author",
+                              "type": "string"
+                            }
+                          }
+                        }
+                      ]
+                    },
+                    {
+                      "type": "object",
+                      "description": "The author schema with an array of items and series they are associated with.",
+                      "allOf": [
+                        {
+                          "type": "object",
+                          "description": "The author schema with an array of items they are associated with.",
+                          "allOf": [
+                            {
+                              "description": "An author object which includes a description and image path.",
+                              "type": "object",
+                              "properties": {
+                                "id": {
+                                  "type": "string",
+                                  "description": "The ID of the author.",
+                                  "format": "uuid",
+                                  "example": "e4bb1afb-4a4f-4dd6-8be0-e615d233185b"
+                                },
+                                "asin": {
+                                  "type": "string",
+                                  "description": "The Audible identifier (ASIN) of the author. Will be null if unknown. Not the Amazon identifier.",
+                                  "nullable": true,
+                                  "example": "B000APZOQA"
+                                },
+                                "name": {
+                                  "description": "The name of the author.",
+                                  "type": "string",
+                                  "example": "Terry Goodkind"
+                                },
+                                "description": {
+                                  "description": "A description of the author. Will be null if there is none.",
+                                  "type": "string",
+                                  "nullable": true,
+                                  "example": "Terry Goodkind is a #1 New York Times Bestselling Author and creator of the critically acclaimed masterwork,\n‘The Sword of Truth’. He has written 30+ major, bestselling novels, has been published in more than 20\nlanguages world-wide, and has sold more than 26 Million books. ‘The Sword of Truth’ is a revered literary\ntour de force, comprised of 17 volumes, borne from over 25 years of dedicated writing. Terry Goodkind's\nbrilliant books are character-driven stories, with a focus on the complexity of the human psyche. Goodkind\nhas an uncanny grasp for crafting compelling stories about people like you and me, trapped in terrifying\nsituations.\n"
+                                },
+                                "imagePath": {
+                                  "description": "The absolute path for the author image located in the `metadata/` directory. Will be null if there is no image.",
+                                  "type": "string",
+                                  "nullable": true,
+                                  "example": "/metadata/authors/aut_bxxbyjiptmgb56yzoz.jpg"
+                                },
+                                "addedAt": {
+                                  "type": "integer",
+                                  "description": "The time (in ms since POSIX epoch) when added to the server.",
+                                  "example": 1633522963509
+                                },
+                                "updatedAt": {
+                                  "type": "integer",
+                                  "description": "The time (in ms since POSIX epoch) when last updated.",
+                                  "example": 1633522963509
+                                }
+                              }
+                            },
+                            {
+                              "type": "object",
+                              "properties": {
+                                "libraryItems": {
+                                  "description": "The items associated with the author",
+                                  "type": "string"
+                                }
+                              }
+                            }
+                          ]
+                        },
+                        {
+                          "type": "object",
+                          "properties": {
+                            "series": {
+                              "description": "The series associated with the author",
+                              "type": "array",
+                              "items": {
+                                "type": "object",
+                                "description": "Series and the included library items that an author has written.",
+                                "properties": {
+                                  "id": {
+                                    "type": "string",
+                                    "description": "The ID of the series.",
+                                    "format": "uuid",
+                                    "example": "e4bb1afb-4a4f-4dd6-8be0-e615d233185b"
+                                  },
+                                  "name": {
+                                    "description": "The name of the series.",
+                                    "type": "string",
+                                    "example": "Sword of Truth"
+                                  },
+                                  "items": {
+                                    "description": "The items in the series. Each library item's media's metadata will have a `series` attribute, a `Series Sequence`, which is the matching series.",
+                                    "type": "array",
+                                    "items": {}
+                                  }
+                                }
+                              }
+                            }
+                          }
+                        }
+                      ]
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "404": {
+            "description": "Author not found.",
+            "content": {
+              "text/html": {
+                "schema": {
+                  "type": "string",
+                  "example": "Not found"
+                }
+              }
+            }
+          }
+        }
+      },
+      "patch": {
+        "operationId": "updateAuthorByID",
+        "summary": "Update a single author by ID on server. This endpoint will merge two authors if the new author name matches another author in the database.",
+        "tags": [
+          "Authors"
+        ],
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "description": "Author ID",
+            "required": true,
+            "schema": {
+              "type": "string",
+              "description": "The ID of the author.",
+              "format": "uuid",
+              "example": "e4bb1afb-4a4f-4dd6-8be0-e615d233185b"
+            }
+          },
+          {
+            "name": "asin",
+            "in": "query",
+            "description": "The Audible Identifier (ASIN).",
+            "required": false,
+            "schema": {
+              "type": "string",
+              "description": "The Audible identifier (ASIN) of the author. Will be null if unknown. Not the Amazon identifier.",
+              "nullable": true,
+              "example": "B000APZOQA"
+            }
+          },
+          {
+            "name": "name",
+            "in": "query",
+            "description": "The new name of the author.",
+            "required": false,
+            "schema": {
+              "description": "The name of the author.",
+              "type": "string",
+              "example": "Terry Goodkind"
+            }
+          },
+          {
+            "name": "description",
+            "in": "query",
+            "description": "The new description of the author.",
+            "required": false,
+            "schema": {
+              "type": "string",
+              "nullable": true,
+              "example": "Terry Goodkind is a"
+            }
+          },
+          {
+            "name": "imagePath",
+            "in": "query",
+            "description": "The new absolute path for the author image.",
+            "required": false,
+            "schema": {
+              "type": "string",
+              "nullable": true,
+              "example": "/metadata/authors/aut_z3leimgybl7uf3y4ab.jpg"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "updateAuthorByID OK",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "allOf": [
+                    {
+                      "description": "An author object which includes a description and image path.",
+                      "type": "object",
+                      "properties": {
+                        "id": {
+                          "type": "string",
+                          "description": "The ID of the author.",
+                          "format": "uuid",
+                          "example": "e4bb1afb-4a4f-4dd6-8be0-e615d233185b"
+                        },
+                        "asin": {
+                          "type": "string",
+                          "description": "The Audible identifier (ASIN) of the author. Will be null if unknown. Not the Amazon identifier.",
+                          "nullable": true,
+                          "example": "B000APZOQA"
+                        },
+                        "name": {
+                          "description": "The name of the author.",
+                          "type": "string",
+                          "example": "Terry Goodkind"
+                        },
+                        "description": {
+                          "description": "A description of the author. Will be null if there is none.",
+                          "type": "string",
+                          "nullable": true,
+                          "example": "Terry Goodkind is a #1 New York Times Bestselling Author and creator of the critically acclaimed masterwork,\n‘The Sword of Truth’. He has written 30+ major, bestselling novels, has been published in more than 20\nlanguages world-wide, and has sold more than 26 Million books. ‘The Sword of Truth’ is a revered literary\ntour de force, comprised of 17 volumes, borne from over 25 years of dedicated writing. Terry Goodkind's\nbrilliant books are character-driven stories, with a focus on the complexity of the human psyche. Goodkind\nhas an uncanny grasp for crafting compelling stories about people like you and me, trapped in terrifying\nsituations.\n"
+                        },
+                        "imagePath": {
+                          "description": "The absolute path for the author image located in the `metadata/` directory. Will be null if there is no image.",
+                          "type": "string",
+                          "nullable": true,
+                          "example": "/metadata/authors/aut_bxxbyjiptmgb56yzoz.jpg"
+                        },
+                        "addedAt": {
+                          "type": "integer",
+                          "description": "The time (in ms since POSIX epoch) when added to the server.",
+                          "example": 1633522963509
+                        },
+                        "updatedAt": {
+                          "type": "integer",
+                          "description": "The time (in ms since POSIX epoch) when last updated.",
+                          "example": 1633522963509
+                        }
+                      }
+                    },
+                    {
+                      "description": "Whether the author was updated without errors. Will not exist if author was merged.",
+                      "type": "boolean",
+                      "nullable": true
+                    },
+                    {
+                      "type": "object",
+                      "properties": {
+                        "merged": {
+                          "description": "Will only exist and be `true` if the author was merged with another author",
+                          "type": "boolean",
+                          "nullable": true
+                        }
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "404": {
+            "description": "Author not found.",
+            "content": {
+              "text/html": {
+                "schema": {
+                  "type": "string",
+                  "example": "Not found"
+                }
+              }
+            }
+          }
+        }
+      },
+      "delete": {
+        "operationId": "deleteAuthorByID",
+        "summary": "Delete a single author by ID on server and remove author from all books.",
+        "tags": [
+          "Authors"
+        ],
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "description": "Author ID",
+            "required": true,
+            "schema": {
+              "type": "string",
+              "description": "The ID of the author.",
+              "format": "uuid",
+              "example": "e4bb1afb-4a4f-4dd6-8be0-e615d233185b"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/components/responses/ok200"
+          },
+          "404": {
+            "description": "Author not found.",
+            "content": {
+              "text/html": {
+                "schema": {
+                  "type": "string",
+                  "example": "Not found"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/api/authors/{id}/image": {
+      "post": {
+        "operationId": "setAuthorImageByID",
+        "summary": "Set an author image using a provided URL.",
+        "tags": [
+          "Authors"
+        ],
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "description": "Author ID",
+            "required": true,
+            "schema": {
+              "type": "string",
+              "description": "The ID of the author.",
+              "format": "uuid",
+              "example": "e4bb1afb-4a4f-4dd6-8be0-e615d233185b"
+            }
+          },
+          {
+            "name": "url",
+            "in": "query",
+            "description": "The URL of the image to add to the server",
+            "required": true,
+            "schema": {
+              "type": "string",
+              "format": "uri",
+              "example": "https://images-na.ssl-images-amazon.com/images/I/51NoQTm33OL.__01_SX120_CR0,0,120,120__.jpg"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "setAuthorImageByID OK",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "oneOf": [
+                    {
+                      "description": "An author object which includes a description and image path.",
+                      "type": "object",
+                      "properties": {
+                        "id": {
+                          "type": "string",
+                          "description": "The ID of the author.",
+                          "format": "uuid",
+                          "example": "e4bb1afb-4a4f-4dd6-8be0-e615d233185b"
+                        },
+                        "asin": {
+                          "type": "string",
+                          "description": "The Audible identifier (ASIN) of the author. Will be null if unknown. Not the Amazon identifier.",
+                          "nullable": true,
+                          "example": "B000APZOQA"
+                        },
+                        "name": {
+                          "description": "The name of the author.",
+                          "type": "string",
+                          "example": "Terry Goodkind"
+                        },
+                        "description": {
+                          "description": "A description of the author. Will be null if there is none.",
+                          "type": "string",
+                          "nullable": true,
+                          "example": "Terry Goodkind is a #1 New York Times Bestselling Author and creator of the critically acclaimed masterwork,\n‘The Sword of Truth’. He has written 30+ major, bestselling novels, has been published in more than 20\nlanguages world-wide, and has sold more than 26 Million books. ‘The Sword of Truth’ is a revered literary\ntour de force, comprised of 17 volumes, borne from over 25 years of dedicated writing. Terry Goodkind's\nbrilliant books are character-driven stories, with a focus on the complexity of the human psyche. Goodkind\nhas an uncanny grasp for crafting compelling stories about people like you and me, trapped in terrifying\nsituations.\n"
+                        },
+                        "imagePath": {
+                          "description": "The absolute path for the author image located in the `metadata/` directory. Will be null if there is no image.",
+                          "type": "string",
+                          "nullable": true,
+                          "example": "/metadata/authors/aut_bxxbyjiptmgb56yzoz.jpg"
+                        },
+                        "addedAt": {
+                          "type": "integer",
+                          "description": "The time (in ms since POSIX epoch) when added to the server.",
+                          "example": 1633522963509
+                        },
+                        "updatedAt": {
+                          "type": "integer",
+                          "description": "The time (in ms since POSIX epoch) when last updated.",
+                          "example": 1633522963509
+                        }
+                      }
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "404": {
+            "description": "Author not found.",
+            "content": {
+              "text/html": {
+                "schema": {
+                  "type": "string",
+                  "example": "Not found"
+                }
+              }
+            }
+          }
+        }
+      },
+      "delete": {
+        "operationId": "deleteAuthorImageByID",
+        "summary": "Delete an author image from the server and remove the image from the database.",
+        "tags": [
+          "Authors"
+        ],
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "description": "Author ID",
+            "required": true,
+            "schema": {
+              "type": "string",
+              "description": "The ID of the author.",
+              "format": "uuid",
+              "example": "e4bb1afb-4a4f-4dd6-8be0-e615d233185b"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "$ref": "#/components/responses/ok200"
+          },
+          "404": {
+            "description": "Author not found.",
+            "content": {
+              "text/html": {
+                "schema": {
+                  "type": "string",
+                  "example": "Not found"
+                }
+              }
+            }
+          }
+        }
+      },
+      "patch": {
+        "operationId": "getAuthorImageByID",
+        "summary": "Return the author image by author ID.",
+        "tags": [
+          "Authors"
+        ],
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "description": "Author ID",
+            "required": true,
+            "schema": {
+              "type": "string",
+              "description": "The ID of the author.",
+              "format": "uuid",
+              "example": "e4bb1afb-4a4f-4dd6-8be0-e615d233185b"
+            }
+          },
+          {
+            "name": "width",
+            "in": "query",
+            "description": "The requested width of image in pixels.",
+            "schema": {
+              "type": "integer",
+              "default": 400,
+              "example": 400
+            },
+            "example": 400
+          },
+          {
+            "name": "height",
+            "in": "query",
+            "description": "The requested height of image in pixels. If `null`, the height is scaled to maintain aspect ratio based on the requested width.",
+            "schema": {
+              "type": "integer",
+              "nullable": true,
+              "default": null,
+              "example": 600
+            },
+            "examples": {
+              "scaleHeight": {
+                "summary": "Scale height with width",
+                "value": null
+              },
+              "fixedHeight": {
+                "summary": "Force height of image",
+                "value": 600
+              }
+            }
+          },
+          {
+            "name": "format",
+            "in": "query",
+            "description": "The requested output format.",
+            "schema": {
+              "type": "string",
+              "default": "jpeg",
+              "example": "webp"
+            }
+          },
+          {
+            "name": "raw",
+            "in": "query",
+            "description": "Return the raw image without scaling if true.",
+            "schema": {
+              "type": "boolean",
+              "default": false
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "getAuthorImageByID OK",
+            "content": {
+              "image/*": {
+                "schema": {
+                  "type": "string",
+                  "format": "binary"
+                }
+              }
+            }
+          },
+          "404": {
+            "description": "Author not found.",
+            "content": {
+              "text/html": {
+                "schema": {
+                  "type": "string",
+                  "example": "Not found"
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/api/authors/{id}/match": {
+      "post": {
+        "operationId": "matchAuthorByID",
+        "summary": "Match the author against Audible using quick match. Quick match updates the author's description and image (if no image already existed) with information from audible. Either `asin` or `q` must be provided, with `asin` taking priority if both are provided.",
+        "tags": [
+          "Authors"
+        ],
+        "parameters": [
+          {
+            "name": "id",
+            "in": "path",
+            "description": "Author ID",
+            "required": true,
+            "schema": {
+              "type": "string",
+              "description": "The ID of the author.",
+              "format": "uuid",
+              "example": "e4bb1afb-4a4f-4dd6-8be0-e615d233185b"
+            }
+          },
+          {
+            "name": "asin",
+            "in": "query",
+            "description": "The Audible Identifier (ASIN).",
+            "required": false,
+            "schema": {
+              "type": "string",
+              "description": "The Audible identifier (ASIN) of the author. Will be null if unknown. Not the Amazon identifier.",
+              "nullable": true,
+              "example": "B000APZOQA"
+            }
+          },
+          {
+            "name": "q",
+            "in": "query",
+            "description": "The name of the author to use for searching.",
+            "required": false,
+            "schema": {
+              "type": "string",
+              "example": "Terry Goodkind"
+            }
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "matchAuthorByID OK",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "allOf": [
+                    {
+                      "description": "An author object which includes a description and image path.",
+                      "type": "object",
+                      "properties": {
+                        "id": {
+                          "type": "string",
+                          "description": "The ID of the author.",
+                          "format": "uuid",
+                          "example": "e4bb1afb-4a4f-4dd6-8be0-e615d233185b"
+                        },
+                        "asin": {
+                          "type": "string",
+                          "description": "The Audible identifier (ASIN) of the author. Will be null if unknown. Not the Amazon identifier.",
+                          "nullable": true,
+                          "example": "B000APZOQA"
+                        },
+                        "name": {
+                          "description": "The name of the author.",
+                          "type": "string",
+                          "example": "Terry Goodkind"
+                        },
+                        "description": {
+                          "description": "A description of the author. Will be null if there is none.",
+                          "type": "string",
+                          "nullable": true,
+                          "example": "Terry Goodkind is a #1 New York Times Bestselling Author and creator of the critically acclaimed masterwork,\n‘The Sword of Truth’. He has written 30+ major, bestselling novels, has been published in more than 20\nlanguages world-wide, and has sold more than 26 Million books. ‘The Sword of Truth’ is a revered literary\ntour de force, comprised of 17 volumes, borne from over 25 years of dedicated writing. Terry Goodkind's\nbrilliant books are character-driven stories, with a focus on the complexity of the human psyche. Goodkind\nhas an uncanny grasp for crafting compelling stories about people like you and me, trapped in terrifying\nsituations.\n"
+                        },
+                        "imagePath": {
+                          "description": "The absolute path for the author image located in the `metadata/` directory. Will be null if there is no image.",
+                          "type": "string",
+                          "nullable": true,
+                          "example": "/metadata/authors/aut_bxxbyjiptmgb56yzoz.jpg"
+                        },
+                        "addedAt": {
+                          "type": "integer",
+                          "description": "The time (in ms since POSIX epoch) when added to the server.",
+                          "example": 1633522963509
+                        },
+                        "updatedAt": {
+                          "type": "integer",
+                          "description": "The time (in ms since POSIX epoch) when last updated.",
+                          "example": 1633522963509
+                        }
+                      }
+                    },
+                    {
+                      "description": "Whether the author was updated without errors. Will not exist if author was merged.",
+                      "type": "boolean",
+                      "nullable": true
+                    }
+                  ]
+                }
+              }
+            }
+          },
+          "404": {
+            "description": "Author not found.",
+            "content": {
+              "text/html": {
+                "schema": {
+                  "type": "string",
+                  "example": "Not found"
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  },
+  "tags": [
+    {
+      "name": "Authors",
+      "description": "Author endpoints"
+    }
+  ]
+}

From ca7eaf97502676f0933925ad9450396f0c18161f Mon Sep 17 00:00:00 2001
From: Nicholas Wallace <nicholaslwallace@gmail.com>
Date: Mon, 1 Apr 2024 00:44:51 +0000
Subject: [PATCH 5/7] OpenAPI spec readme

---
 docs/README.md | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)
 create mode 100644 docs/README.md

diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000000..c6f278edf7
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,30 @@
+# OpenAPI specification
+
+This directory includes the OpenAPI spec for the ABS server.
+The spec is made up of a number of individual `yaml` files located here and in the subfolders, with `root.yaml` being the file that references all of the others.
+The files are organized to have the same hierarchy as the server source files.
+The full spec is bundled into one file in `openapi.json`.
+
+The spec is linted and bundled by the [`vacuum` tool](https://quobix.com/vacuum/).
+The spec can also be tested with a real server using the [`wiretap` tool](https://pb33f.io/wiretap/).
+Both of these tools are created by [pb33f](https://pb33f.io/).
+
+### Bundling the spec
+The command to bundle the spec into a `yaml` file is `vacuum bundle root.yaml openapi.yaml`.
+
+The current version of `vacuum` cannot convert input `yaml` files to `json` files.
+To convert the spec to `json`, you can use the `yq` tool or another tool.
+
+The command to convert the spec using `yq` is `yq -p yaml -o json openapi.yaml > openapi.json`.
+
+### Viewing report
+To generate an HTML report, you can use `vacuum html-report [file]` to generate `report.html` and view the report in your browser.
+
+### Putting it all together
+The full command that I run to bundle the spec and generate the report is:
+
+```
+vacuum bundle root.yaml openapi.yaml && \
+yq -p yaml -o json openapi.yaml > openapi.json && \
+vacuum html-report openapi.json
+```

From 8e6ead59ceea4653b562fa108242e494e568bf6d Mon Sep 17 00:00:00 2001
From: advplyr <advplyr@protonmail.com>
Date: Sat, 20 Apr 2024 14:55:57 -0500
Subject: [PATCH 6/7] Update yaml keys to camelCase

---
 docs/controllers/AuthorController.yaml |   6 ++--
 docs/objects/entities/Author.yaml      |   4 +--
 docs/openapi.json                      | Bin 36876 -> 75466 bytes
 docs/root.yaml                         |  40 ++++++++++++-------------
 4 files changed, 25 insertions(+), 25 deletions(-)

diff --git a/docs/controllers/AuthorController.yaml b/docs/controllers/AuthorController.yaml
index 4e576af786..2af95a62f8 100644
--- a/docs/controllers/AuthorController.yaml
+++ b/docs/controllers/AuthorController.yaml
@@ -5,7 +5,7 @@ components:
       type: boolean
       nullable: true
   parameters:
-    authorID:
+    authorId:
       name: id
       in: path
       description: Author ID
@@ -43,7 +43,7 @@ components:
       description: The Audible Identifier (ASIN).
       required: false
       schema:
-        $ref: '../objects/entities/Author.yaml#/components/schemas/authorASIN'
+        $ref: '../objects/entities/Author.yaml#/components/schemas/authorAsin'
     authorSearchName:
       name: q
       in: query
@@ -77,7 +77,7 @@ components:
         type: string
         nullable: true
         example: /metadata/authors/aut_z3leimgybl7uf3y4ab.jpg
-    imageURL:
+    imageUrl:
       name: url
       in: query
       description: The URL of the image to add to the server
diff --git a/docs/objects/entities/Author.yaml b/docs/objects/entities/Author.yaml
index d2b173fc54..a38344d38b 100644
--- a/docs/objects/entities/Author.yaml
+++ b/docs/objects/entities/Author.yaml
@@ -5,7 +5,7 @@ components:
       description: The ID of the author.
       format: uuid
       example: e4bb1afb-4a4f-4dd6-8be0-e615d233185b
-    authorASIN:
+    authorAsin:
       type: string
       description: The Audible identifier (ASIN) of the author. Will be null if unknown. Not the Amazon identifier.
       nullable: true
@@ -34,7 +34,7 @@ components:
         id:
           $ref: '#/components/schemas/authorId'
         asin:
-          $ref: '#/components/schemas/authorASIN'
+          $ref: '#/components/schemas/authorAsin'
         name:
           $ref: '#/components/schemas/authorName'
         description:
diff --git a/docs/openapi.json b/docs/openapi.json
index 8b47584b5ec035c5e439d2dea2dc2a952bbbfe6f..7d20981711adc3865ec70635df66387820aeea4d 100644
GIT binary patch
literal 75466
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
La!>n8+xq<fH&;p1

literal 36876
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
ZOiQ1EO^EV&x#wgm{o0iK#Yew=^nWM>6Q%$F

diff --git a/docs/root.yaml b/docs/root.yaml
index fdab0125ae..b1e01d8f56 100644
--- a/docs/root.yaml
+++ b/docs/root.yaml
@@ -13,17 +13,17 @@ components:
 paths:
   /api/authors/{id}:
     get:
-      operationId: getAuthorByID
+      operationId: getAuthorById
       summary: Get a single author by ID on server
       tags:
         - Authors
       parameters:
-        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorID'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorId'
         - $ref: './controllers/AuthorController.yaml#/components/parameters/authorInclude'
         - $ref: './controllers/AuthorController.yaml#/components/parameters/authorLibraryId'
       responses:
         200:
-          description: getAuthorByID OK
+          description: getAuthorById OK
           content:
             application/json:
               schema:
@@ -34,19 +34,19 @@ paths:
         404:
           $ref: './controllers/AuthorController.yaml#/components/responses/author404'
     patch:
-      operationId: updateAuthorByID
+      operationId: updateAuthorById
       summary: Update a single author by ID on server. This endpoint will merge two authors if the new author name matches another author in the database.
       tags:
         - Authors
       parameters:
-        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorID'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorId'
         - $ref: './controllers/AuthorController.yaml#/components/parameters/asin'
         - $ref: './controllers/AuthorController.yaml#/components/parameters/authorName'
         - $ref: './controllers/AuthorController.yaml#/components/parameters/authorDescription'
         - $ref: './controllers/AuthorController.yaml#/components/parameters/authorImagePath'
       responses:
         200:
-          description: updateAuthorByID OK
+          description: updateAuthorById OK
           content:
             application/json:
               schema:
@@ -62,12 +62,12 @@ paths:
         404:
           $ref: './controllers/AuthorController.yaml#/components/responses/author404'
     delete:
-      operationId: deleteAuthorByID
+      operationId: deleteAuthorById
       summary: Delete a single author by ID on server and remove author from all books.
       tags:
         - Authors
       parameters:
-        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorID'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorId'
       responses:
         200:
           $ref: '#/components/responses/ok200'
@@ -75,16 +75,16 @@ paths:
           $ref: './controllers/AuthorController.yaml#/components/responses/author404'
   /api/authors/{id}/image:
     post:
-      operationId: setAuthorImageByID
+      operationId: setAuthorImageById
       summary: Set an author image using a provided URL.
       tags:
         - Authors
       parameters:
-        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorID'
-        - $ref: './controllers/AuthorController.yaml#/components/parameters/imageURL'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorId'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/imageUrl'
       responses:
         200:
-          description: setAuthorImageByID OK
+          description: setAuthorImageById OK
           content:
             application/json:
               schema:
@@ -93,31 +93,31 @@ paths:
         404:
           $ref: './controllers/AuthorController.yaml#/components/responses/author404'
     delete:
-      operationId: deleteAuthorImageByID
+      operationId: deleteAuthorImageById
       summary: Delete an author image from the server and remove the image from the database.
       tags:
         - Authors
       parameters:
-        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorID'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorId'
       responses:
         200:
           $ref: '#/components/responses/ok200'
         404:
           $ref: './controllers/AuthorController.yaml#/components/responses/author404'
     patch:
-      operationId: getAuthorImageByID
+      operationId: getAuthorImageById
       summary: Return the author image by author ID.
       tags:
         - Authors
       parameters:
-        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorID'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorId'
         - $ref: './controllers/AuthorController.yaml#/components/parameters/imageWidth'
         - $ref: './controllers/AuthorController.yaml#/components/parameters/imageHeight'
         - $ref: './controllers/AuthorController.yaml#/components/parameters/imageFormat'
         - $ref: './controllers/AuthorController.yaml#/components/parameters/imageRaw'
       responses:
         200:
-          description: getAuthorImageByID OK
+          description: getAuthorImageById OK
           content:
             image/*:
               schema:
@@ -127,17 +127,17 @@ paths:
           $ref: './controllers/AuthorController.yaml#/components/responses/author404'
   /api/authors/{id}/match:
     post:
-      operationId: matchAuthorByID
+      operationId: matchAuthorById
       summary: Match the author against Audible using quick match. Quick match updates the author's description and image (if no image already existed) with information from audible. Either `asin` or `q` must be provided, with `asin` taking priority if both are provided.
       tags:
         - Authors
       parameters:
-        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorID'
+        - $ref: './controllers/AuthorController.yaml#/components/parameters/authorId'
         - $ref: './controllers/AuthorController.yaml#/components/parameters/asin'
         - $ref: './controllers/AuthorController.yaml#/components/parameters/authorSearchName'
       responses:
         200:
-          description: matchAuthorByID OK
+          description: matchAuthorById OK
           content:
             application/json:
               schema:

From b124d6182669dd1e6abc226ac42715983ac9ca1f Mon Sep 17 00:00:00 2001
From: advplyr <advplyr@protonmail.com>
Date: Sat, 20 Apr 2024 14:57:38 -0500
Subject: [PATCH 7/7] Update yaml docs to include BearerAuth

---
 docs/openapi.json | Bin 75466 -> 75826 bytes
 docs/root.yaml    |  12 +++++++++---
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/docs/openapi.json b/docs/openapi.json
index 7d20981711adc3865ec70635df66387820aeea4d..93c7b27adde742fbd558d44dd7fd2b64b7b2d03a 100644
GIT binary patch
delta 198
zcmX?gmSxigmJLlzlEn<E49N_o3`Go?3?&Se48cHN22d<_^2J_RK_vz!AUhE#n+n8^
z45gDFUR0bcpd-Yd0aRVWP%!!6UFpdNY+RzL3`z`E3<^Lp387=MqM7jYd#a2AlYMyj
zrdP2u>P%k4)WVNu=j4xnMW-vMF^b5dSq8Kq18QtEL+teXs*K{3y_oo#4VbqZFf-0l
F1^}k^GQj`<

delta 22
ecmdmVf#uX$mJLlzlM9&FHP2w#K7)mEmNEc#?+H2p

diff --git a/docs/root.yaml b/docs/root.yaml
index b1e01d8f56..f31ccf67c9 100644
--- a/docs/root.yaml
+++ b/docs/root.yaml
@@ -7,9 +7,15 @@ servers:
   - url: http://localhost:3000
     description: Development server
 components:
-  responses: 
-    ok200:
-      description: OK
+    securitySchemes:
+        BearerAuth:
+            type: http
+            scheme: bearer
+    responses:
+        ok200:
+            description: OK
+security:
+    - BearerAuth: []
 paths:
   /api/authors/{id}:
     get: