From dcb5725642f10c22baf527e13de1b5d32c63e9ef Mon Sep 17 00:00:00 2001 From: Deluan Date: Sat, 18 Mar 2023 20:09:41 -0400 Subject: [PATCH 01/22] Add OpenAPI spec for new API, and wire up a new API handler Based on Aura and JSONAPI --- Makefile | 4 + api/spec.yml | 537 +++++++++++++++ api/spec1.yml | 202 ++++++ cmd/root.go | 1 + cmd/wire_gen.go | 10 +- cmd/wire_injectors.go | 8 + consts/consts.go | 1 + go.sum | 197 ++++-- server/api/api.go | 94 +++ server/api/api_suite_test.go | 17 + server/api/helpers.go | 193 ++++++ server/api/helpers_test.go | 110 +++ server/api/openapi_api.cfg.yaml | 6 + server/api/openapi_api.gen.go | 1040 +++++++++++++++++++++++++++++ server/api/openapi_types.cfg.yaml | 4 + server/api/openapi_types.gen.go | 282 ++++++++ 16 files changed, 2660 insertions(+), 46 deletions(-) create mode 100644 api/spec.yml create mode 100644 api/spec1.yml create mode 100644 server/api/api.go create mode 100644 server/api/api_suite_test.go create mode 100644 server/api/helpers.go create mode 100644 server/api/helpers_test.go create mode 100644 server/api/openapi_api.cfg.yaml create mode 100644 server/api/openapi_api.gen.go create mode 100644 server/api/openapi_types.cfg.yaml create mode 100644 server/api/openapi_types.gen.go diff --git a/Makefile b/Makefile index 43dca10a9..f834cfcb6 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,10 @@ wire: check_go_env ##@Development Update Dependency Injection go run github.com/google/wire/cmd/wire@latest ./... .PHONY: wire +api: check_go_env ##@Development Generate Aura API Server Boilerplate + go generate ./server/api/... +.PHONY: api + snapshots: ##@Development Update (GoLang) Snapshot tests UPDATE_SNAPSHOTS=true go run github.com/onsi/ginkgo/v2/ginkgo@latest ./server/subsonic/... .PHONY: snapshots diff --git a/api/spec.yml b/api/spec.yml new file mode 100644 index 000000000..387a7d117 --- /dev/null +++ b/api/spec.yml @@ -0,0 +1,537 @@ +openapi: 3.0.0 +info: + version: 0.2.0 + title: Navidrome API + description: > + This spec describes the Navidrome API, which allows users to browse and manage their music library via a JSON:API + based interface. The API provides endpoints for albums, tracks, artists, playlists and images, along with their + relationships. Clients can retrieve information about the items in the library, filter and sort results, and + perform actions such as creating and deleting playlists. With this API, developers can build music apps and + services that integrate with Navidrome music server, providing a seamless experience for users to access and + manage their music collection. + contact: + name: Navidrome + url: https://navidrome.org +servers: + - url: /api/v2 +paths: + /server: + get: + summary: Get server's global info + operationId: getServerInfo + responses: + '200': + description: The response’s data key maps to a resource object dictionary representing the server. + content: + application/vnd.api+json: + schema: + type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/ServerInfo' + '403': + $ref: '#/components/responses/NotAuthorized' + '500': + $ref: '#/components/responses/InternalServerError' + /tracks: + get: + summary: Get a list of tracks + operationId: getTracks + parameters: + - $ref: '#/components/parameters/pageLimit' + - $ref: '#/components/parameters/pageOffset' + - $ref: '#/components/parameters/filterEquals' + - $ref: '#/components/parameters/filterContains' + - $ref: '#/components/parameters/filterLessThan' + - $ref: '#/components/parameters/filterLessOrEqual' + - $ref: '#/components/parameters/filterGreaterThan' + - $ref: '#/components/parameters/filterGreaterOrEqual' + - $ref: '#/components/parameters/filterStartsWith' + - $ref: '#/components/parameters/filterEndsWith' + - $ref: '#/components/parameters/sort' + - $ref: '#/components/parameters/include' + responses: + '200': + description: A list of tracks + content: + application/vnd.api+json: + schema: + type: object + required: [data, links] + properties: + data: + type: array + items: + $ref: '#/components/schemas/Track' + links: + $ref: '#/components/schemas/PaginationLinks' + meta: + $ref: '#/components/schemas/PaginationMeta' + '400': + $ref: '#/components/responses/BadRequest' + '403': + $ref: '#/components/responses/NotAuthorized' + '500': + $ref: '#/components/responses/InternalServerError' + /tracks/{trackId}: + get: + summary: Retrieve an individual track + operationId: getTrack + parameters: + - $ref: '#/components/parameters/include' + - name: trackId + in: path + description: The unique identifier of the track + required: true + schema: + type: string + responses: + '200': + description: A track object + content: + application/vnd.api+json: + schema: + type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/Track' + '403': + $ref: '#/components/responses/NotAuthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + /artists: + get: + summary: Retrieve a list of artists + operationId: getArtists + parameters: + - $ref: '#/components/parameters/pageLimit' + - $ref: '#/components/parameters/pageOffset' + - $ref: '#/components/parameters/filterEquals' + - $ref: '#/components/parameters/filterContains' + - $ref: '#/components/parameters/filterLessThan' + - $ref: '#/components/parameters/filterLessOrEqual' + - $ref: '#/components/parameters/filterGreaterThan' + - $ref: '#/components/parameters/filterGreaterOrEqual' + - $ref: '#/components/parameters/filterStartsWith' + - $ref: '#/components/parameters/filterEndsWith' + - $ref: '#/components/parameters/sort' + - $ref: '#/components/parameters/include' + responses: + '200': + description: A list of artists + content: + application/vnd.api+json: + schema: + type: object + required: [data, links] + properties: + data: + type: array + items: + $ref: '#/components/schemas/Artist' + links: + $ref: '#/components/schemas/PaginationLinks' + meta: + $ref: '#/components/schemas/PaginationMeta' + '400': + $ref: '#/components/responses/BadRequest' + '403': + $ref: '#/components/responses/NotAuthorized' + '500': + $ref: '#/components/responses/InternalServerError' + /artists/{artistId}: + get: + summary: Retrieve an individual artist + operationId: getArtist + parameters: + - $ref: '#/components/parameters/include' + - name: artistId + in: path + description: The unique identifier of the artist + required: true + schema: + type: string + responses: + '200': + description: An artist object + content: + application/vnd.api+json: + schema: + type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/Artist' + '403': + $ref: '#/components/responses/NotAuthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' + +components: + parameters: + pageOffset: + name: page[offset] + in: query + description: The offset for pagination + required: false + schema: + type: integer + format: int32 + minimum: 0 + default: 0 + pageLimit: + name: page[limit] + in: query + description: The number of items per page + required: false + schema: + type: integer + format: int32 + minimum: 0 + default: 10 + filterEquals: + name: filter[equals] + in: query + description: 'Filter by any property with an exact match. Usage: filter[equals]=property:value' + required: false + schema: + type: array + items: + type: string + pattern: '^\w+:\w+' + filterLessThan: + name: filter[lessThan] + in: query + description: 'Filter by any numeric property less than a value. Usage: filter[lessThan]=property:value' + required: false + schema: + type: array + items: + type: string + pattern: '^\w+:\d+' + filterLessOrEqual: + name: filter[lessOrEqual] + in: query + description: 'Filter by any numeric property less than or equal to a value. Usage: filter[lessOrEqual]=property:value' + required: false + schema: + type: array + items: + type: string + pattern: '^\w+:\d+' + filterGreaterThan: + name: filter[greaterThan] + in: query + description: 'Filter by any numeric property greater than a value. Usage: filter[greaterThan]=property:value' + required: false + schema: + type: array + items: + type: string + pattern: '^\w+:\d+' + filterGreaterOrEqual: + name: filter[greaterOrEqual] + in: query + description: 'Filter by any numeric property greater than or equal to a value. Usage: filter[greaterOrEqual]=property:value' + required: false + schema: + type: array + items: + type: string + pattern: '^\w+:\d+' + filterContains: + name: filter[contains] + in: query + description: 'Filter by any property containing text. Usage: filter[contains]=property:value' + required: false + schema: + type: array + items: + type: string + pattern: '^\w+:\w+' + filterStartsWith: + name: filter[startsWith] + in: query + description: 'Filter by any property that starts with text. Usage: filter[startsWith]=property:value' + required: false + schema: + type: array + items: + type: string + pattern: '^\w+:\w+' + filterEndsWith: + name: filter[endsWith] + in: query + description: 'Filter by any property that ends with text. Usage: filter[endsWith]=property:value' + required: false + schema: + type: array + items: + type: string + pattern: '^\w+:\w+' + sort: + name: sort + in: query + description: Sort the results by one or more properties, separated by commas. Prefix the property with '-' for descending order. + required: false + schema: + type: string + include: + name: include + in: query + description: Related resources to include in the response, separated by commas + required: false + schema: + type: string + schemas: + ServerInfo: + type: object + required: [server, serverVersion, authRequired, features] + properties: + server: + type: string + description: The name of the server software. + example: "navidrome" + serverVersion: + type: string + description: The version number of the server. + example: "0.60.0" + authRequired: + type: boolean + description: Whether the user has access to the server. + example: true + features: + type: array + description: A list of optional features the server supports. + items: + type: string + enum: + - albums + - artists + - images + ResourceObject: + type: object + required: [id, type] + properties: + id: + type: string + description: The unique identifier for the resource + type: + type: string + description: The type of the resource + ResourceList: + type: object + properties: + data: + oneOf: + - $ref: '#/components/schemas/Track' + - $ref: '#/components/schemas/Artist' + - type: array + items: + $ref: '#/components/schemas/ResourceObject' + included: + type: array + items: + $ref: '#/components/schemas/IncludedResource' + links: + $ref: '#/components/schemas/PaginationLinks' + meta: + $ref: '#/components/schemas/PaginationMeta' + IncludedResource: + oneOf: + - $ref: '#/components/schemas/Track' + - $ref: '#/components/schemas/Artist' + discriminator: + propertyName: type + mapping: + track: '#/components/schemas/Track' + artist: '#/components/schemas/Artist' + Track: + allOf: + - $ref: '#/components/schemas/ResourceObject' + - type: object + properties: + attributes: + $ref: '#/components/schemas/TrackAttributes' + relationships: + type: object + properties: + artists: + type: array + items: + $ref: '#/components/schemas/TrackArtistRelationship' + required: + - artist + TrackAttributes: + type: object + properties: + title: + type: string + description: The title of the track + artist: + type: string + description: The name of the artist who performed the track + album: + type: string + description: The name of the album the track belongs to + composer: + type: string + description: The name of the composer who created the track + duration: + type: number + format: float + description: The duration of the track in seconds + required: + - title + - artist + - album + - duration + TrackArtistRelationship: + type: object + properties: + meta: + type: object + properties: + role: + $ref: '#/components/schemas/ArtistRole' + required: + - role + data: + $ref: '#/components/schemas/ResourceObject' + required: + - meta + - data + ArtistRole: + type: string + enum: + - albumArtist + - composer + - vocalist + description: The role of an artist in a track or album + Artist: + allOf: + - $ref: '#/components/schemas/ResourceObject' + - type: object + properties: + attributes: + $ref: '#/components/schemas/ArtistAttributes' + relationships: + type: object + properties: + tracks: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/ArtistTrackRelationship' + required: + - data + required: + - tracks + ArtistAttributes: + type: object + properties: + name: + type: string + description: The name of the artist + bio: + type: string + description: A short biography of the artist + ArtistTrackRelationship: + type: object + properties: + meta: + type: object + properties: + role: + $ref: '#/components/schemas/ArtistRole' + required: + - role + data: + $ref: '#/components/schemas/ResourceObject' + required: + - meta + - data + PaginationLinks: + type: object + properties: + first: + type: string + format: uri + prev: + type: string + format: uri + next: + type: string + format: uri + last: + type: string + format: uri + PaginationMeta: + type: object + properties: + currentPage: + type: integer + format: int32 + description: The current page in the collection + totalPages: + type: integer + format: int32 + description: The total numeber of pages in the collection + totalItems: + type: integer + format: int32 + description: The total number of items in the collection + ErrorList: + type: object + required: [errors] + properties: + errors: + type: array + items: + $ref: '#/components/schemas/ErrorObject' + ErrorObject: + type: object + properties: + id: + type: string + status: + type: string + title: + type: string + detail: + type: string + required: + - errors + responses: + NotFound: + description: Not Found + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/ErrorList' + NotAuthorized: + description: Not Authorized + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/ErrorList' + BadRequest: + description: Bad Request + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/ErrorList' + InternalServerError: + description: Internal Server Error + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/ErrorList' diff --git a/api/spec1.yml b/api/spec1.yml new file mode 100644 index 000000000..fbcc853b5 --- /dev/null +++ b/api/spec1.yml @@ -0,0 +1,202 @@ +openapi: "3.0.0" +info: + version: 0.3.0 + title: Navidrome API + description: 'This is heavily based on Aura API: https://auraspec.readthedocs.io/en/latest/api.html' + contact: + name: Navidrome + url: https://navidrome.org +servers: + - url: /api/v2 +security: + - bearerAuth: [] +paths: + /server: + get: + operationId: getServerInfo + responses: + '200': + description: The response’s data key maps to a resource object dictionary representing the server. + content: + application/vnd.api+json: + schema: + type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/ServerInfo' + default: + description: unexpected error + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/JSONAPIErrorResponse' + /tracks: + get: + operationId: getTracks + parameters: + - in: query + name: limit + schema: + type: integer + minimum: 0 + default: 0 + responses: + '200': + description: TODO + content: + application/vnd.api+json: + schema: + type: object + required: [data] + properties: + data: + type: array + items: + $ref: '#/components/schemas/Track' + default: + description: unexpected error + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/JSONAPIErrorResponse' + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + + schemas: + ServerInfo: + type: object + required: [aura-version, server, server-version, auth-required, features] + properties: + aura-version: + type: string + description: The version of the AURA spec implemented. + example: "1.0.0" + server: + type: string + description: The name of the server software. + example: "navidrome" + server-version: + type: string + description: The version number of the server. + example: "0.60.0" + auth-required: + type: boolean + description: Whether the user has access to the server. + example: true + features: + type: array + description: A list of optional features the server supports. + items: + type: string + enum: + - albums + - artists + - images + JSONAPIResourceObject: + title: JSON:API Resource Object + description: | + A JSON:API-compliant resource object. An inheritable object schema that contains the basic framework of a Resource Object from the JSON:API spec. + The `attributes`, `relationship`, and `meta` properties are added, as necessary, by the inheriting schema. + type: object + required: [type, id] + properties: + type: + $ref: '#/components/schemas/JSONAPIType' + id: + $ref: '#/components/schemas/JSONAPIId' + + JSONAPIType: + title: JSON:API type Member + description: | + The `type` member is used to describe resource objects that share common attributes and relationships. + Note: The type member is required in every resource object throughout requests and responses in JSON:API. There are some cases, such as when POSTing to an endpoint representing heterogeneous data, when the type could not be inferred from the endpoint. However, picking and choosing when it is required would be confusing; it would be hard to remember when it was required and when it was not. Therefore, to improve consistency and minimize confusion, type is always required. + type: string + + JSONAPIId: + title: JSON:API id Member + description: Every resource object MUST contain an id member and a type member (exception-the id member is not required when the resource object originates at the client and represents a new resource to be created on the server). Within a given API, each resource object’s type and id pair MUST identify a single, unique resource. + type: string + + Track: + allOf: + - $ref: '#/components/schemas/JSONAPIResourceObject' + - type: object + required: [attributes] + properties: + attributes: + $ref: '#/components/schemas/TrackAttributes' + + TrackAttributes: + type: object + required: [title, artist, album, albumartist, track, mimetype, duration, channels, bitrate, size] + properties: + title: + type: string + artist: + type: string + album: + type: string + albumartist: + type: string + track: + type: integer + disc: + type: integer + year: + type: integer + bpm: + type: integer + genre: + type: string + recording-mbid: + type: string + track-mbid: + type: string + comments: + type: string + mimetype: + type: string + duration: + type: number + channels: + type: integer + bitrate: + type: integer + size: + type: integer + + JSONAPIErrorResponse: + title: JSON:API Resource Object + type: object + required: [errors] + properties: + errors: + type: array + items: + $ref: '#/components/schemas/JSONAPIErrorObject' + JSONAPIErrorObject: + type: object + description: 'A JSON-API error object. see: https://jsonapi.org/format/#errors' + properties: + id: + type: string + description: a unique identifier for this particular occurrence of the problem. + example: 1 + status: + type: integer + description: the HTTP status code applicable to this problem, expressed as a string value. + example: 400 + code: + type: string + description: an application-specific error code, expressed as a string value. + example: invalid_request + detail: + type: string + description: a human-readable explanation specific to this occurrence of the problem. + example: First name must contain at least two characters. diff --git a/cmd/root.go b/cmd/root.go index d9fd9d614..29c4fde68 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -103,6 +103,7 @@ func startServer(ctx context.Context) func() error { if strings.HasPrefix(conf.Server.UILoginBackgroundURL, "/") { a.MountRouter("Background images", consts.DefaultUILoginBackgroundURL, backgrounds.NewHandler()) } + a.MountRouter("Aura API", consts.URLPathAPI, CreateAuraAPIRouter()) return a.Run(ctx, conf.Server.Address, conf.Server.Port, conf.Server.TLSCert, conf.Server.TLSKey) } } diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index 8fc241985..3fc249262 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -19,6 +19,7 @@ import ( "github.com/navidrome/navidrome/persistence" "github.com/navidrome/navidrome/scanner" "github.com/navidrome/navidrome/server" + "github.com/navidrome/navidrome/server/api" "github.com/navidrome/navidrome/server/events" "github.com/navidrome/navidrome/server/nativeapi" "github.com/navidrome/navidrome/server/public" @@ -44,6 +45,13 @@ func CreateNativeAPIRouter() *nativeapi.Router { return router } +func CreateAuraAPIRouter() *api.Router { + sqlDB := db.Db() + dataStore := persistence.New(sqlDB) + router := api.New(dataStore) + return router +} + func CreateSubsonicAPIRouter() *subsonic.Router { sqlDB := db.Db() dataStore := persistence.New(sqlDB) @@ -112,7 +120,7 @@ func createScanner() scanner.Scanner { // wire_injectors.go: -var allProviders = wire.NewSet(core.Set, artwork.Set, subsonic.New, nativeapi.New, public.New, persistence.New, lastfm.NewRouter, listenbrainz.NewRouter, events.GetBroker, db.Db) +var allProviders = wire.NewSet(core.Set, artwork.Set, subsonic.New, nativeapi.New, api.New, public.New, persistence.New, lastfm.NewRouter, listenbrainz.NewRouter, events.GetBroker, db.Db) // Scanner must be a Singleton var ( diff --git a/cmd/wire_injectors.go b/cmd/wire_injectors.go index cc896421f..dea1ce9eb 100644 --- a/cmd/wire_injectors.go +++ b/cmd/wire_injectors.go @@ -14,6 +14,7 @@ import ( "github.com/navidrome/navidrome/persistence" "github.com/navidrome/navidrome/scanner" "github.com/navidrome/navidrome/server" + "github.com/navidrome/navidrome/server/api" "github.com/navidrome/navidrome/server/events" "github.com/navidrome/navidrome/server/nativeapi" "github.com/navidrome/navidrome/server/public" @@ -25,6 +26,7 @@ var allProviders = wire.NewSet( artwork.Set, subsonic.New, nativeapi.New, + api.New, public.New, persistence.New, lastfm.NewRouter, @@ -46,6 +48,12 @@ func CreateNativeAPIRouter() *nativeapi.Router { )) } +func CreateAuraAPIRouter() *api.Router { + panic(wire.Build( + allProviders, + )) +} + func CreateSubsonicAPIRouter() *subsonic.Router { panic(wire.Build( allProviders, diff --git a/consts/consts.go b/consts/consts.go index d169661dd..6491479bc 100644 --- a/consts/consts.go +++ b/consts/consts.go @@ -32,6 +32,7 @@ const ( URLPathUI = "/app" URLPathNativeAPI = "/api" + URLPathAPI = "/api/v2" URLPathSubsonicAPI = "/rest" URLPathPublic = "/share" URLPathPublicImages = URLPathPublic + "/img" diff --git a/go.sum b/go.sum index 1b8ac6b5d..c5a3a3699 100644 --- a/go.sum +++ b/go.sum @@ -44,17 +44,30 @@ github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8 github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/ReneKroon/ttlcache/v2 v2.11.0 h1:OvlcYFYi941SBN3v9dsDcC2N8vRxyHcCmJb3Vl4QMoM= github.com/ReneKroon/ttlcache/v2 v2.11.0/go.mod h1:mBxvsNY+BT8qLLd6CuAJubbKo6r0jh3nb5et22bbfGY= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beego/beego/v2 v2.0.7 h1:9KNnUM40tn3pbCOFfe6SJ1oOL0oTi/oBS/C/wCEdAXA= github.com/beego/beego/v2 v2.0.7/go.mod h1:f0uOEkmJWgAuDTlTxUdgJzwG3PDSIf3UWF3NpMohbFE= +<<<<<<< HEAD +======= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +>>>>>>> 43f38c7d (WIP) +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -103,21 +116,31 @@ github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= -github.com/go-chi/httprate v0.7.4 h1:a2GIjv8he9LRf3712zxxnRdckQCm7I8y8yQhkJ84V6M= -github.com/go-chi/httprate v0.7.4/go.mod h1:6GOYBSwnpra4CQfAKXu8sQZg+nZ0M1g9QnyFvxrAB8A= +github.com/go-chi/httprate v0.7.1 h1:d5kXARdms2PREQfU4pHvq44S6hJ1hPu4OXLeBKmCKWs= +github.com/go-chi/httprate v0.7.1/go.mod h1:6GOYBSwnpra4CQfAKXu8sQZg+nZ0M1g9QnyFvxrAB8A= github.com/go-chi/jwtauth/v5 v5.1.0 h1:wJyf2YZ/ohPvNJBwPOzZaQbyzwgMZZceE1m8FOzXLeA= github.com/go-chi/jwtauth/v5 v5.1.0/go.mod h1:MA93hc1au3tAQwCKry+fI4LqJ5MIVN4XSsglOo+lSc8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -144,6 +167,7 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -160,6 +184,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -204,14 +229,23 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -236,25 +270,34 @@ github.com/lestrrat-go/jwx/v2 v2.0.9/go.mod h1:K68euYaR95FnL0hIQB8VvzL70vB7pSifb github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U= github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0= github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-zglob v0.0.3 h1:6Ry4EYsScDyt5di4OI6xw1bYhOqfE5S33Z1OPy+d+To= github.com/mattn/go-zglob v0.0.3/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/microcosm-cc/bluemonday v1.0.24 h1:NGQoPtwGVcbGkKfvyYk1yRqknzBuoMiUrO6R7uFTPlw= -github.com/microcosm-cc/bluemonday v1.0.24/go.mod h1:ArQySAMps0790cHSkdPEJ7bGkF2VePWH773hsJNSHf8= -github.com/mileusna/useragent v1.3.2 h1:yGBQVNkyrlnSe4l0rlaQoH8XlG9xDkc6a7ygwPxALoU= -github.com/mileusna/useragent v1.3.2/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/microcosm-cc/bluemonday v1.0.23 h1:SMZe2IGa0NuHvnVNAZ+6B38gsTbi5e4sViiWJyDDqFY= +github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4= +github.com/mileusna/useragent v1.2.1 h1:p3RJWhi3LfuI6BHdddojREyK3p6qX67vIfOVMnUIVr0= +github.com/mileusna/useragent v1.2.1/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -262,29 +305,48 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= +github.com/onsi/ginkgo/v2 v2.9.2 h1:BA2GMJOtfGAfagzYtrAlufIP0lq6QERkFmHLMLPwFSU= +github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= -github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pressly/goose/v3 v3.11.2 h1:QgTP45FhBBHdmf7hWKlbWFHtwPtxo0phSDkwDKGUrYs= -github.com/pressly/goose/v3 v3.11.2/go.mod h1:LWQzSc4vwfHA/3B8getTp8g3J5Z8tFBxgxinmGlMlJk= -github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= -github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/pressly/goose/v3 v3.10.0 h1:Gn5E9CkPqTtWvfaDVqtJqMjYtsrZ9K5mU/8wzTsvg04= +github.com/pressly/goose/v3 v3.10.0/go.mod h1:c5D3a7j66cT0fhRPj7KsXolfduVrhLlxKZjmCVSey5w= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= @@ -296,9 +358,11 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik= github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= -github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= @@ -321,6 +385,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -328,9 +393,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/unrolled/secure v1.13.0 h1:sdr3Phw2+f8Px8HE5sd1EHdj1aV3yUwed/uZXChLFsk= @@ -349,9 +413,21 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +<<<<<<< HEAD go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +======= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +>>>>>>> 43f38c7d (WIP) +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -360,9 +436,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -373,8 +448,17 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +<<<<<<< HEAD golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= +======= +golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= +golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2 h1:J74nGeMgeFnYQJN59eFwh06jX/V8g0lB7LWpjSLxtgU= +golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +>>>>>>> 43f38c7d (WIP) golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -409,6 +493,7 @@ golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -416,6 +501,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -440,11 +526,14 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -454,6 +543,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -466,11 +557,12 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= -golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -486,6 +578,7 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -498,6 +591,8 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -506,19 +601,24 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -529,6 +629,7 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= @@ -592,8 +693,8 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= +golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -688,20 +789,26 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -712,14 +819,14 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= -modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= +modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/sqlite v1.22.1 h1:P2+Dhp5FR1RlVRkQ3dDfCiv3Ok8XPxqpe70IjYVA9oE= +modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/server/api/api.go b/server/api/api.go new file mode 100644 index 000000000..f68f0ea8f --- /dev/null +++ b/server/api/api.go @@ -0,0 +1,94 @@ +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen -config ./openapi_api.cfg.yaml "../../api/spec.yml" +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen -config ./openapi_types.cfg.yaml "../../api/spec.yml" + +package api + +import ( + "context" + "net/http" + "net/url" + + middleware "github.com/deepmap/oapi-codegen/pkg/chi-middleware" + "github.com/getkin/kin-openapi/openapi3" + "github.com/go-chi/chi/v5" + "github.com/navidrome/navidrome/consts" + "github.com/navidrome/navidrome/model" +) + +var spec = func() *openapi3.T { + s, _ := GetSwagger() + //s.Servers = nil + //s.Components.SecuritySchemes = nil + s.Security = nil //TODO + return s +}() + +func New(ds model.DataStore) *Router { + r := &Router{ds: ds} + mux := chi.NewRouter() + mux.Use(middleware.OapiRequestValidatorWithOptions(spec, &middleware.Options{ + ErrorHandler: validationErrorHandler, + })) + + handler := NewStrictHandlerWithOptions(r, nil, StrictHTTPServerOptions{ + RequestErrorHandlerFunc: apiErrorHandler, + ResponseErrorHandlerFunc: apiErrorHandler, + }) + r.Handler = HandlerFromMux(handler, mux) + return r +} + +var _ StrictServerInterface = (*Router)(nil) + +type Router struct { + http.Handler + ds model.DataStore +} + +func (a *Router) GetArtists(ctx context.Context, request GetArtistsRequestObject) (GetArtistsResponseObject, error) { + //TODO implement me + panic("implement me") +} + +func (a *Router) GetArtist(ctx context.Context, request GetArtistRequestObject) (GetArtistResponseObject, error) { + //TODO implement me + panic("implement me") +} + +func (a *Router) GetServerInfo(_ context.Context, _ GetServerInfoRequestObject) (GetServerInfoResponseObject, error) { + return GetServerInfo200JSONResponse{ + Data: ServerInfo{ + AuthRequired: true, + Features: []ServerInfoFeatures{}, + Server: consts.AppName, + ServerVersion: consts.Version, + }, + }, nil +} + +func (a *Router) GetTracks(ctx context.Context, request GetTracksRequestObject) (GetTracksResponseObject, error) { + options := toQueryOptions(request.Params) + mfs, err := a.ds.MediaFile(ctx).GetAll(options) + if err != nil { + return nil, err + } + cnt, err := a.ds.MediaFile(ctx).CountAll(options) + if err != nil { + return nil, err + } + baseUrl, _ := url.JoinPath(spec.Servers[0].URL, "tracks") + links, meta := buildPaginationLinksAndMeta(int32(cnt), request.Params, baseUrl) + return GetTracks200JSONResponse{ + Data: toAPITracks(mfs), + Links: links, + Meta: &meta, + }, nil +} + +func (a *Router) GetTrack(ctx context.Context, request GetTrackRequestObject) (GetTrackResponseObject, error) { + mf, err := a.ds.MediaFile(ctx).Get(request.TrackId) + if err != nil { + return nil, err + } + return GetTrack200JSONResponse{Data: toAPITrack(*mf)}, nil +} diff --git a/server/api/api_suite_test.go b/server/api/api_suite_test.go new file mode 100644 index 000000000..a322cebba --- /dev/null +++ b/server/api/api_suite_test.go @@ -0,0 +1,17 @@ +package api + +import ( + "testing" + + "github.com/navidrome/navidrome/log" + "github.com/navidrome/navidrome/tests" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestApi(t *testing.T) { + tests.Init(t, false) + log.SetLevel(log.LevelFatal) + RegisterFailHandler(Fail) + RunSpecs(t, "Navidrome JSON:API Suite") +} diff --git a/server/api/helpers.go b/server/api/helpers.go new file mode 100644 index 000000000..c5ada9b82 --- /dev/null +++ b/server/api/helpers.go @@ -0,0 +1,193 @@ +package api + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/Masterminds/squirrel" + "github.com/navidrome/navidrome/model" +) + +func toAPITrack(mf model.MediaFile) Track { + return Track{ + Type: "track", + Id: mf.ID, + Attributes: &TrackAttributes{ + Album: mf.Album, + //Albumartist: mf.AlbumArtist, + Artist: mf.Artist, + //Bitrate: mf.BitRate, + //Bpm: p(mf.Bpm), + //Channels: mf.Channels, + //Comments: p(mf.Comment), + //Disc: p(mf.DiscNumber), + Duration: mf.Duration, + //Genre: p(mf.Genre), + //Mimetype: mf.ContentType(), + //RecordingMbid: p(mf.MbzTrackID), + //Size: int(mf.Size), + Title: mf.Title, + //Track: mf.TrackNumber, + //TrackMbid: p(mf.MbzReleaseTrackID), + //Year: p(mf.Year), + }, + } +} + +func toAPITracks(mfs model.MediaFiles) []Track { + tracks := make([]Track, len(mfs)) + for i := range mfs { + tracks[i] = toAPITrack(mfs[i]) + } + return tracks +} + +func p[T comparable](t T) *T { + var zero T + if t == zero { + return nil + } + return &t +} + +func v[T comparable](p *T) T { + var zero T + if p == nil { + return zero + } + return *p +} + +// toQueryOptions convert a params struct to a model.QueryOptions struct, to be used by the +// GetAll and CountAll functions. It assumes all GetXxxxParams functions have the exact same structure. +func toQueryOptions(params GetTracksParams) model.QueryOptions { + var filters squirrel.And + parseFilter := func(fs *[]string, op func(f, v string) squirrel.Sqlizer) { + if fs != nil { + for _, f := range *fs { + parts := strings.SplitN(f, ":", 2) + filters = append(filters, op(parts[0], parts[1])) + } + } + } + parseFilter(params.FilterEquals, func(f, v string) squirrel.Sqlizer { return squirrel.Eq{f: v} }) + parseFilter(params.FilterContains, func(f, v string) squirrel.Sqlizer { return squirrel.Like{f: "%" + v + "%"} }) + parseFilter(params.FilterStartsWith, func(f, v string) squirrel.Sqlizer { return squirrel.Like{f: v + "%"} }) + parseFilter(params.FilterEndsWith, func(f, v string) squirrel.Sqlizer { return squirrel.Like{f: "%" + v} }) + parseFilter(params.FilterGreaterThan, func(f, v string) squirrel.Sqlizer { return squirrel.Gt{f: v} }) + parseFilter(params.FilterGreaterOrEqual, func(f, v string) squirrel.Sqlizer { return squirrel.GtOrEq{f: v} }) + parseFilter(params.FilterLessThan, func(f, v string) squirrel.Sqlizer { return squirrel.Lt{f: v} }) + parseFilter(params.FilterLessOrEqual, func(f, v string) squirrel.Sqlizer { return squirrel.LtOrEq{f: v} }) + offset := v(params.PageOffset) + limit := v(params.PageLimit) + return model.QueryOptions{Max: int(limit), Offset: int(offset), Filters: filters} +} + +func apiErrorHandler(w http.ResponseWriter, r *http.Request, err error) { + var res ErrorObject + switch err { + case model.ErrNotAuthorized: + res = ErrorObject{Status: p(strconv.Itoa(http.StatusForbidden)), Title: p(http.StatusText(http.StatusForbidden))} + case model.ErrNotFound: + res = ErrorObject{Status: p(strconv.Itoa(http.StatusNotFound)), Title: p(http.StatusText(http.StatusNotFound))} + default: + res = ErrorObject{Status: p(strconv.Itoa(http.StatusInternalServerError)), Title: p(http.StatusText(http.StatusInternalServerError))} + } + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(403) + + json.NewEncoder(w).Encode(ErrorList{[]ErrorObject{res}}) +} + +func validationErrorHandler(w http.ResponseWriter, message string, statusCode int) { + _ = GetTracks400JSONResponse{BadRequestJSONResponse{Errors: []ErrorObject{ + { + Status: p(strconv.Itoa(statusCode)), + Title: p(http.StatusText(statusCode)), + Detail: p(message), + }, + }}}.VisitGetTracksResponse(w) +} + +func buildPaginationLinksAndMeta(totalItems int32, params GetTracksParams, resourceName string) (PaginationLinks, PaginationMeta) { + pageLimit := *params.PageLimit + pageOffset := *params.PageOffset + + totalPages := (totalItems + pageLimit - 1) / pageLimit + currentPage := pageOffset/pageLimit + 1 + + meta := PaginationMeta{ + CurrentPage: ¤tPage, + TotalItems: &totalItems, + TotalPages: &totalPages, + } + + var first, last, next, prev *string + + buildLink := func(page int32) *string { + query := url.Values{} + query.Add("page[offset]", strconv.Itoa(int(page*pageLimit))) + query.Add("page[limit]", strconv.Itoa(int(pageLimit))) + + addFilterParams := func(paramName string, values *[]string) { + if values == nil { + return + } + for _, value := range *values { + query.Add(paramName, value) + } + } + + addFilterParams("filter[equals]", params.FilterEquals) + addFilterParams("filter[contains]", params.FilterContains) + addFilterParams("filter[lessThan]", params.FilterLessThan) + addFilterParams("filter[lessOrEqual]", params.FilterLessOrEqual) + addFilterParams("filter[greaterThan]", params.FilterGreaterThan) + addFilterParams("filter[greaterOrEqual]", params.FilterGreaterOrEqual) + addFilterParams("filter[startsWith]", params.FilterStartsWith) + addFilterParams("filter[endsWith]", params.FilterEndsWith) + + if params.Sort != nil { + query.Add("sort", string(*params.Sort)) + } + if params.Include != nil { + query.Add("include", string(*params.Include)) + } + + link := resourceName + if len(query) > 0 { + link += "&" + query.Encode() + } + return &link + } + + if totalPages > 0 { + firstLink := buildLink(0) + first = firstLink + + lastLink := buildLink(totalPages - 1) + last = lastLink + } + + if currentPage < totalPages { + nextLink := buildLink(currentPage) + next = nextLink + } + + if currentPage > 1 { + prevLink := buildLink(currentPage - 2) + prev = prevLink + } + + links := PaginationLinks{ + First: first, + Last: last, + Next: next, + Prev: prev, + } + + return links, meta +} diff --git a/server/api/helpers_test.go b/server/api/helpers_test.go new file mode 100644 index 000000000..9efe1a132 --- /dev/null +++ b/server/api/helpers_test.go @@ -0,0 +1,110 @@ +package api + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("BuildPaginationLinksAndMeta", func() { + var ( + totalItems int32 + params GetTracksParams + resourceName string + ) + + BeforeEach(func() { + totalItems = 150 + resourceName = "api/resource" + }) + + Context("with default page limit and offset", func() { + BeforeEach(func() { + l, o := int32(10), int32(0) + params = GetTracksParams{ + PageLimit: &l, + PageOffset: &o, + } + }) + + It("returns correct pagination links and meta", func() { + links, meta := buildPaginationLinksAndMeta(totalItems, params, resourceName) + + Expect(links.First).To(Equal(p("api/resource?page[offset]=0&page[limit]=10"))) + Expect(links.Last).To(Equal(p("api/resource?page[offset]=140&page[limit]=10"))) + Expect(links.Next).To(Equal(p("api/resource?page[offset]=10&page[limit]=10"))) + Expect(links.Prev).To(BeNil()) + + Expect(meta.CurrentPage).To(Equal(p(int32(1)))) + Expect(meta.TotalItems).To(Equal(p(int32(150)))) + Expect(meta.TotalPages).To(Equal(p(int32(15)))) + }) + }) + + Context("with custom page limit and offset", func() { + BeforeEach(func() { + params = GetTracksParams{ + PageLimit: p((PageLimit)(20)), + PageOffset: p((PageOffset)(40)), + } + }) + + It("returns correct pagination links and meta", func() { + links, meta := buildPaginationLinksAndMeta(totalItems, params, resourceName) + + Expect(links.First).To(Equal(p("api/resource?page[offset]=0&page[limit]=20"))) + Expect(links.Last).To(Equal(p("api/resource?page[offset]=140&page[limit]=20"))) + Expect(links.Next).To(Equal(p("api/resource?page[offset]=60&page[limit]=20"))) + Expect(links.Prev).To(Equal(p("api/resource?page[offset]=20&page[limit]=20"))) + + Expect(meta.CurrentPage).To(Equal(p(int32(3)))) + Expect(meta.TotalItems).To(Equal(p(int32(150)))) + Expect(meta.TotalPages).To(Equal(p(int32(8)))) + }) + }) + + Context("with various filter params", func() { + BeforeEach(func() { + params = GetTracksParams{ + PageLimit: p((PageLimit)(20)), + PageOffset: p((PageOffset)(40)), + FilterEquals: &[]string{"property1:value1", "property2:value2"}, + FilterContains: &[]string{"property3:value3"}, + FilterLessThan: &[]string{"property4:value4"}, + FilterLessOrEqual: &[]string{"property5:value5"}, + FilterGreaterThan: &[]string{"property6:value6"}, + FilterGreaterOrEqual: &[]string{"property7:value7"}, + FilterStartsWith: &[]string{"property8:value8"}, + FilterEndsWith: &[]string{"property9:value9"}, + } + }) + + It("returns correct pagination links with filter params", func() { + links, _ := buildPaginationLinksAndMeta(totalItems, params, resourceName) + + expectedLinkPrefix := "api/resource?" + expectedParams := []string{ + "page[offset]=0&page[limit]=20", + "filter[equals]=property1:value1&filter[equals]=property2:value2", + "filter[contains]=property3:value3", + "filter[lessThan]=property4:value4", + "filter[lessOrEqual]=property5:value5", + "filter[greaterThan]=property6:value6", + "filter[greaterOrEqual]=property7:value7", + "filter[startsWith]=property8:value8", + "filter[endsWith]=property9:value9", + } + + Expect(*links.First).To(HavePrefix(expectedLinkPrefix)) + Expect(*links.Last).To(HavePrefix(expectedLinkPrefix)) + Expect(*links.Next).To(HavePrefix(expectedLinkPrefix)) + Expect(*links.Prev).To(HavePrefix(expectedLinkPrefix)) + + for _, param := range expectedParams { + Expect(*links.First).To(ContainSubstring(param)) + Expect(*links.Last).To(ContainSubstring(param)) + Expect(*links.Next).To(ContainSubstring(param)) + Expect(*links.Prev).To(ContainSubstring(param)) + } + }) + }) +}) diff --git a/server/api/openapi_api.cfg.yaml b/server/api/openapi_api.cfg.yaml new file mode 100644 index 000000000..e046b5986 --- /dev/null +++ b/server/api/openapi_api.cfg.yaml @@ -0,0 +1,6 @@ +package: api +generate: + chi-server: true + strict-server: true + embedded-spec: true +output: openapi_api.gen.go diff --git a/server/api/openapi_api.gen.go b/server/api/openapi_api.gen.go new file mode 100644 index 000000000..e93502a35 --- /dev/null +++ b/server/api/openapi_api.gen.go @@ -0,0 +1,1040 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version v1.12.5-0.20230314231417-0cfaaa77a7d2 DO NOT EDIT. +package api + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "net/url" + "path" + "strings" + + "github.com/deepmap/oapi-codegen/pkg/runtime" + "github.com/getkin/kin-openapi/openapi3" + "github.com/go-chi/chi/v5" +) + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Retrieve a list of artists + // (GET /artists) + GetArtists(w http.ResponseWriter, r *http.Request, params GetArtistsParams) + // Retrieve an individual artist + // (GET /artists/{artistId}) + GetArtist(w http.ResponseWriter, r *http.Request, artistId string, params GetArtistParams) + // Get server's global info + // (GET /server) + GetServerInfo(w http.ResponseWriter, r *http.Request) + // Get a list of tracks + // (GET /tracks) + GetTracks(w http.ResponseWriter, r *http.Request, params GetTracksParams) + // Retrieve an individual track + // (GET /tracks/{trackId}) + GetTrack(w http.ResponseWriter, r *http.Request, trackId string, params GetTrackParams) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetArtists operation middleware +func (siw *ServerInterfaceWrapper) GetArtists(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params GetArtistsParams + + // ------------- Optional query parameter "page[limit]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "page[limit]", r.URL.Query(), ¶ms.PageLimit) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "page[limit]", Err: err}) + return + } + + // ------------- Optional query parameter "page[offset]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "page[offset]", r.URL.Query(), ¶ms.PageOffset) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "page[offset]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[equals]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[equals]", r.URL.Query(), ¶ms.FilterEquals) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[equals]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[contains]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[contains]", r.URL.Query(), ¶ms.FilterContains) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[contains]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[lessThan]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[lessThan]", r.URL.Query(), ¶ms.FilterLessThan) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[lessThan]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[lessOrEqual]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[lessOrEqual]", r.URL.Query(), ¶ms.FilterLessOrEqual) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[lessOrEqual]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[greaterThan]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[greaterThan]", r.URL.Query(), ¶ms.FilterGreaterThan) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[greaterThan]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[greaterOrEqual]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[greaterOrEqual]", r.URL.Query(), ¶ms.FilterGreaterOrEqual) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[greaterOrEqual]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[startsWith]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[startsWith]", r.URL.Query(), ¶ms.FilterStartsWith) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[startsWith]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[endsWith]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[endsWith]", r.URL.Query(), ¶ms.FilterEndsWith) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[endsWith]", Err: err}) + return + } + + // ------------- Optional query parameter "sort" ------------- + + err = runtime.BindQueryParameter("form", true, false, "sort", r.URL.Query(), ¶ms.Sort) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "sort", Err: err}) + return + } + + // ------------- Optional query parameter "include" ------------- + + err = runtime.BindQueryParameter("form", true, false, "include", r.URL.Query(), ¶ms.Include) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "include", Err: err}) + return + } + + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetArtists(w, r, params) + }) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetArtist operation middleware +func (siw *ServerInterfaceWrapper) GetArtist(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "artistId" ------------- + var artistId string + + err = runtime.BindStyledParameterWithLocation("simple", false, "artistId", runtime.ParamLocationPath, chi.URLParam(r, "artistId"), &artistId) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "artistId", Err: err}) + return + } + + // Parameter object where we will unmarshal all parameters from the context + var params GetArtistParams + + // ------------- Optional query parameter "include" ------------- + + err = runtime.BindQueryParameter("form", true, false, "include", r.URL.Query(), ¶ms.Include) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "include", Err: err}) + return + } + + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetArtist(w, r, artistId, params) + }) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetServerInfo operation middleware +func (siw *ServerInterfaceWrapper) GetServerInfo(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetServerInfo(w, r) + }) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetTracks operation middleware +func (siw *ServerInterfaceWrapper) GetTracks(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params GetTracksParams + + // ------------- Optional query parameter "page[limit]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "page[limit]", r.URL.Query(), ¶ms.PageLimit) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "page[limit]", Err: err}) + return + } + + // ------------- Optional query parameter "page[offset]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "page[offset]", r.URL.Query(), ¶ms.PageOffset) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "page[offset]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[equals]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[equals]", r.URL.Query(), ¶ms.FilterEquals) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[equals]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[contains]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[contains]", r.URL.Query(), ¶ms.FilterContains) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[contains]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[lessThan]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[lessThan]", r.URL.Query(), ¶ms.FilterLessThan) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[lessThan]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[lessOrEqual]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[lessOrEqual]", r.URL.Query(), ¶ms.FilterLessOrEqual) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[lessOrEqual]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[greaterThan]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[greaterThan]", r.URL.Query(), ¶ms.FilterGreaterThan) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[greaterThan]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[greaterOrEqual]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[greaterOrEqual]", r.URL.Query(), ¶ms.FilterGreaterOrEqual) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[greaterOrEqual]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[startsWith]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[startsWith]", r.URL.Query(), ¶ms.FilterStartsWith) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[startsWith]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[endsWith]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[endsWith]", r.URL.Query(), ¶ms.FilterEndsWith) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[endsWith]", Err: err}) + return + } + + // ------------- Optional query parameter "sort" ------------- + + err = runtime.BindQueryParameter("form", true, false, "sort", r.URL.Query(), ¶ms.Sort) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "sort", Err: err}) + return + } + + // ------------- Optional query parameter "include" ------------- + + err = runtime.BindQueryParameter("form", true, false, "include", r.URL.Query(), ¶ms.Include) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "include", Err: err}) + return + } + + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetTracks(w, r, params) + }) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetTrack operation middleware +func (siw *ServerInterfaceWrapper) GetTrack(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "trackId" ------------- + var trackId string + + err = runtime.BindStyledParameterWithLocation("simple", false, "trackId", runtime.ParamLocationPath, chi.URLParam(r, "trackId"), &trackId) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "trackId", Err: err}) + return + } + + // Parameter object where we will unmarshal all parameters from the context + var params GetTrackParams + + // ------------- Optional query parameter "include" ------------- + + err = runtime.BindQueryParameter("form", true, false, "include", r.URL.Query(), ¶ms.Include) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "include", Err: err}) + return + } + + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetTrack(w, r, trackId, params) + }) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshallingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshallingParamError) Error() string { + return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshallingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/artists", wrapper.GetArtists) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/artists/{artistId}", wrapper.GetArtist) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/server", wrapper.GetServerInfo) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/tracks", wrapper.GetTracks) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/tracks/{trackId}", wrapper.GetTrack) + }) + + return r +} + +type BadRequestJSONResponse ErrorList + +type InternalServerErrorJSONResponse ErrorList + +type NotAuthorizedJSONResponse ErrorList + +type NotFoundJSONResponse ErrorList + +type GetArtistsRequestObject struct { + Params GetArtistsParams +} + +type GetArtistsResponseObject interface { + VisitGetArtistsResponse(w http.ResponseWriter) error +} + +type GetArtists200JSONResponse struct { + Data []Artist `json:"data"` + Links PaginationLinks `json:"links"` + Meta *PaginationMeta `json:"meta,omitempty"` +} + +func (response GetArtists200JSONResponse) VisitGetArtistsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetArtists400JSONResponse struct{ BadRequestJSONResponse } + +func (response GetArtists400JSONResponse) VisitGetArtistsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type GetArtists403JSONResponse struct{ NotAuthorizedJSONResponse } + +func (response GetArtists403JSONResponse) VisitGetArtistsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(403) + + return json.NewEncoder(w).Encode(response) +} + +type GetArtists500JSONResponse struct { + InternalServerErrorJSONResponse +} + +func (response GetArtists500JSONResponse) VisitGetArtistsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type GetArtistRequestObject struct { + ArtistId string `json:"artistId"` + Params GetArtistParams +} + +type GetArtistResponseObject interface { + VisitGetArtistResponse(w http.ResponseWriter) error +} + +type GetArtist200JSONResponse struct { + Data Artist `json:"data"` +} + +func (response GetArtist200JSONResponse) VisitGetArtistResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetArtist403JSONResponse struct{ NotAuthorizedJSONResponse } + +func (response GetArtist403JSONResponse) VisitGetArtistResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(403) + + return json.NewEncoder(w).Encode(response) +} + +type GetArtist404JSONResponse struct{ NotFoundJSONResponse } + +func (response GetArtist404JSONResponse) VisitGetArtistResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type GetArtist500JSONResponse struct { + InternalServerErrorJSONResponse +} + +func (response GetArtist500JSONResponse) VisitGetArtistResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type GetServerInfoRequestObject struct { +} + +type GetServerInfoResponseObject interface { + VisitGetServerInfoResponse(w http.ResponseWriter) error +} + +type GetServerInfo200JSONResponse struct { + Data ServerInfo `json:"data"` +} + +func (response GetServerInfo200JSONResponse) VisitGetServerInfoResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetServerInfo403JSONResponse struct{ NotAuthorizedJSONResponse } + +func (response GetServerInfo403JSONResponse) VisitGetServerInfoResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(403) + + return json.NewEncoder(w).Encode(response) +} + +type GetServerInfo500JSONResponse struct { + InternalServerErrorJSONResponse +} + +func (response GetServerInfo500JSONResponse) VisitGetServerInfoResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type GetTracksRequestObject struct { + Params GetTracksParams +} + +type GetTracksResponseObject interface { + VisitGetTracksResponse(w http.ResponseWriter) error +} + +type GetTracks200JSONResponse struct { + Data []Track `json:"data"` + Links PaginationLinks `json:"links"` + Meta *PaginationMeta `json:"meta,omitempty"` +} + +func (response GetTracks200JSONResponse) VisitGetTracksResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetTracks400JSONResponse struct{ BadRequestJSONResponse } + +func (response GetTracks400JSONResponse) VisitGetTracksResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type GetTracks403JSONResponse struct{ NotAuthorizedJSONResponse } + +func (response GetTracks403JSONResponse) VisitGetTracksResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(403) + + return json.NewEncoder(w).Encode(response) +} + +type GetTracks500JSONResponse struct { + InternalServerErrorJSONResponse +} + +func (response GetTracks500JSONResponse) VisitGetTracksResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type GetTrackRequestObject struct { + TrackId string `json:"trackId"` + Params GetTrackParams +} + +type GetTrackResponseObject interface { + VisitGetTrackResponse(w http.ResponseWriter) error +} + +type GetTrack200JSONResponse struct { + Data Track `json:"data"` +} + +func (response GetTrack200JSONResponse) VisitGetTrackResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetTrack403JSONResponse struct{ NotAuthorizedJSONResponse } + +func (response GetTrack403JSONResponse) VisitGetTrackResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(403) + + return json.NewEncoder(w).Encode(response) +} + +type GetTrack404JSONResponse struct{ NotFoundJSONResponse } + +func (response GetTrack404JSONResponse) VisitGetTrackResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type GetTrack500JSONResponse struct { + InternalServerErrorJSONResponse +} + +func (response GetTrack500JSONResponse) VisitGetTrackResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + // Retrieve a list of artists + // (GET /artists) + GetArtists(ctx context.Context, request GetArtistsRequestObject) (GetArtistsResponseObject, error) + // Retrieve an individual artist + // (GET /artists/{artistId}) + GetArtist(ctx context.Context, request GetArtistRequestObject) (GetArtistResponseObject, error) + // Get server's global info + // (GET /server) + GetServerInfo(ctx context.Context, request GetServerInfoRequestObject) (GetServerInfoResponseObject, error) + // Get a list of tracks + // (GET /tracks) + GetTracks(ctx context.Context, request GetTracksRequestObject) (GetTracksResponseObject, error) + // Retrieve an individual track + // (GET /tracks/{trackId}) + GetTrack(ctx context.Context, request GetTrackRequestObject) (GetTrackResponseObject, error) +} + +type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, args interface{}) (interface{}, error) + +type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc + +type StrictHTTPServerOptions struct { + RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions{ + RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + }, + ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusInternalServerError) + }, + }} +} + +func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: options} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc + options StrictHTTPServerOptions +} + +// GetArtists operation middleware +func (sh *strictHandler) GetArtists(w http.ResponseWriter, r *http.Request, params GetArtistsParams) { + var request GetArtistsRequestObject + + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetArtists(ctx, request.(GetArtistsRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetArtists") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetArtistsResponseObject); ok { + if err := validResponse.VisitGetArtistsResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + } +} + +// GetArtist operation middleware +func (sh *strictHandler) GetArtist(w http.ResponseWriter, r *http.Request, artistId string, params GetArtistParams) { + var request GetArtistRequestObject + + request.ArtistId = artistId + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetArtist(ctx, request.(GetArtistRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetArtist") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetArtistResponseObject); ok { + if err := validResponse.VisitGetArtistResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + } +} + +// GetServerInfo operation middleware +func (sh *strictHandler) GetServerInfo(w http.ResponseWriter, r *http.Request) { + var request GetServerInfoRequestObject + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetServerInfo(ctx, request.(GetServerInfoRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetServerInfo") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetServerInfoResponseObject); ok { + if err := validResponse.VisitGetServerInfoResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + } +} + +// GetTracks operation middleware +func (sh *strictHandler) GetTracks(w http.ResponseWriter, r *http.Request, params GetTracksParams) { + var request GetTracksRequestObject + + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetTracks(ctx, request.(GetTracksRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetTracks") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetTracksResponseObject); ok { + if err := validResponse.VisitGetTracksResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + } +} + +// GetTrack operation middleware +func (sh *strictHandler) GetTrack(w http.ResponseWriter, r *http.Request, trackId string, params GetTrackParams) { + var request GetTrackRequestObject + + request.TrackId = trackId + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetTrack(ctx, request.(GetTrackRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetTrack") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetTrackResponseObject); ok { + if err := validResponse.VisitGetTrackResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + } +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "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", + "Od/ebP8XAAD//1dw5/GYLgAA", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %s", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %s", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + var res = make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + var resolvePath = PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + var pathToFile = url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/server/api/openapi_types.cfg.yaml b/server/api/openapi_types.cfg.yaml new file mode 100644 index 000000000..8cf96c5c9 --- /dev/null +++ b/server/api/openapi_types.cfg.yaml @@ -0,0 +1,4 @@ +package: api +generate: + models: true +output: openapi_types.gen.go diff --git a/server/api/openapi_types.gen.go b/server/api/openapi_types.gen.go new file mode 100644 index 000000000..6156f40d1 --- /dev/null +++ b/server/api/openapi_types.gen.go @@ -0,0 +1,282 @@ +// Package api provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version v1.12.5-0.20230314231417-0cfaaa77a7d2 DO NOT EDIT. +package api + +// Defines values for ArtistRole. +const ( + AlbumArtist ArtistRole = "albumArtist" + Composer ArtistRole = "composer" + Vocalist ArtistRole = "vocalist" +) + +// Defines values for ServerInfoFeatures. +const ( + Albums ServerInfoFeatures = "albums" + Artists ServerInfoFeatures = "artists" + Images ServerInfoFeatures = "images" +) + +// Artist defines model for Artist. +type Artist struct { + Attributes *ArtistAttributes `json:"attributes,omitempty"` + + // Id The unique identifier for the resource + Id string `json:"id"` + Relationships *struct { + Tracks struct { + Data []ArtistTrackRelationship `json:"data"` + } `json:"tracks"` + } `json:"relationships,omitempty"` + + // Type The type of the resource + Type string `json:"type"` +} + +// ArtistAttributes defines model for ArtistAttributes. +type ArtistAttributes struct { + // Bio A short biography of the artist + Bio *string `json:"bio,omitempty"` + + // Name The name of the artist + Name *string `json:"name,omitempty"` +} + +// ArtistRole The role of an artist in a track or album +type ArtistRole string + +// ArtistTrackRelationship defines model for ArtistTrackRelationship. +type ArtistTrackRelationship struct { + Data ResourceObject `json:"data"` + Meta struct { + // Role The role of an artist in a track or album + Role ArtistRole `json:"role"` + } `json:"meta"` +} + +// ErrorList defines model for ErrorList. +type ErrorList struct { + Errors []ErrorObject `json:"errors"` +} + +// ErrorObject defines model for ErrorObject. +type ErrorObject struct { + Detail *string `json:"detail,omitempty"` + Id *string `json:"id,omitempty"` + Status *string `json:"status,omitempty"` + Title *string `json:"title,omitempty"` +} + +// PaginationLinks defines model for PaginationLinks. +type PaginationLinks struct { + First *string `json:"first,omitempty"` + Last *string `json:"last,omitempty"` + Next *string `json:"next,omitempty"` + Prev *string `json:"prev,omitempty"` +} + +// PaginationMeta defines model for PaginationMeta. +type PaginationMeta struct { + // CurrentPage The current page in the collection + CurrentPage *int32 `json:"currentPage,omitempty"` + + // TotalItems The total number of items in the collection + TotalItems *int32 `json:"totalItems,omitempty"` + + // TotalPages The total numeber of pages in the collection + TotalPages *int32 `json:"totalPages,omitempty"` +} + +// ResourceObject defines model for ResourceObject. +type ResourceObject struct { + // Id The unique identifier for the resource + Id string `json:"id"` + + // Type The type of the resource + Type string `json:"type"` +} + +// ServerInfo defines model for ServerInfo. +type ServerInfo struct { + // AuthRequired Whether the user has access to the server. + AuthRequired bool `json:"authRequired"` + + // Features A list of optional features the server supports. + Features []ServerInfoFeatures `json:"features"` + + // Server The name of the server software. + Server string `json:"server"` + + // ServerVersion The version number of the server. + ServerVersion string `json:"serverVersion"` +} + +// ServerInfoFeatures defines model for ServerInfo.Features. +type ServerInfoFeatures string + +// Track defines model for Track. +type Track struct { + Attributes *TrackAttributes `json:"attributes,omitempty"` + + // Id The unique identifier for the resource + Id string `json:"id"` + Relationships *struct { + Artists *[]TrackArtistRelationship `json:"artists,omitempty"` + } `json:"relationships,omitempty"` + + // Type The type of the resource + Type string `json:"type"` +} + +// TrackArtistRelationship defines model for TrackArtistRelationship. +type TrackArtistRelationship struct { + Data ResourceObject `json:"data"` + Meta struct { + // Role The role of an artist in a track or album + Role ArtistRole `json:"role"` + } `json:"meta"` +} + +// TrackAttributes defines model for TrackAttributes. +type TrackAttributes struct { + // Album The name of the album the track belongs to + Album string `json:"album"` + + // Artist The name of the artist who performed the track + Artist string `json:"artist"` + + // Composer The name of the composer who created the track + Composer *string `json:"composer,omitempty"` + + // Duration The duration of the track in seconds + Duration float32 `json:"duration"` + + // Title The title of the track + Title string `json:"title"` +} + +// FilterContains defines model for filterContains. +type FilterContains = []string + +// FilterEndsWith defines model for filterEndsWith. +type FilterEndsWith = []string + +// FilterEquals defines model for filterEquals. +type FilterEquals = []string + +// FilterGreaterOrEqual defines model for filterGreaterOrEqual. +type FilterGreaterOrEqual = []string + +// FilterGreaterThan defines model for filterGreaterThan. +type FilterGreaterThan = []string + +// FilterLessOrEqual defines model for filterLessOrEqual. +type FilterLessOrEqual = []string + +// FilterLessThan defines model for filterLessThan. +type FilterLessThan = []string + +// FilterStartsWith defines model for filterStartsWith. +type FilterStartsWith = []string + +// Include defines model for include. +type Include = string + +// PageLimit defines model for pageLimit. +type PageLimit = int32 + +// PageOffset defines model for pageOffset. +type PageOffset = int32 + +// Sort defines model for sort. +type Sort = string + +// GetArtistsParams defines parameters for GetArtists. +type GetArtistsParams struct { + // PageLimit The number of items per page + PageLimit *PageLimit `form:"page[limit],omitempty" json:"page[limit],omitempty"` + + // PageOffset The offset for pagination + PageOffset *PageOffset `form:"page[offset],omitempty" json:"page[offset],omitempty"` + + // FilterEquals Filter by any property with an exact match. Usage: filter[equals]=property:value + FilterEquals *FilterEquals `form:"filter[equals],omitempty" json:"filter[equals],omitempty"` + + // FilterContains Filter by any property containing text. Usage: filter[contains]=property:value + FilterContains *FilterContains `form:"filter[contains],omitempty" json:"filter[contains],omitempty"` + + // FilterLessThan Filter by any numeric property less than a value. Usage: filter[lessThan]=property:value + FilterLessThan *FilterLessThan `form:"filter[lessThan],omitempty" json:"filter[lessThan],omitempty"` + + // FilterLessOrEqual Filter by any numeric property less than or equal to a value. Usage: filter[lessOrEqual]=property:value + FilterLessOrEqual *FilterLessOrEqual `form:"filter[lessOrEqual],omitempty" json:"filter[lessOrEqual],omitempty"` + + // FilterGreaterThan Filter by any numeric property greater than a value. Usage: filter[greaterThan]=property:value + FilterGreaterThan *FilterGreaterThan `form:"filter[greaterThan],omitempty" json:"filter[greaterThan],omitempty"` + + // FilterGreaterOrEqual Filter by any numeric property greater than or equal to a value. Usage: filter[greaterOrEqual]=property:value + FilterGreaterOrEqual *FilterGreaterOrEqual `form:"filter[greaterOrEqual],omitempty" json:"filter[greaterOrEqual],omitempty"` + + // FilterStartsWith Filter by any property that starts with text. Usage: filter[startsWith]=property:value + FilterStartsWith *FilterStartsWith `form:"filter[startsWith],omitempty" json:"filter[startsWith],omitempty"` + + // FilterEndsWith Filter by any property that ends with text. Usage: filter[endsWith]=property:value + FilterEndsWith *FilterEndsWith `form:"filter[endsWith],omitempty" json:"filter[endsWith],omitempty"` + + // Sort Sort the results by one or more properties, separated by commas. Prefix the property with '-' for descending order. + Sort *Sort `form:"sort,omitempty" json:"sort,omitempty"` + + // Include Related resources to include in the response, separated by commas + Include *Include `form:"include,omitempty" json:"include,omitempty"` +} + +// GetArtistParams defines parameters for GetArtist. +type GetArtistParams struct { + // Include Related resources to include in the response, separated by commas + Include *Include `form:"include,omitempty" json:"include,omitempty"` +} + +// GetTracksParams defines parameters for GetTracks. +type GetTracksParams struct { + // PageLimit The number of items per page + PageLimit *PageLimit `form:"page[limit],omitempty" json:"page[limit],omitempty"` + + // PageOffset The offset for pagination + PageOffset *PageOffset `form:"page[offset],omitempty" json:"page[offset],omitempty"` + + // FilterEquals Filter by any property with an exact match. Usage: filter[equals]=property:value + FilterEquals *FilterEquals `form:"filter[equals],omitempty" json:"filter[equals],omitempty"` + + // FilterContains Filter by any property containing text. Usage: filter[contains]=property:value + FilterContains *FilterContains `form:"filter[contains],omitempty" json:"filter[contains],omitempty"` + + // FilterLessThan Filter by any numeric property less than a value. Usage: filter[lessThan]=property:value + FilterLessThan *FilterLessThan `form:"filter[lessThan],omitempty" json:"filter[lessThan],omitempty"` + + // FilterLessOrEqual Filter by any numeric property less than or equal to a value. Usage: filter[lessOrEqual]=property:value + FilterLessOrEqual *FilterLessOrEqual `form:"filter[lessOrEqual],omitempty" json:"filter[lessOrEqual],omitempty"` + + // FilterGreaterThan Filter by any numeric property greater than a value. Usage: filter[greaterThan]=property:value + FilterGreaterThan *FilterGreaterThan `form:"filter[greaterThan],omitempty" json:"filter[greaterThan],omitempty"` + + // FilterGreaterOrEqual Filter by any numeric property greater than or equal to a value. Usage: filter[greaterOrEqual]=property:value + FilterGreaterOrEqual *FilterGreaterOrEqual `form:"filter[greaterOrEqual],omitempty" json:"filter[greaterOrEqual],omitempty"` + + // FilterStartsWith Filter by any property that starts with text. Usage: filter[startsWith]=property:value + FilterStartsWith *FilterStartsWith `form:"filter[startsWith],omitempty" json:"filter[startsWith],omitempty"` + + // FilterEndsWith Filter by any property that ends with text. Usage: filter[endsWith]=property:value + FilterEndsWith *FilterEndsWith `form:"filter[endsWith],omitempty" json:"filter[endsWith],omitempty"` + + // Sort Sort the results by one or more properties, separated by commas. Prefix the property with '-' for descending order. + Sort *Sort `form:"sort,omitempty" json:"sort,omitempty"` + + // Include Related resources to include in the response, separated by commas + Include *Include `form:"include,omitempty" json:"include,omitempty"` +} + +// GetTrackParams defines parameters for GetTrack. +type GetTrackParams struct { + // Include Related resources to include in the response, separated by commas + Include *Include `form:"include,omitempty" json:"include,omitempty"` +} From ed87e703ffbfadeced8fcbbb5c5cc9ba994dd2df Mon Sep 17 00:00:00 2001 From: Deluan Date: Sat, 25 Mar 2023 23:00:08 -0400 Subject: [PATCH 02/22] Build collection Links --- server/api/api.go | 8 ++-- server/api/helpers.go | 35 ++++++++++++---- server/api/helpers_test.go | 86 +++++++++++++++++++++++++------------- 3 files changed, 90 insertions(+), 39 deletions(-) diff --git a/server/api/api.go b/server/api/api.go index f68f0ea8f..869dc5237 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -6,7 +6,6 @@ package api import ( "context" "net/http" - "net/url" middleware "github.com/deepmap/oapi-codegen/pkg/chi-middleware" "github.com/getkin/kin-openapi/openapi3" @@ -34,7 +33,10 @@ func New(ds model.DataStore) *Router { RequestErrorHandlerFunc: apiErrorHandler, ResponseErrorHandlerFunc: apiErrorHandler, }) - r.Handler = HandlerFromMux(handler, mux) + r.Handler = HandlerWithOptions(handler, ChiServerOptions{ + BaseRouter: mux, + Middlewares: []MiddlewareFunc{storeRequestInContext}, + }) return r } @@ -76,7 +78,7 @@ func (a *Router) GetTracks(ctx context.Context, request GetTracksRequestObject) if err != nil { return nil, err } - baseUrl, _ := url.JoinPath(spec.Servers[0].URL, "tracks") + baseUrl := baseResourceUrl(ctx, "tracks") links, meta := buildPaginationLinksAndMeta(int32(cnt), request.Params, baseUrl) return GetTracks200JSONResponse{ Data: toAPITracks(mfs), diff --git a/server/api/helpers.go b/server/api/helpers.go index c5ada9b82..5a524fb44 100644 --- a/server/api/helpers.go +++ b/server/api/helpers.go @@ -1,7 +1,9 @@ package api import ( + "context" "encoding/json" + "errors" "net/http" "net/url" "strconv" @@ -9,8 +11,21 @@ import ( "github.com/Masterminds/squirrel" "github.com/navidrome/navidrome/model" + "github.com/navidrome/navidrome/server" ) +type contextKey string + +const requestInContext contextKey = "request" + +// storeRequestInContext is a middleware function that adds the full request object to the context. +func storeRequestInContext(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), requestInContext, r) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + func toAPITrack(mf model.MediaFile) Track { return Track{ Type: "track", @@ -88,10 +103,10 @@ func toQueryOptions(params GetTracksParams) model.QueryOptions { func apiErrorHandler(w http.ResponseWriter, r *http.Request, err error) { var res ErrorObject - switch err { - case model.ErrNotAuthorized: + switch { + case errors.Is(err, model.ErrNotAuthorized): res = ErrorObject{Status: p(strconv.Itoa(http.StatusForbidden)), Title: p(http.StatusText(http.StatusForbidden))} - case model.ErrNotFound: + case errors.Is(err, model.ErrNotFound): res = ErrorObject{Status: p(strconv.Itoa(http.StatusNotFound)), Title: p(http.StatusText(http.StatusNotFound))} default: res = ErrorObject{Status: p(strconv.Itoa(http.StatusInternalServerError)), Title: p(http.StatusText(http.StatusInternalServerError))} @@ -99,7 +114,7 @@ func apiErrorHandler(w http.ResponseWriter, r *http.Request, err error) { w.Header().Set("Content-Type", "application/vnd.api+json") w.WriteHeader(403) - json.NewEncoder(w).Encode(ErrorList{[]ErrorObject{res}}) + _ = json.NewEncoder(w).Encode(ErrorList{[]ErrorObject{res}}) } func validationErrorHandler(w http.ResponseWriter, message string, statusCode int) { @@ -151,15 +166,15 @@ func buildPaginationLinksAndMeta(totalItems int32, params GetTracksParams, resou addFilterParams("filter[endsWith]", params.FilterEndsWith) if params.Sort != nil { - query.Add("sort", string(*params.Sort)) + query.Add("sort", *params.Sort) } if params.Include != nil { - query.Add("include", string(*params.Include)) + query.Add("include", *params.Include) } link := resourceName if len(query) > 0 { - link += "&" + query.Encode() + link += "?" + query.Encode() } return &link } @@ -191,3 +206,9 @@ func buildPaginationLinksAndMeta(totalItems int32, params GetTracksParams, resou return links, meta } + +func baseResourceUrl(ctx context.Context, resourceName string) string { + r := ctx.Value(requestInContext).(*http.Request) + baseUrl, _ := url.JoinPath(spec.Servers[0].URL, resourceName) + return server.AbsoluteURL(r, baseUrl, nil) +} diff --git a/server/api/helpers_test.go b/server/api/helpers_test.go index 9efe1a132..0b4a9e7d2 100644 --- a/server/api/helpers_test.go +++ b/server/api/helpers_test.go @@ -1,6 +1,8 @@ package api import ( + "net/url" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -29,9 +31,9 @@ var _ = Describe("BuildPaginationLinksAndMeta", func() { It("returns correct pagination links and meta", func() { links, meta := buildPaginationLinksAndMeta(totalItems, params, resourceName) - Expect(links.First).To(Equal(p("api/resource?page[offset]=0&page[limit]=10"))) - Expect(links.Last).To(Equal(p("api/resource?page[offset]=140&page[limit]=10"))) - Expect(links.Next).To(Equal(p("api/resource?page[offset]=10&page[limit]=10"))) + testLinkEquality(links.First, p("api/resource?page[offset]=0&page[limit]=10")) + testLinkEquality(links.Last, p("api/resource?page[offset]=140&page[limit]=10")) + testLinkEquality(links.Next, p("api/resource?page[offset]=10&page[limit]=10")) Expect(links.Prev).To(BeNil()) Expect(meta.CurrentPage).To(Equal(p(int32(1)))) @@ -51,10 +53,10 @@ var _ = Describe("BuildPaginationLinksAndMeta", func() { It("returns correct pagination links and meta", func() { links, meta := buildPaginationLinksAndMeta(totalItems, params, resourceName) - Expect(links.First).To(Equal(p("api/resource?page[offset]=0&page[limit]=20"))) - Expect(links.Last).To(Equal(p("api/resource?page[offset]=140&page[limit]=20"))) - Expect(links.Next).To(Equal(p("api/resource?page[offset]=60&page[limit]=20"))) - Expect(links.Prev).To(Equal(p("api/resource?page[offset]=20&page[limit]=20"))) + testLinkEquality(links.First, p("api/resource?page[offset]=0&page[limit]=20")) + testLinkEquality(links.Last, p("api/resource?page[offset]=140&page[limit]=20")) + testLinkEquality(links.Next, p("api/resource?page[offset]=60&page[limit]=20")) + testLinkEquality(links.Prev, p("api/resource?page[offset]=20&page[limit]=20")) Expect(meta.CurrentPage).To(Equal(p(int32(3)))) Expect(meta.TotalItems).To(Equal(p(int32(150)))) @@ -81,30 +83,56 @@ var _ = Describe("BuildPaginationLinksAndMeta", func() { It("returns correct pagination links with filter params", func() { links, _ := buildPaginationLinksAndMeta(totalItems, params, resourceName) - expectedLinkPrefix := "api/resource?" - expectedParams := []string{ - "page[offset]=0&page[limit]=20", - "filter[equals]=property1:value1&filter[equals]=property2:value2", - "filter[contains]=property3:value3", - "filter[lessThan]=property4:value4", - "filter[lessOrEqual]=property5:value5", - "filter[greaterThan]=property6:value6", - "filter[greaterOrEqual]=property7:value7", - "filter[startsWith]=property8:value8", - "filter[endsWith]=property9:value9", + validateLink := func(link *string, expectedOffset string) { + parsedLink, err := url.Parse(*link) + Expect(err).NotTo(HaveOccurred()) + + queryParams, _ := url.ParseQuery(parsedLink.RawQuery) + Expect(queryParams["page[offset]"]).To(ConsistOf(expectedOffset)) + Expect(queryParams["page[limit]"]).To(ConsistOf("20")) + + for _, param := range *params.FilterEquals { + Expect(queryParams["filter[equals]"]).To(ContainElements(param)) + } + for _, param := range *params.FilterContains { + Expect(queryParams["filter[contains]"]).To(ContainElement(param)) + } + for _, param := range *params.FilterLessThan { + Expect(queryParams["filter[lessThan]"]).To(ContainElement(param)) + } + for _, param := range *params.FilterLessOrEqual { + Expect(queryParams["filter[lessOrEqual]"]).To(ContainElement(param)) + } + for _, param := range *params.FilterGreaterThan { + Expect(queryParams["filter[greaterThan]"]).To(ContainElement(param)) + } + for _, param := range *params.FilterGreaterOrEqual { + Expect(queryParams["filter[greaterOrEqual]"]).To(ContainElement(param)) + } + for _, param := range *params.FilterStartsWith { + Expect(queryParams["filter[startsWith]"]).To(ContainElement(param)) + } + for _, param := range *params.FilterEndsWith { + Expect(queryParams["filter[endsWith]"]).To(ContainElement(param)) + } } - Expect(*links.First).To(HavePrefix(expectedLinkPrefix)) - Expect(*links.Last).To(HavePrefix(expectedLinkPrefix)) - Expect(*links.Next).To(HavePrefix(expectedLinkPrefix)) - Expect(*links.Prev).To(HavePrefix(expectedLinkPrefix)) - - for _, param := range expectedParams { - Expect(*links.First).To(ContainSubstring(param)) - Expect(*links.Last).To(ContainSubstring(param)) - Expect(*links.Next).To(ContainSubstring(param)) - Expect(*links.Prev).To(ContainSubstring(param)) - } + validateLink(links.First, "0") + validateLink(links.Last, "140") + validateLink(links.Next, "60") + validateLink(links.Prev, "20") }) }) }) + +func testLinkEquality(link1, link2 *string) { + parsedLink1, err := url.Parse(*link1) + Expect(err).NotTo(HaveOccurred()) + queryParams1, _ := url.ParseQuery(parsedLink1.RawQuery) + + parsedLink2, err := url.Parse(*link2) + Expect(err).NotTo(HaveOccurred()) + queryParams2, _ := url.ParseQuery(parsedLink2.RawQuery) + + Expect(queryParams1).To(Equal(queryParams2)) +} From d4c458d19324cd03ecb9533ca098271fff7278d4 Mon Sep 17 00:00:00 2001 From: Deluan Date: Sun, 26 Mar 2023 01:17:23 -0400 Subject: [PATCH 03/22] Add tests --- server/api/helpers_test.go | 22 ++++++++++++++++++++++ server/server_test.go | 4 ++++ 2 files changed, 26 insertions(+) diff --git a/server/api/helpers_test.go b/server/api/helpers_test.go index 0b4a9e7d2..cd8b56c49 100644 --- a/server/api/helpers_test.go +++ b/server/api/helpers_test.go @@ -1,6 +1,8 @@ package api import ( + "net/http" + "net/http/httptest" "net/url" . "github.com/onsi/ginkgo/v2" @@ -125,6 +127,26 @@ var _ = Describe("BuildPaginationLinksAndMeta", func() { }) }) +var _ = Describe("storeRequestInContext", func() { + var ( + nextHandler http.Handler + handler http.Handler + ) + + BeforeEach(func() { + nextHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + Expect(r.Context().Value(requestInContext).(*http.Request).URL).To(Equal(r.URL)) + }) + handler = storeRequestInContext(nextHandler) + }) + + It("adds the full request object to the context", func() { + req := httptest.NewRequest("GET", "http://example.com", nil) + resp := httptest.NewRecorder() + handler.ServeHTTP(resp, req) + }) +}) + func testLinkEquality(link1, link2 *string) { parsedLink1, err := url.Parse(*link1) Expect(err).NotTo(HaveOccurred()) diff --git a/server/server_test.go b/server/server_test.go index 61d2f78da..26155f3c0 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -5,11 +5,15 @@ import ( "net/url" "github.com/navidrome/navidrome/conf" + "github.com/navidrome/navidrome/conf/configtest" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) var _ = Describe("AbsoluteURL", func() { + BeforeEach(func() { + DeferCleanup(configtest.SetupConfig()) + }) When("BaseURL is empty", func() { BeforeEach(func() { conf.Server.BasePath = "" From 20a1e3160b2732c93f92d8bc0068192a22154522 Mon Sep 17 00:00:00 2001 From: Deluan Date: Wed, 29 Mar 2023 22:22:11 -0400 Subject: [PATCH 04/22] Add Album and relationships --- api/spec.yml | 194 +++++++++++++-- server/api/api.go | 10 + server/api/helpers.go | 36 ++- server/api/openapi_api.gen.go | 403 +++++++++++++++++++++++++++++--- server/api/openapi_types.gen.go | 146 ++++++++++-- 5 files changed, 716 insertions(+), 73 deletions(-) diff --git a/api/spec.yml b/api/spec.yml index 387a7d117..4e5f7548d 100644 --- a/api/spec.yml +++ b/api/spec.yml @@ -172,6 +172,75 @@ paths: $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' + /albums: + get: + summary: Retrieve a list of albums + operationId: getAlbums + parameters: + - $ref: '#/components/parameters/pageLimit' + - $ref: '#/components/parameters/pageOffset' + - $ref: '#/components/parameters/filterEquals' + - $ref: '#/components/parameters/filterContains' + - $ref: '#/components/parameters/filterLessThan' + - $ref: '#/components/parameters/filterLessOrEqual' + - $ref: '#/components/parameters/filterGreaterThan' + - $ref: '#/components/parameters/filterGreaterOrEqual' + - $ref: '#/components/parameters/filterStartsWith' + - $ref: '#/components/parameters/filterEndsWith' + - $ref: '#/components/parameters/sort' + - $ref: '#/components/parameters/include' + responses: + '200': + description: A list of albums + content: + application/vnd.api+json: + schema: + type: object + required: [data, links] + properties: + data: + type: array + items: + $ref: '#/components/schemas/Album' + links: + $ref: '#/components/schemas/PaginationLinks' + meta: + $ref: '#/components/schemas/PaginationMeta' + '400': + $ref: '#/components/responses/BadRequest' + '403': + $ref: '#/components/responses/NotAuthorized' + '500': + $ref: '#/components/responses/InternalServerError' + /albums/{albumId}: + get: + summary: Retrieve an individual album + operationId: getAlbum + parameters: + - $ref: '#/components/parameters/include' + - name: albumId + in: path + description: The unique identifier of the album + required: true + schema: + type: string + responses: + '200': + description: An album object + content: + application/vnd.api+json: + schema: + type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/Album' + '403': + $ref: '#/components/responses/NotAuthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' components: parameters: @@ -323,14 +392,21 @@ components: type: string description: The unique identifier for the resource type: - type: string - description: The type of the resource + $ref: '#/components/schemas/ResourceType' + ResourceType: + type: string + description: The type of the resource + enum: + - album + - artist + - track ResourceList: type: object properties: data: oneOf: - $ref: '#/components/schemas/Track' + - $ref: '#/components/schemas/Album' - $ref: '#/components/schemas/Artist' - type: array items: @@ -351,6 +427,7 @@ components: propertyName: type mapping: track: '#/components/schemas/Track' + album: '#/components/schemas/Album' artist: '#/components/schemas/Artist' Track: allOf: @@ -360,14 +437,7 @@ components: attributes: $ref: '#/components/schemas/TrackAttributes' relationships: - type: object - properties: - artists: - type: array - items: - $ref: '#/components/schemas/TrackArtistRelationship' - required: - - artist + $ref: '#/components/schemas/TrackRelationships' TrackAttributes: type: object properties: @@ -392,16 +462,24 @@ components: - artist - album - duration + TrackRelationships: + type: object + properties: + artists: + type: array + items: + $ref: '#/components/schemas/TrackArtistRelationship' + albums: + type: array + items: + $ref: '#/components/schemas/AlbumTrackRelationship' + required: + - artists TrackArtistRelationship: type: object properties: meta: - type: object - properties: - role: - $ref: '#/components/schemas/ArtistRole' - required: - - role + $ref: #/components/schemas/ArtistMetaObject data: $ref: '#/components/schemas/ResourceObject' required: @@ -410,6 +488,7 @@ components: ArtistRole: type: string enum: + - artist - albumArtist - composer - vocalist @@ -433,8 +512,15 @@ components: $ref: '#/components/schemas/ArtistTrackRelationship' required: - data - required: - - tracks + albums: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/ArtistAlbumRelationship' + required: + - data ArtistAttributes: type: object properties: @@ -444,6 +530,16 @@ components: bio: type: string description: A short biography of the artist + ArtistAlbumRelationship: + type: object + properties: + meta: + $ref: '#/components/schemas/ArtistMetaObject' + data: + $ref: '#/components/schemas/ResourceObject' + required: + - meta + - data ArtistTrackRelationship: type: object properties: @@ -459,6 +555,68 @@ components: required: - meta - data + ArtistMetaObject: + type: object + properties: + role: + $ref: '#/components/schemas/ArtistRole' + required: + - role + Album: + allOf: + - $ref: '#/components/schemas/ResourceObject' + - type: object + properties: + attributes: + $ref: '#/components/schemas/AlbumAttributes' + relationships: + type: object + properties: + artists: + type: array + items: + $ref: '#/components/schemas/AlbumArtistRelationship' + tracks: + type: array + items: + $ref: '#/components/schemas/AlbumTrackRelationship' + required: + - artists + - tracks + AlbumAttributes: + type: object + properties: + title: + type: string + description: The title of the album + releaseDate: + type: string + format: date + description: The release date of the album + genre: + type: string + description: The genre of the album + required: + - title + - releaseDate + - genre + AlbumArtistRelationship: + type: object + properties: + meta: + $ref: #/components/schemas/ArtistMetaObject + data: + $ref: '#/components/schemas/ResourceObject' + required: + - meta + - data + AlbumTrackRelationship: + type: object + properties: + data: + $ref: '#/components/schemas/ResourceObject' + required: + - data PaginationLinks: type: object properties: diff --git a/server/api/api.go b/server/api/api.go index 869dc5237..e29d16eed 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -47,6 +47,16 @@ type Router struct { ds model.DataStore } +func (a *Router) GetAlbums(ctx context.Context, request GetAlbumsRequestObject) (GetAlbumsResponseObject, error) { + //TODO implement me + panic("implement me") +} + +func (a *Router) GetAlbum(ctx context.Context, request GetAlbumRequestObject) (GetAlbumResponseObject, error) { + //TODO implement me + panic("implement me") +} + func (a *Router) GetArtists(ctx context.Context, request GetArtistsRequestObject) (GetArtistsResponseObject, error) { //TODO implement me panic("implement me") diff --git a/server/api/helpers.go b/server/api/helpers.go index 5a524fb44..5c2a3ae0b 100644 --- a/server/api/helpers.go +++ b/server/api/helpers.go @@ -28,7 +28,7 @@ func storeRequestInContext(next http.Handler) http.Handler { func toAPITrack(mf model.MediaFile) Track { return Track{ - Type: "track", + Type: ResourceTypeTrack, Id: mf.ID, Attributes: &TrackAttributes{ Album: mf.Album, @@ -49,6 +49,40 @@ func toAPITrack(mf model.MediaFile) Track { //TrackMbid: p(mf.MbzReleaseTrackID), //Year: p(mf.Year), }, + Relationships: &TrackRelationships{ + Albums: &[]AlbumTrackRelationship{toAlbumRelationship(mf)}, + Artists: trackArtistRelationships(mf), + }, + } +} + +func trackArtistRelationships(mf model.MediaFile) []TrackArtistRelationship { + var r []TrackArtistRelationship + if mf.AlbumArtistID != "" { + r = append(r, toArtistRelationship(mf.AlbumArtistID, ArtistRoleAlbumArtist)) + } + if mf.ArtistID != "" { + r = append(r, toArtistRelationship(mf.ArtistID, ArtistRoleArtist)) + } + return r +} + +func toArtistRelationship(id string, artist ArtistRole) TrackArtistRelationship { + return TrackArtistRelationship{ + Data: ResourceObject{ + Type: ResourceTypeArtist, + Id: id, + }, + Meta: struct{ Role ArtistRole }{Role: artist}, + } +} + +func toAlbumRelationship(mf model.MediaFile) AlbumTrackRelationship { + return AlbumTrackRelationship{ + Data: ResourceObject{ + Type: ResourceTypeAlbum, + Id: mf.AlbumID, + }, } } diff --git a/server/api/openapi_api.gen.go b/server/api/openapi_api.gen.go index e93502a35..ed12e05f4 100644 --- a/server/api/openapi_api.gen.go +++ b/server/api/openapi_api.gen.go @@ -22,6 +22,12 @@ import ( // ServerInterface represents all server handlers. type ServerInterface interface { + // Retrieve a list of albums + // (GET /albums) + GetAlbums(w http.ResponseWriter, r *http.Request, params GetAlbumsParams) + // Retrieve an individual album + // (GET /albums/{albumId}) + GetAlbum(w http.ResponseWriter, r *http.Request, albumId string, params GetAlbumParams) // Retrieve a list of artists // (GET /artists) GetArtists(w http.ResponseWriter, r *http.Request, params GetArtistsParams) @@ -48,6 +54,159 @@ type ServerInterfaceWrapper struct { type MiddlewareFunc func(http.Handler) http.Handler +// GetAlbums operation middleware +func (siw *ServerInterfaceWrapper) GetAlbums(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // Parameter object where we will unmarshal all parameters from the context + var params GetAlbumsParams + + // ------------- Optional query parameter "page[limit]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "page[limit]", r.URL.Query(), ¶ms.PageLimit) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "page[limit]", Err: err}) + return + } + + // ------------- Optional query parameter "page[offset]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "page[offset]", r.URL.Query(), ¶ms.PageOffset) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "page[offset]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[equals]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[equals]", r.URL.Query(), ¶ms.FilterEquals) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[equals]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[contains]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[contains]", r.URL.Query(), ¶ms.FilterContains) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[contains]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[lessThan]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[lessThan]", r.URL.Query(), ¶ms.FilterLessThan) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[lessThan]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[lessOrEqual]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[lessOrEqual]", r.URL.Query(), ¶ms.FilterLessOrEqual) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[lessOrEqual]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[greaterThan]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[greaterThan]", r.URL.Query(), ¶ms.FilterGreaterThan) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[greaterThan]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[greaterOrEqual]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[greaterOrEqual]", r.URL.Query(), ¶ms.FilterGreaterOrEqual) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[greaterOrEqual]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[startsWith]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[startsWith]", r.URL.Query(), ¶ms.FilterStartsWith) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[startsWith]", Err: err}) + return + } + + // ------------- Optional query parameter "filter[endsWith]" ------------- + + err = runtime.BindQueryParameter("form", true, false, "filter[endsWith]", r.URL.Query(), ¶ms.FilterEndsWith) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "filter[endsWith]", Err: err}) + return + } + + // ------------- Optional query parameter "sort" ------------- + + err = runtime.BindQueryParameter("form", true, false, "sort", r.URL.Query(), ¶ms.Sort) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "sort", Err: err}) + return + } + + // ------------- Optional query parameter "include" ------------- + + err = runtime.BindQueryParameter("form", true, false, "include", r.URL.Query(), ¶ms.Include) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "include", Err: err}) + return + } + + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetAlbums(w, r, params) + }) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +// GetAlbum operation middleware +func (siw *ServerInterfaceWrapper) GetAlbum(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var err error + + // ------------- Path parameter "albumId" ------------- + var albumId string + + err = runtime.BindStyledParameterWithLocation("simple", false, "albumId", runtime.ParamLocationPath, chi.URLParam(r, "albumId"), &albumId) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "albumId", Err: err}) + return + } + + // Parameter object where we will unmarshal all parameters from the context + var params GetAlbumParams + + // ------------- Optional query parameter "include" ------------- + + err = runtime.BindQueryParameter("form", true, false, "include", r.URL.Query(), ¶ms.Include) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "include", Err: err}) + return + } + + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetAlbum(w, r, albumId, params) + }) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // GetArtists operation middleware func (siw *ServerInterfaceWrapper) GetArtists(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -482,6 +641,12 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl ErrorHandlerFunc: options.ErrorHandlerFunc, } + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/albums", wrapper.GetAlbums) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/albums/{albumId}", wrapper.GetAlbum) + }) r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/artists", wrapper.GetArtists) }) @@ -509,6 +674,105 @@ type NotAuthorizedJSONResponse ErrorList type NotFoundJSONResponse ErrorList +type GetAlbumsRequestObject struct { + Params GetAlbumsParams +} + +type GetAlbumsResponseObject interface { + VisitGetAlbumsResponse(w http.ResponseWriter) error +} + +type GetAlbums200JSONResponse struct { + Data []Album `json:"data"` + Links PaginationLinks `json:"links"` + Meta *PaginationMeta `json:"meta,omitempty"` +} + +func (response GetAlbums200JSONResponse) VisitGetAlbumsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetAlbums400JSONResponse struct{ BadRequestJSONResponse } + +func (response GetAlbums400JSONResponse) VisitGetAlbumsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type GetAlbums403JSONResponse struct{ NotAuthorizedJSONResponse } + +func (response GetAlbums403JSONResponse) VisitGetAlbumsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(403) + + return json.NewEncoder(w).Encode(response) +} + +type GetAlbums500JSONResponse struct { + InternalServerErrorJSONResponse +} + +func (response GetAlbums500JSONResponse) VisitGetAlbumsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type GetAlbumRequestObject struct { + AlbumId string `json:"albumId"` + Params GetAlbumParams +} + +type GetAlbumResponseObject interface { + VisitGetAlbumResponse(w http.ResponseWriter) error +} + +type GetAlbum200JSONResponse struct { + Data Album `json:"data"` +} + +func (response GetAlbum200JSONResponse) VisitGetAlbumResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetAlbum403JSONResponse struct{ NotAuthorizedJSONResponse } + +func (response GetAlbum403JSONResponse) VisitGetAlbumResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(403) + + return json.NewEncoder(w).Encode(response) +} + +type GetAlbum404JSONResponse struct{ NotFoundJSONResponse } + +func (response GetAlbum404JSONResponse) VisitGetAlbumResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type GetAlbum500JSONResponse struct { + InternalServerErrorJSONResponse +} + +func (response GetAlbum500JSONResponse) VisitGetAlbumResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/vnd.api+json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type GetArtistsRequestObject struct { Params GetArtistsParams } @@ -747,6 +1011,12 @@ func (response GetTrack500JSONResponse) VisitGetTrackResponse(w http.ResponseWri // StrictServerInterface represents all server handlers. type StrictServerInterface interface { + // Retrieve a list of albums + // (GET /albums) + GetAlbums(ctx context.Context, request GetAlbumsRequestObject) (GetAlbumsResponseObject, error) + // Retrieve an individual album + // (GET /albums/{albumId}) + GetAlbum(ctx context.Context, request GetAlbumRequestObject) (GetAlbumResponseObject, error) // Retrieve a list of artists // (GET /artists) GetArtists(ctx context.Context, request GetArtistsRequestObject) (GetArtistsResponseObject, error) @@ -794,6 +1064,59 @@ type strictHandler struct { options StrictHTTPServerOptions } +// GetAlbums operation middleware +func (sh *strictHandler) GetAlbums(w http.ResponseWriter, r *http.Request, params GetAlbumsParams) { + var request GetAlbumsRequestObject + + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetAlbums(ctx, request.(GetAlbumsRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetAlbums") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetAlbumsResponseObject); ok { + if err := validResponse.VisitGetAlbumsResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + } +} + +// GetAlbum operation middleware +func (sh *strictHandler) GetAlbum(w http.ResponseWriter, r *http.Request, albumId string, params GetAlbumParams) { + var request GetAlbumRequestObject + + request.AlbumId = albumId + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetAlbum(ctx, request.(GetAlbumRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetAlbum") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetAlbumResponseObject); ok { + if err := validResponse.VisitGetAlbumResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("Unexpected response type: %T", response)) + } +} + // GetArtists operation middleware func (sh *strictHandler) GetArtists(w http.ResponseWriter, r *http.Request, params GetArtistsParams) { var request GetArtistsRequestObject @@ -927,44 +1250,48 @@ func (sh *strictHandler) GetTrack(w http.ResponseWriter, r *http.Request, trackI // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "Od/ebP8XAAD//1dw5/GYLgAA", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/api/openapi_types.gen.go b/server/api/openapi_types.gen.go index 6156f40d1..8b23ec8a3 100644 --- a/server/api/openapi_types.gen.go +++ b/server/api/openapi_types.gen.go @@ -3,11 +3,23 @@ // Code generated by github.com/deepmap/oapi-codegen version v1.12.5-0.20230314231417-0cfaaa77a7d2 DO NOT EDIT. package api +import ( + openapi_types "github.com/deepmap/oapi-codegen/pkg/types" +) + // Defines values for ArtistRole. const ( - AlbumArtist ArtistRole = "albumArtist" - Composer ArtistRole = "composer" - Vocalist ArtistRole = "vocalist" + ArtistRoleAlbumArtist ArtistRole = "albumArtist" + ArtistRoleArtist ArtistRole = "artist" + ArtistRoleComposer ArtistRole = "composer" + ArtistRoleVocalist ArtistRole = "vocalist" +) + +// Defines values for ResourceType. +const ( + ResourceTypeAlbum ResourceType = "album" + ResourceTypeArtist ResourceType = "artist" + ResourceTypeTrack ResourceType = "track" ) // Defines values for ServerInfoFeatures. @@ -17,6 +29,44 @@ const ( Images ServerInfoFeatures = "images" ) +// Album defines model for Album. +type Album struct { + Attributes *AlbumAttributes `json:"attributes,omitempty"` + + // Id The unique identifier for the resource + Id string `json:"id"` + Relationships *struct { + Artists []AlbumArtistRelationship `json:"artists"` + Tracks []AlbumTrackRelationship `json:"tracks"` + } `json:"relationships,omitempty"` + + // Type The type of the resource + Type ResourceType `json:"type"` +} + +// AlbumArtistRelationship defines model for AlbumArtistRelationship. +type AlbumArtistRelationship struct { + Data ResourceObject `json:"data"` + Meta interface{} `json:"meta"` +} + +// AlbumAttributes defines model for AlbumAttributes. +type AlbumAttributes struct { + // Genre The genre of the album + Genre string `json:"genre"` + + // ReleaseDate The release date of the album + ReleaseDate openapi_types.Date `json:"releaseDate"` + + // Title The title of the album + Title string `json:"title"` +} + +// AlbumTrackRelationship defines model for AlbumTrackRelationship. +type AlbumTrackRelationship struct { + Data ResourceObject `json:"data"` +} + // Artist defines model for Artist. type Artist struct { Attributes *ArtistAttributes `json:"attributes,omitempty"` @@ -24,13 +74,22 @@ type Artist struct { // Id The unique identifier for the resource Id string `json:"id"` Relationships *struct { - Tracks struct { + Albums *struct { + Data []ArtistAlbumRelationship `json:"data"` + } `json:"albums,omitempty"` + Tracks *struct { Data []ArtistTrackRelationship `json:"data"` - } `json:"tracks"` + } `json:"tracks,omitempty"` } `json:"relationships,omitempty"` // Type The type of the resource - Type string `json:"type"` + Type ResourceType `json:"type"` +} + +// ArtistAlbumRelationship defines model for ArtistAlbumRelationship. +type ArtistAlbumRelationship struct { + Data ResourceObject `json:"data"` + Meta ArtistMetaObject `json:"meta"` } // ArtistAttributes defines model for ArtistAttributes. @@ -42,6 +101,12 @@ type ArtistAttributes struct { Name *string `json:"name,omitempty"` } +// ArtistMetaObject defines model for ArtistMetaObject. +type ArtistMetaObject struct { + // Role The role of an artist in a track or album + Role ArtistRole `json:"role"` +} + // ArtistRole The role of an artist in a track or album type ArtistRole string @@ -93,9 +158,12 @@ type ResourceObject struct { Id string `json:"id"` // Type The type of the resource - Type string `json:"type"` + Type ResourceType `json:"type"` } +// ResourceType The type of the resource +type ResourceType string + // ServerInfo defines model for ServerInfo. type ServerInfo struct { // AuthRequired Whether the user has access to the server. @@ -119,22 +187,17 @@ type Track struct { Attributes *TrackAttributes `json:"attributes,omitempty"` // Id The unique identifier for the resource - Id string `json:"id"` - Relationships *struct { - Artists *[]TrackArtistRelationship `json:"artists,omitempty"` - } `json:"relationships,omitempty"` + Id string `json:"id"` + Relationships *TrackRelationships `json:"relationships,omitempty"` // Type The type of the resource - Type string `json:"type"` + Type ResourceType `json:"type"` } // TrackArtistRelationship defines model for TrackArtistRelationship. type TrackArtistRelationship struct { Data ResourceObject `json:"data"` - Meta struct { - // Role The role of an artist in a track or album - Role ArtistRole `json:"role"` - } `json:"meta"` + Meta interface{} `json:"meta"` } // TrackAttributes defines model for TrackAttributes. @@ -155,6 +218,12 @@ type TrackAttributes struct { Title string `json:"title"` } +// TrackRelationships defines model for TrackRelationships. +type TrackRelationships struct { + Albums *[]AlbumTrackRelationship `json:"albums,omitempty"` + Artists []TrackArtistRelationship `json:"artists"` +} + // FilterContains defines model for filterContains. type FilterContains = []string @@ -191,6 +260,51 @@ type PageOffset = int32 // Sort defines model for sort. type Sort = string +// GetAlbumsParams defines parameters for GetAlbums. +type GetAlbumsParams struct { + // PageLimit The number of items per page + PageLimit *PageLimit `form:"page[limit],omitempty" json:"page[limit],omitempty"` + + // PageOffset The offset for pagination + PageOffset *PageOffset `form:"page[offset],omitempty" json:"page[offset],omitempty"` + + // FilterEquals Filter by any property with an exact match. Usage: filter[equals]=property:value + FilterEquals *FilterEquals `form:"filter[equals],omitempty" json:"filter[equals],omitempty"` + + // FilterContains Filter by any property containing text. Usage: filter[contains]=property:value + FilterContains *FilterContains `form:"filter[contains],omitempty" json:"filter[contains],omitempty"` + + // FilterLessThan Filter by any numeric property less than a value. Usage: filter[lessThan]=property:value + FilterLessThan *FilterLessThan `form:"filter[lessThan],omitempty" json:"filter[lessThan],omitempty"` + + // FilterLessOrEqual Filter by any numeric property less than or equal to a value. Usage: filter[lessOrEqual]=property:value + FilterLessOrEqual *FilterLessOrEqual `form:"filter[lessOrEqual],omitempty" json:"filter[lessOrEqual],omitempty"` + + // FilterGreaterThan Filter by any numeric property greater than a value. Usage: filter[greaterThan]=property:value + FilterGreaterThan *FilterGreaterThan `form:"filter[greaterThan],omitempty" json:"filter[greaterThan],omitempty"` + + // FilterGreaterOrEqual Filter by any numeric property greater than or equal to a value. Usage: filter[greaterOrEqual]=property:value + FilterGreaterOrEqual *FilterGreaterOrEqual `form:"filter[greaterOrEqual],omitempty" json:"filter[greaterOrEqual],omitempty"` + + // FilterStartsWith Filter by any property that starts with text. Usage: filter[startsWith]=property:value + FilterStartsWith *FilterStartsWith `form:"filter[startsWith],omitempty" json:"filter[startsWith],omitempty"` + + // FilterEndsWith Filter by any property that ends with text. Usage: filter[endsWith]=property:value + FilterEndsWith *FilterEndsWith `form:"filter[endsWith],omitempty" json:"filter[endsWith],omitempty"` + + // Sort Sort the results by one or more properties, separated by commas. Prefix the property with '-' for descending order. + Sort *Sort `form:"sort,omitempty" json:"sort,omitempty"` + + // Include Related resources to include in the response, separated by commas + Include *Include `form:"include,omitempty" json:"include,omitempty"` +} + +// GetAlbumParams defines parameters for GetAlbum. +type GetAlbumParams struct { + // Include Related resources to include in the response, separated by commas + Include *Include `form:"include,omitempty" json:"include,omitempty"` +} + // GetArtistsParams defines parameters for GetArtists. type GetArtistsParams struct { // PageLimit The number of items per page From 141edb881ec453f6159c427de3435b0e20ca0326 Mon Sep 17 00:00:00 2001 From: Deluan Date: Fri, 31 Mar 2023 14:51:08 -0400 Subject: [PATCH 05/22] Add sort param --- persistence/sql_base_repository.go | 22 +---- server/api/api.go | 2 +- server/api/helpers.go | 46 +++++++++- server/api/helpers_test.go | 24 +++++ utils/strings.go | 17 ++++ utils/strings_test.go | 142 ++++++----------------------- 6 files changed, 117 insertions(+), 136 deletions(-) diff --git a/persistence/sql_base_repository.go b/persistence/sql_base_repository.go index d1ecf2d18..fa8938f45 100644 --- a/persistence/sql_base_repository.go +++ b/persistence/sql_base_repository.go @@ -13,6 +13,7 @@ import ( "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model/request" + "github.com/navidrome/navidrome/utils" ) type sqlRepository struct { @@ -78,9 +79,9 @@ func (r sqlRepository) buildSortOrder(sort, order string) string { } var newSort []string - parts := strings.FieldsFunc(sort, splitFunc(',')) + parts := strings.FieldsFunc(sort, utils.SplitFunc(',')) for _, p := range parts { - f := strings.FieldsFunc(p, splitFunc(' ')) + f := strings.FieldsFunc(p, utils.SplitFunc(' ')) newField := []string{f[0]} if len(f) == 1 { newField = append(newField, order) @@ -96,23 +97,6 @@ func (r sqlRepository) buildSortOrder(sort, order string) string { return strings.Join(newSort, ", ") } -func splitFunc(delimiter rune) func(c rune) bool { - open := 0 - return func(c rune) bool { - if c == '(' { - open++ - return false - } - if open > 0 { - if c == ')' { - open-- - } - return false - } - return c == delimiter - } -} - func (r sqlRepository) applyFilters(sq SelectBuilder, options ...model.QueryOptions) SelectBuilder { if len(options) > 0 && options[0].Filters != nil { sq = sq.Where(options[0].Filters) diff --git a/server/api/api.go b/server/api/api.go index e29d16eed..83fb3d82c 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -79,7 +79,7 @@ func (a *Router) GetServerInfo(_ context.Context, _ GetServerInfoRequestObject) } func (a *Router) GetTracks(ctx context.Context, request GetTracksRequestObject) (GetTracksResponseObject, error) { - options := toQueryOptions(request.Params) + options := toQueryOptions(ctx, request.Params) mfs, err := a.ds.MediaFile(ctx).GetAll(options) if err != nil { return nil, err diff --git a/server/api/helpers.go b/server/api/helpers.go index 5c2a3ae0b..feb0b4fc5 100644 --- a/server/api/helpers.go +++ b/server/api/helpers.go @@ -4,12 +4,15 @@ import ( "context" "encoding/json" "errors" + "fmt" "net/http" "net/url" + "regexp" "strconv" "strings" "github.com/Masterminds/squirrel" + "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/server" ) @@ -112,7 +115,7 @@ func v[T comparable](p *T) T { // toQueryOptions convert a params struct to a model.QueryOptions struct, to be used by the // GetAll and CountAll functions. It assumes all GetXxxxParams functions have the exact same structure. -func toQueryOptions(params GetTracksParams) model.QueryOptions { +func toQueryOptions(ctx context.Context, params GetTracksParams) model.QueryOptions { var filters squirrel.And parseFilter := func(fs *[]string, op func(f, v string) squirrel.Sqlizer) { if fs != nil { @@ -132,7 +135,46 @@ func toQueryOptions(params GetTracksParams) model.QueryOptions { parseFilter(params.FilterLessOrEqual, func(f, v string) squirrel.Sqlizer { return squirrel.LtOrEq{f: v} }) offset := v(params.PageOffset) limit := v(params.PageLimit) - return model.QueryOptions{Max: int(limit), Offset: int(offset), Filters: filters} + sort, err := toSortParams(params.Sort) + if err != nil { + log.Warn(ctx, "Ignoring invalid sort parameter", err) + } + return model.QueryOptions{Max: int(limit), Offset: int(offset), Filters: filters, Sort: sort} +} + +var validSortPattern = regexp.MustCompile(`[a-zA-Z0-9_\-]`) + +func toSortParams(sort *string) (string, error) { + if sort == nil || *sort == "" { + return "", nil + } + + // Split input by comma + inputCols := strings.Split(*sort, ",") + + var resultCols []string + + for _, col := range inputCols { + trimmedCol := strings.TrimSpace(col) + if trimmedCol == "" { + continue + } + + // Check for invalid prefix + if !validSortPattern.Match([]byte(string(trimmedCol[0]))) { + return "", errors.New("invalid sort parameter: " + trimmedCol) + } + + colName := strings.TrimSpace(trimmedCol[1:]) + // Check for descending order + if strings.HasPrefix(trimmedCol, "-") { + resultCols = append(resultCols, fmt.Sprintf("%s desc", colName)) + } else { + resultCols = append(resultCols, fmt.Sprintf("%s asc", trimmedCol)) + } + } + + return strings.Join(resultCols, ","), nil } func apiErrorHandler(w http.ResponseWriter, r *http.Request, err error) { diff --git a/server/api/helpers_test.go b/server/api/helpers_test.go index cd8b56c49..57afbe3bc 100644 --- a/server/api/helpers_test.go +++ b/server/api/helpers_test.go @@ -1,6 +1,7 @@ package api import ( + "errors" "net/http" "net/http/httptest" "net/url" @@ -127,6 +128,29 @@ var _ = Describe("BuildPaginationLinksAndMeta", func() { }) }) +var _ = Describe("toSortParams", func() { + DescribeTable("toSortParams", + func(sort string, expected string, expectedError error) { + order, err := toSortParams(&sort) + Expect(order).To(Equal(expected)) + if expectedError == nil { + Expect(err).To(BeNil()) + } else { + Expect(err).To(Equal(expectedError)) + } + }, + Entry("should handle nil input", "", "", nil), + Entry("should handle empty input", "", "", nil), + Entry("should handle single column input", "name", "name asc", nil), + Entry("should handle single column input with descending order", "-name", "name desc", nil), + Entry("should handle multiple columns input", "name,,date,", "name asc,date asc", nil), + Entry("should handle multiple columns input with mixed order and spaces", "name, -age", "name asc,age desc", nil), + Entry("should handle relationship columns", "-artist.name", "artist.name desc", nil), + Entry("should return an error for invalid input with invalid prefix", "+name", "", errors.New("invalid sort parameter: +name")), + Entry("should return an error for invalid prefix in any column", "name,*age", "", errors.New("invalid sort parameter: *age")), + ) +}) + var _ = Describe("storeRequestInContext", func() { var ( nextHandler http.Handler diff --git a/utils/strings.go b/utils/strings.go index 9fbe0d448..2753aeacf 100644 --- a/utils/strings.go +++ b/utils/strings.go @@ -57,3 +57,20 @@ func LongestCommonPrefix(list []string) string { } return list[0] } + +func SplitFunc(delimiter rune) func(c rune) bool { + open := 0 + return func(c rune) bool { + if c == '(' { + open++ + return false + } + if open > 0 { + if c == ')' { + open-- + } + return false + } + return c == delimiter + } +} diff --git a/utils/strings_test.go b/utils/strings_test.go index 0983faa29..706ae6843 100644 --- a/utils/strings_test.go +++ b/utils/strings_test.go @@ -1,6 +1,8 @@ package utils import ( + "strings" + "github.com/navidrome/navidrome/conf" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -57,123 +59,35 @@ var _ = Describe("Strings", func() { }) Describe("LongestCommonPrefix", func() { + var testPaths = []string{ + "/Music/iTunes 1/iTunes Media/Music/ABBA/Gold_ Greatest Hits/Dancing Queen.m4a", + "/Music/iTunes 1/iTunes Media/Music/ABBA/Gold_ Greatest Hits/Mamma Mia.m4a", + "/Music/iTunes 1/iTunes Media/Music/Bachman-Turner Overdrive/Gold/Down Down.m4a", + "/Music/iTunes 1/iTunes Media/Music/Bachman-Turner Overdrive/Gold/Hey You.m4a", + "/Music/iTunes 1/iTunes Media/Music/Bachman-Turner Overdrive/Gold/Hold Back The Water.m4a", + "/Music/iTunes 1/iTunes Media/Music/Compilations/Saturday Night Fever/01 Stayin' Alive.m4a", + "/Music/iTunes 1/iTunes Media/Music/Compilations/Saturday Night Fever/03 Night Fever.m4a", + "/Music/iTunes 1/iTunes Media/Music/Yes/Fragile/01 Roundabout.m4a", + } + It("finds the longest common prefix", func() { Expect(LongestCommonPrefix(testPaths)).To(Equal("/Music/iTunes 1/iTunes Media/Music/")) }) }) + Describe("SplitFunc", func() { + DescribeTable("when splitting strings with a delimiter", + func(delimiter rune, input string, expected []string) { + splitFunc := SplitFunc(delimiter) + actual := strings.FieldsFunc(input, splitFunc) + Expect(actual).To(Equal(expected)) + }, + Entry("should split strings without parentheses", ',', "name,age,email", []string{"name", "age", "email"}), + Entry("should not split strings within parentheses", ',', "name, substr(email, 0, 3), age", []string{"name", " substr(email, 0, 3)", " age"}), + Entry("should handle multiple delimiters outside parentheses", ';', "name;age;email", []string{"name", "age", "email"}), + Entry("should return the whole input as a single element if the delimiter is not found", ';', "name,age,email", []string{"name,age,email"}), + Entry("should handle empty input", ',', "", []string{}), + Entry("should handle input with only delimiters", ',', ",,,", []string{}), + ) + }) }) - -var testPaths = []string{ - "/Music/iTunes 1/iTunes Media/Music/ABBA/Gold_ Greatest Hits/Dancing Queen.m4a", - "/Music/iTunes 1/iTunes Media/Music/ABBA/Gold_ Greatest Hits/Mamma Mia.m4a", - "/Music/iTunes 1/iTunes Media/Music/Art Blakey/A Night At Birdland, Vol. 1/01 Annoucement By Pee Wee Marquette.m4a", - "/Music/iTunes 1/iTunes Media/Music/Art Blakey/A Night At Birdland, Vol. 1/02 Split Kick.m4a", - "/Music/iTunes 1/iTunes Media/Music/As Frenéticas/As Frenéticas/Perigosa.m4a", - "/Music/iTunes 1/iTunes Media/Music/Bachman-Turner Overdrive/Gold/Down Down.m4a", - "/Music/iTunes 1/iTunes Media/Music/Bachman-Turner Overdrive/Gold/Hey You.m4a", - "/Music/iTunes 1/iTunes Media/Music/Bachman-Turner Overdrive/Gold/Hold Back The Water.m4a", - "/Music/iTunes 1/iTunes Media/Music/Belle And Sebastian/Write About Love/01 I Didn't See It Coming.m4a", - "/Music/iTunes 1/iTunes Media/Music/Belle And Sebastian/Write About Love/02 Come On Sister.m4a", - "/Music/iTunes 1/iTunes Media/Music/Black Eyed Peas/Elephunk/03 Let's Get Retarded.m4a", - "/Music/iTunes 1/iTunes Media/Music/Black Eyed Peas/Elephunk/04 Hey Mama.m4a", - "/Music/iTunes 1/iTunes Media/Music/Black Eyed Peas/Monkey Business/10 They Don't Want Music (Feat. James Brown).m4a", - "/Music/iTunes 1/iTunes Media/Music/Black Eyed Peas/The E.N.D/1-01 Boom Boom Pow.m4a", - "/Music/iTunes 1/iTunes Media/Music/Black Eyed Peas/Timeless/01 Mas Que Nada.m4a", - "/Music/iTunes 1/iTunes Media/Music/Blondie/Heart Of Glass/Heart Of Glass.m4a", - "/Music/iTunes 1/iTunes Media/Music/Bob Dylan/Nashville Skyline/06 Lay Lady Lay.m4a", - "/Music/iTunes 1/iTunes Media/Music/Botany/Feeling Today - EP/03 Waterparker.m4a", - "/Music/iTunes 1/iTunes Media/Music/Céu/CéU/06 10 Contados.m4a", - "/Music/iTunes 1/iTunes Media/Music/Chance/Six Through Ten/03 Forgive+Forget.m4a", - "/Music/iTunes 1/iTunes Media/Music/Clive Tanaka Y Su Orquesta/Jet Set Siempre 1°/03 Neu Chicago (Side A) [For Dance].m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Absolute Rock Classics/1-02 Smoke on the water.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Almost Famous Soundtrack/10 Simple Man.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Audio News - Rock'n' Roll Forever/01 Rock Around The Clock.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Austin Powers_ International Man Of Mystery/01 The Magic Piper (Of Love).m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Austin Powers_ The Spy Who Shagged Me/04 American Woman.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Back To Dance/03 Long Cool Woman In A Black Dress.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Back To The 70's - O Album Da Década/03 American Pie.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Bambolê/09 In The Mood.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Bambolê - Volume II/03 Blue Moon.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Big Brother Brasil 2004/04 I Will Survive.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Collateral Soundtrack/03 Hands Of Time.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Forrest Gump - The Soundtrack/1-12 California Dreamin'.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Forrest Gump - The Soundtrack/1-16 Mrs. Robinson.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Ghost World - Original Motion Picture Soundtrack/01 Jaan Pechechaan Ho.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Grease [Original Soundtrack]/01 Grease.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/La Bamba/09 Summertime Blues.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Pretty Woman/10 Oh Pretty Woman.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents African Groove/01 Saye Mogo Bana.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents Arabic Groove/02 Galbi.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents Asian Groove/03 Remember Tomorrow.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents Blues Lounge/01 Midnight Dream.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents Blues Lounge/03 Banal Reality.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents Blues Lounge/04 Parchman Blues.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents Blues Lounge/06 Run On.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents Brazilian Groove/01 Maria Moita.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents Brazilian Lounge/08 E Depois....m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents Brazilian Lounge/11 Os Grilos.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents Euro Lounge/01 Un Simple Histoire.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents Euro Lounge/02 Limbe.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents Euro Lounge/05 Sempre Di Domenica.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents Euro Lounge/12 Voulez-Vous_.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents World Lounge/03 Santa Maria.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents_ A New Groove/02 Dirty Laundry.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents_ Blues Around the World/02 Canceriano Sem Lar (Clinica Tobias Blues).m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents_ Euro Groove/03 Check In.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Putumayo Presents_ World Groove/01 Attention.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Saturday Night Fever/01 Stayin' Alive.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/Saturday Night Fever/03 Night Fever.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/The Best Air Guitar Album In The World... Ever!/2-06 Johnny B. Goode.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/The Full Monty - Soundtrack/02 You Sexy Thing.m4a", - "/Music/iTunes 1/iTunes Media/Music/Compilations/The Full Monty - Soundtrack/11 We Are Family.m4a", - "/Music/iTunes 1/iTunes Media/Music/Cut Copy/Zonoscope (Bonus Version)/10 Corner of the Sky.m4a", - "/Music/iTunes 1/iTunes Media/Music/David Bowie/Changesbowie/07 Diamond Dogs.m4a", - "/Music/iTunes 1/iTunes Media/Music/Douster & Savage Skulls/Get Rich or High Tryin' - EP/01 Bad Gal.m4a", - "/Music/iTunes 1/iTunes Media/Music/Elton John/Greatest Hits 1970-2002/1-04 Rocket Man (I Think It's Going to Be a Long, Long Time).m4a", - "/Music/iTunes 1/iTunes Media/Music/Elvis Presley/ELV1S 30 #1 Hits/02 Don't Be Cruel.m4a", - "/Music/iTunes 1/iTunes Media/Music/Eric Clapton/The Cream Of Clapton/03 I Feel Free.m4a", - "/Music/iTunes 1/iTunes Media/Music/Fleetwood Mac/The Very Best Of Fleetwood Mac/02 Don't Stop.m4a", - "/Music/iTunes 1/iTunes Media/Music/Françoise Hardy/Comment te dire adieu/Comment te dire adieu.m4a", - "/Music/iTunes 1/iTunes Media/Music/Games/That We Can Play - EP/01 Strawberry Skies.m4a", - "/Music/iTunes 1/iTunes Media/Music/Grand Funk Railroad/Collectors Series/The Loco-Motion.m4a", - "/Music/iTunes 1/iTunes Media/Music/Henry Mancini/The Pink Panther (Music from the Film Score)/The Pink Panther Theme.m4a", - "/Music/iTunes 1/iTunes Media/Music/Holy Ghost!/Do It Again - Single/01 Do It Again.m4a", - "/Music/iTunes 1/iTunes Media/Music/K.C. & The Sunshine Band/The Best of/03 I'm Your Boogie Man.m4a", - "/Music/iTunes 1/iTunes Media/Music/K.C. & The Sunshine Band/Unknown Album/Megamix (Thats The Way, Shake Your Booty, Get Down Tonight, Give It Up).m4a", - "/Music/iTunes 1/iTunes Media/Music/Kim Ann Foxman & Andy Butler/Creature - EP/01 Creature.m4a", - "/Music/iTunes 1/iTunes Media/Music/Nico/Chelsea Girl/01 The Fairest Of The Seasons.m4a", - "/Music/iTunes 1/iTunes Media/Music/oOoOO/oOoOO - EP/02 Burnout Eyess.m4a", - "/Music/iTunes 1/iTunes Media/Music/Peter Frampton/The Very Best of Peter Frampton/Baby, I Love Your Way.m4a", - "/Music/iTunes 1/iTunes Media/Music/Peter Frampton/The Very Best of Peter Frampton/Show Me The Way.m4a", - "/Music/iTunes 1/iTunes Media/Music/Raul Seixas/A Arte De Raul Seixas/03 Metamorfose Ambulante.m4a", - "/Music/iTunes 1/iTunes Media/Music/Raul Seixas/A Arte De Raul Seixas/18 Eu Nasci há 10 Mil Anos Atrás.m4a", - "/Music/iTunes 1/iTunes Media/Music/Rick James/Street Songs/Super Freak.m4a", - "/Music/iTunes 1/iTunes Media/Music/Rita Lee/Fruto Proibido/Agora Só Falta Você.m4a", - "/Music/iTunes 1/iTunes Media/Music/Rita Lee/Fruto Proibido/Esse Tal De Roque Enrow.m4a", - "/Music/iTunes 1/iTunes Media/Music/Roberto Carlos/Roberto Carlos 1966/05 Negro Gato.m4a", - "/Music/iTunes 1/iTunes Media/Music/SOHO/Goddess/02 Hippychick.m4a", - "/Music/iTunes 1/iTunes Media/Music/Stan Getz/Getz_Gilberto/05 Corcovado (Quiet Nights of Quiet Stars).m4a", - "/Music/iTunes 1/iTunes Media/Music/Steely Dan/Pretzel Logic/Rikki Don't Loose That Number.m4a", - "/Music/iTunes 1/iTunes Media/Music/Stevie Wonder/For Once In My Life/I Don't Know Why.m4a", - "/Music/iTunes 1/iTunes Media/Music/Teebs/Ardour/While You Doooo.m4a", - "/Music/iTunes 1/iTunes Media/Music/The Beatles/Magical Mystery Tour/08 Strawberry Fields Forever.m4a", - "/Music/iTunes 1/iTunes Media/Music/The Beatles/Past Masters, Vol. 1/10 Long Tall Sally.m4a", - "/Music/iTunes 1/iTunes Media/Music/The Beatles/Please Please Me/14 Twist And Shout.m4a", - "/Music/iTunes 1/iTunes Media/Music/The Beatles/Sgt. Pepper's Lonely Hearts Club Band/03 Lucy In The Sky With Diamonds.m4a", - "/Music/iTunes 1/iTunes Media/Music/The Black Crowes/Amorica/09 Wiser Time.m4a", - "/Music/iTunes 1/iTunes Media/Music/The Black Crowes/By Your Side/05 Only A Fool.m4a", - "/Music/iTunes 1/iTunes Media/Music/The Black Crowes/Shake Your Money Maker/04 Could I''ve Been So Blind.m4a", - "/Music/iTunes 1/iTunes Media/Music/The Black Crowes/The Southern Harmony And Musical Companion/01 Sting Me.m4a", - "/Music/iTunes 1/iTunes Media/Music/The Black Crowes/Three Snakes And One Charm/02 Good Friday.m4a", - "/Music/iTunes 1/iTunes Media/Music/The Doors/Strange Days (40th Anniversary Mixes)/01 Strange Days.m4a", - "/Music/iTunes 1/iTunes Media/Music/The Rolling Stones/Forty Licks/1-03 (I Can't Get No) Satisfaction.m4a", - "/Music/iTunes 1/iTunes Media/Music/The Velvet Underground/The Velvet Underground & Nico/02 I'm Waiting For The Man.m4a", - "/Music/iTunes 1/iTunes Media/Music/The Velvet Underground/The Velvet Underground & Nico/03 Femme Fatale.m4a", - "/Music/iTunes 1/iTunes Media/Music/The Velvet Underground/White Light_White Heat/04 Here She Comes Now.m4a", - "/Music/iTunes 1/iTunes Media/Music/The Who/Sings My Generation/My Generation.m4a", - "/Music/iTunes 1/iTunes Media/Music/Village People/The Very Best Of Village People/Macho Man.m4a", - "/Music/iTunes 1/iTunes Media/Music/Vondelpark/Sauna - EP/01 California Analog Dream.m4a", - "/Music/iTunes 1/iTunes Media/Music/War/Why Can't We Be Friends/Low Rider.m4a", - "/Music/iTunes 1/iTunes Media/Music/Yes/Fragile/01 Roundabout.m4a", -} From 93081273429812494bbb00c8be278573d1a731c9 Mon Sep 17 00:00:00 2001 From: Deluan Date: Fri, 31 Mar 2023 15:05:39 -0400 Subject: [PATCH 06/22] Add more tracks attributes --- api/spec.yml | 52 ++++++-- api/spec1.yml | 202 -------------------------------- server/api/helpers.go | 34 +++--- server/api/openapi_api.gen.go | 88 +++++++------- server/api/openapi_types.gen.go | 42 ++++++- 5 files changed, 142 insertions(+), 276 deletions(-) delete mode 100644 api/spec1.yml diff --git a/api/spec.yml b/api/spec.yml index 4e5f7548d..842e358c0 100644 --- a/api/spec.yml +++ b/api/spec.yml @@ -440,28 +440,60 @@ components: $ref: '#/components/schemas/TrackRelationships' TrackAttributes: type: object + required: [title, artist, album, albumartist, track, mimetype, duration, channels, bitrate, size] properties: title: type: string description: The title of the track - artist: + artist: # TODO: Remove type: string description: The name of the artist who performed the track - album: + albumartist: # TODO: Remove + type: string + description: The primary artist of the album the track belongs to. + album: # TODO Remove type: string description: The name of the album the track belongs to - composer: + genre: # TODO Remove type: string - description: The name of the composer who created the track + description: The genre of the track. + track: + type: integer + description: The track number within the album. + disc: + type: integer + description: The disc number within a multi-disc album. + year: + type: integer + description: The release year of the track or album. + bpm: + type: integer + description: The beats per minute (BPM) of the track. + recording-mbid: + type: string + description: The MusicBrainz identifier for the recording of the track. + track-mbid: + type: string + description: The MusicBrainz identifier for the track. + comments: + type: string + description: Any additional comments or notes about the track. + mimetype: + type: string + description: The MIME type of the audio file. duration: type: number format: float description: The duration of the track in seconds - required: - - title - - artist - - album - - duration + channels: + type: integer + description: The number of audio channels in the track. + bitrate: + type: integer + description: The bitrate of the audio file in kilobits per second (kbps). + size: + type: integer + description: The size of the audio file in bytes. TrackRelationships: type: object properties: @@ -490,8 +522,6 @@ components: enum: - artist - albumArtist - - composer - - vocalist description: The role of an artist in a track or album Artist: allOf: diff --git a/api/spec1.yml b/api/spec1.yml deleted file mode 100644 index fbcc853b5..000000000 --- a/api/spec1.yml +++ /dev/null @@ -1,202 +0,0 @@ -openapi: "3.0.0" -info: - version: 0.3.0 - title: Navidrome API - description: 'This is heavily based on Aura API: https://auraspec.readthedocs.io/en/latest/api.html' - contact: - name: Navidrome - url: https://navidrome.org -servers: - - url: /api/v2 -security: - - bearerAuth: [] -paths: - /server: - get: - operationId: getServerInfo - responses: - '200': - description: The response’s data key maps to a resource object dictionary representing the server. - content: - application/vnd.api+json: - schema: - type: object - required: [data] - properties: - data: - $ref: '#/components/schemas/ServerInfo' - default: - description: unexpected error - content: - application/vnd.api+json: - schema: - $ref: '#/components/schemas/JSONAPIErrorResponse' - /tracks: - get: - operationId: getTracks - parameters: - - in: query - name: limit - schema: - type: integer - minimum: 0 - default: 0 - responses: - '200': - description: TODO - content: - application/vnd.api+json: - schema: - type: object - required: [data] - properties: - data: - type: array - items: - $ref: '#/components/schemas/Track' - default: - description: unexpected error - content: - application/vnd.api+json: - schema: - $ref: '#/components/schemas/JSONAPIErrorResponse' - -components: - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT - - schemas: - ServerInfo: - type: object - required: [aura-version, server, server-version, auth-required, features] - properties: - aura-version: - type: string - description: The version of the AURA spec implemented. - example: "1.0.0" - server: - type: string - description: The name of the server software. - example: "navidrome" - server-version: - type: string - description: The version number of the server. - example: "0.60.0" - auth-required: - type: boolean - description: Whether the user has access to the server. - example: true - features: - type: array - description: A list of optional features the server supports. - items: - type: string - enum: - - albums - - artists - - images - JSONAPIResourceObject: - title: JSON:API Resource Object - description: | - A JSON:API-compliant resource object. An inheritable object schema that contains the basic framework of a Resource Object from the JSON:API spec. - The `attributes`, `relationship`, and `meta` properties are added, as necessary, by the inheriting schema. - type: object - required: [type, id] - properties: - type: - $ref: '#/components/schemas/JSONAPIType' - id: - $ref: '#/components/schemas/JSONAPIId' - - JSONAPIType: - title: JSON:API type Member - description: | - The `type` member is used to describe resource objects that share common attributes and relationships. - Note: The type member is required in every resource object throughout requests and responses in JSON:API. There are some cases, such as when POSTing to an endpoint representing heterogeneous data, when the type could not be inferred from the endpoint. However, picking and choosing when it is required would be confusing; it would be hard to remember when it was required and when it was not. Therefore, to improve consistency and minimize confusion, type is always required. - type: string - - JSONAPIId: - title: JSON:API id Member - description: Every resource object MUST contain an id member and a type member (exception-the id member is not required when the resource object originates at the client and represents a new resource to be created on the server). Within a given API, each resource object’s type and id pair MUST identify a single, unique resource. - type: string - - Track: - allOf: - - $ref: '#/components/schemas/JSONAPIResourceObject' - - type: object - required: [attributes] - properties: - attributes: - $ref: '#/components/schemas/TrackAttributes' - - TrackAttributes: - type: object - required: [title, artist, album, albumartist, track, mimetype, duration, channels, bitrate, size] - properties: - title: - type: string - artist: - type: string - album: - type: string - albumartist: - type: string - track: - type: integer - disc: - type: integer - year: - type: integer - bpm: - type: integer - genre: - type: string - recording-mbid: - type: string - track-mbid: - type: string - comments: - type: string - mimetype: - type: string - duration: - type: number - channels: - type: integer - bitrate: - type: integer - size: - type: integer - - JSONAPIErrorResponse: - title: JSON:API Resource Object - type: object - required: [errors] - properties: - errors: - type: array - items: - $ref: '#/components/schemas/JSONAPIErrorObject' - JSONAPIErrorObject: - type: object - description: 'A JSON-API error object. see: https://jsonapi.org/format/#errors' - properties: - id: - type: string - description: a unique identifier for this particular occurrence of the problem. - example: 1 - status: - type: integer - description: the HTTP status code applicable to this problem, expressed as a string value. - example: 400 - code: - type: string - description: an application-specific error code, expressed as a string value. - example: invalid_request - detail: - type: string - description: a human-readable explanation specific to this occurrence of the problem. - example: First name must contain at least two characters. diff --git a/server/api/helpers.go b/server/api/helpers.go index feb0b4fc5..36d1bf078 100644 --- a/server/api/helpers.go +++ b/server/api/helpers.go @@ -34,23 +34,23 @@ func toAPITrack(mf model.MediaFile) Track { Type: ResourceTypeTrack, Id: mf.ID, Attributes: &TrackAttributes{ - Album: mf.Album, - //Albumartist: mf.AlbumArtist, - Artist: mf.Artist, - //Bitrate: mf.BitRate, - //Bpm: p(mf.Bpm), - //Channels: mf.Channels, - //Comments: p(mf.Comment), - //Disc: p(mf.DiscNumber), - Duration: mf.Duration, - //Genre: p(mf.Genre), - //Mimetype: mf.ContentType(), - //RecordingMbid: p(mf.MbzTrackID), - //Size: int(mf.Size), - Title: mf.Title, - //Track: mf.TrackNumber, - //TrackMbid: p(mf.MbzReleaseTrackID), - //Year: p(mf.Year), + Album: mf.Album, + Albumartist: mf.AlbumArtist, + Artist: mf.Artist, + Bitrate: mf.BitRate, + Bpm: p(mf.Bpm), + Channels: mf.Channels, + Comments: p(mf.Comment), + Disc: p(mf.DiscNumber), + Duration: mf.Duration, + Genre: p(mf.Genre), + Mimetype: mf.ContentType(), + RecordingMbid: p(mf.MbzTrackID), + Size: int(mf.Size), + Title: mf.Title, + Track: mf.TrackNumber, + TrackMbid: p(mf.MbzReleaseTrackID), + Year: p(mf.Year), }, Relationships: &TrackRelationships{ Albums: &[]AlbumTrackRelationship{toAlbumRelationship(mf)}, diff --git a/server/api/openapi_api.gen.go b/server/api/openapi_api.gen.go index ed12e05f4..7cb3fa302 100644 --- a/server/api/openapi_api.gen.go +++ b/server/api/openapi_api.gen.go @@ -1250,48 +1250,52 @@ func (sh *strictHandler) GetTrack(w http.ResponseWriter, r *http.Request, trackI // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/api/openapi_types.gen.go b/server/api/openapi_types.gen.go index 8b23ec8a3..07e035303 100644 --- a/server/api/openapi_types.gen.go +++ b/server/api/openapi_types.gen.go @@ -11,8 +11,6 @@ import ( const ( ArtistRoleAlbumArtist ArtistRole = "albumArtist" ArtistRoleArtist ArtistRole = "artist" - ArtistRoleComposer ArtistRole = "composer" - ArtistRoleVocalist ArtistRole = "vocalist" ) // Defines values for ResourceType. @@ -205,17 +203,53 @@ type TrackAttributes struct { // Album The name of the album the track belongs to Album string `json:"album"` + // Albumartist The primary artist of the album the track belongs to. + Albumartist string `json:"albumartist"` + // Artist The name of the artist who performed the track Artist string `json:"artist"` - // Composer The name of the composer who created the track - Composer *string `json:"composer,omitempty"` + // Bitrate The bitrate of the audio file in kilobits per second (kbps). + Bitrate int `json:"bitrate"` + + // Bpm The beats per minute (BPM) of the track. + Bpm *int `json:"bpm,omitempty"` + + // Channels The number of audio channels in the track. + Channels int `json:"channels"` + + // Comments Any additional comments or notes about the track. + Comments *string `json:"comments,omitempty"` + + // Disc The disc number within a multi-disc album. + Disc *int `json:"disc,omitempty"` // Duration The duration of the track in seconds Duration float32 `json:"duration"` + // Genre The genre of the track. + Genre *string `json:"genre,omitempty"` + + // Mimetype The MIME type of the audio file. + Mimetype string `json:"mimetype"` + + // RecordingMbid The MusicBrainz identifier for the recording of the track. + RecordingMbid *string `json:"recording-mbid,omitempty"` + + // Size The size of the audio file in bytes. + Size int `json:"size"` + // Title The title of the track Title string `json:"title"` + + // Track The track number within the album. + Track int `json:"track"` + + // TrackMbid The MusicBrainz identifier for the track. + TrackMbid *string `json:"track-mbid,omitempty"` + + // Year The release year of the track or album. + Year *int `json:"year,omitempty"` } // TrackRelationships defines model for TrackRelationships. From be945e010aca851a3e1a7fcaeb831d5f15a5a520 Mon Sep 17 00:00:00 2001 From: Deluan Date: Sun, 9 Apr 2023 17:29:54 -0400 Subject: [PATCH 07/22] go mod tidy --- go.mod | 50 ++++++++++++++++++++++++++---------------- go.sum | 69 +++++++++++++++++++++++++++++++++++++--------------------- 2 files changed, 75 insertions(+), 44 deletions(-) diff --git a/go.mod b/go.mod index e8f77756d..164aa4343 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/ReneKroon/ttlcache/v2 v2.11.0 github.com/beego/beego/v2 v2.0.7 github.com/bradleyjkemp/cupaloy/v2 v2.8.0 + github.com/deepmap/oapi-codegen v1.12.4 github.com/deluan/rest v0.0.0-20211101235434-380523c4bb47 github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086 @@ -17,9 +18,10 @@ require ( github.com/djherbis/stream v1.4.0 github.com/dustin/go-humanize v1.0.1 github.com/fatih/structs v1.1.0 + github.com/getkin/kin-openapi v0.115.0 github.com/go-chi/chi/v5 v5.0.8 github.com/go-chi/cors v1.2.1 - github.com/go-chi/httprate v0.7.4 + github.com/go-chi/httprate v0.7.1 github.com/go-chi/jwtauth/v5 v5.1.0 github.com/google/uuid v1.3.0 github.com/google/wire v0.5.0 @@ -29,45 +31,51 @@ require ( github.com/matoous/go-nanoid/v2 v2.0.0 github.com/mattn/go-sqlite3 v1.14.16 github.com/mattn/go-zglob v0.0.3 - github.com/microcosm-cc/bluemonday v1.0.24 - github.com/mileusna/useragent v1.3.2 - github.com/onsi/ginkgo/v2 v2.9.5 - github.com/onsi/gomega v1.27.7 - github.com/pressly/goose/v3 v3.11.2 - github.com/prometheus/client_golang v1.15.1 + github.com/microcosm-cc/bluemonday v1.0.23 + github.com/mileusna/useragent v1.2.1 + github.com/onsi/ginkgo/v2 v2.9.2 + github.com/onsi/gomega v1.27.6 + github.com/pressly/goose/v3 v3.10.0 + github.com/prometheus/client_golang v1.14.0 github.com/robfig/cron/v3 v3.0.1 - github.com/sirupsen/logrus v1.9.2 + github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.7.0 github.com/spf13/viper v1.15.0 - github.com/stretchr/testify v1.8.3 + github.com/stretchr/testify v1.8.2 github.com/unrolled/secure v1.13.0 github.com/xrash/smetrics v0.0.0-20200730060457-89a2a8a1fb0b golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e golang.org/x/image v0.7.0 - golang.org/x/sync v0.2.0 + golang.org/x/sync v0.1.0 golang.org/x/text v0.9.0 - golang.org/x/tools v0.9.1 + golang.org/x/tools v0.8.0 gopkg.in/yaml.v3 v3.0.1 ) require ( + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/swag v0.21.1 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/pprof v0.0.0-20230323073829-e72429f035bd // indirect github.com/gorilla/css v1.0.0 // indirect + github.com/gorilla/mux v1.8.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/invopop/yaml v0.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect @@ -77,14 +85,17 @@ require ( github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/magiconair/properties v1.8.7 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/perimeterx/marshmallow v1.1.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect @@ -95,10 +106,11 @@ require ( github.com/stretchr/objx v0.5.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect go.uber.org/goleak v1.1.11 // indirect - golang.org/x/crypto v0.8.0 // indirect + golang.org/x/crypto v0.7.0 // indirect golang.org/x/mod v0.10.0 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.8.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + golang.org/x/net v0.9.0 // indirect + golang.org/x/sys v0.7.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index c5a3a3699..2dd7778f8 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/ReneKroon/ttlcache/v2 v2.11.0 h1:OvlcYFYi941SBN3v9dsDcC2N8vRxyHcCmJb3Vl4QMoM= github.com/ReneKroon/ttlcache/v2 v2.11.0/go.mod h1:mBxvsNY+BT8qLLd6CuAJubbKo6r0jh3nb5et22bbfGY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -49,19 +50,17 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beego/beego/v2 v2.0.7 h1:9KNnUM40tn3pbCOFfe6SJ1oOL0oTi/oBS/C/wCEdAXA= github.com/beego/beego/v2 v2.0.7/go.mod h1:f0uOEkmJWgAuDTlTxUdgJzwG3PDSIf3UWF3NpMohbFE= -<<<<<<< HEAD -======= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= ->>>>>>> 43f38c7d (WIP) github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -83,6 +82,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/deepmap/oapi-codegen v1.12.4 h1:pPmn6qI9MuOtCz82WY2Xaw46EQjgvxednXXrP7g5Q2s= +github.com/deepmap/oapi-codegen v1.12.4/go.mod h1:3lgHGMu6myQ2vqbbTXH2H1o4eXFTGnFiDaOaKKl5yas= github.com/deluan/rest v0.0.0-20211101235434-380523c4bb47 h1:IhGAYGDi212gspq0XkYAI+DN5e9lfAIm8Qgu1wj9yN4= github.com/deluan/rest v0.0.0-20211101235434-380523c4bb47/go.mod h1:tSgDythFsl0QgS/PFWfIZqcJKnkADWneY80jaVRlqK8= github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 h1:mGvOb3zxl4vCLv+dbf7JA6CAaM2UH/AGP1KX4DsJmTI= @@ -112,6 +113,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/getkin/kin-openapi v0.115.0 h1:c8WHRLVY3G8m9jQTy0/DnIuljgRwTCB5twZytQS4JyU= +github.com/getkin/kin-openapi v0.115.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= @@ -133,11 +136,18 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -214,6 +224,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -229,6 +241,10 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc= +github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -238,6 +254,7 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= @@ -253,6 +270,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y= +github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= @@ -273,9 +292,15 @@ github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmt github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U= github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0= github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= @@ -294,6 +319,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= @@ -313,6 +340,8 @@ github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/perimeterx/marshmallow v1.1.4 h1:pZLDH9RjlLGGorbXhcaQLhfuV0pFMNfPO55FuFkxqLw= +github.com/perimeterx/marshmallow v1.1.4/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -379,6 +408,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -397,8 +427,14 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/unrolled/secure v1.13.0 h1:sdr3Phw2+f8Px8HE5sd1EHdj1aV3yUwed/uZXChLFsk= github.com/unrolled/secure v1.13.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/xrash/smetrics v0.0.0-20200730060457-89a2a8a1fb0b h1:tnWgqoOBmInkt5pbLjagwNVjjT4RdJhFHzL1ebCSRh8= github.com/xrash/smetrics v0.0.0-20200730060457-89a2a8a1fb0b/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -413,20 +449,9 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -<<<<<<< HEAD go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -======= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= ->>>>>>> 43f38c7d (WIP) golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -448,17 +473,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -<<<<<<< HEAD golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= -======= -golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= -golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2 h1:J74nGeMgeFnYQJN59eFwh06jX/V8g0lB7LWpjSLxtgU= -golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= ->>>>>>> 43f38c7d (WIP) golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -808,8 +824,11 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From 250b6dbb33652876f50527a7414b95e1c7ad91ff Mon Sep 17 00:00:00 2001 From: Deluan Date: Sun, 9 Apr 2023 17:50:04 -0400 Subject: [PATCH 08/22] Start breaking OpenAPI spec --- api/paths/album.yml | 28 ++++ api/paths/albums.yml | 39 ++++++ api/paths/artist.yml | 28 ++++ api/paths/artists.yml | 39 ++++++ api/paths/server.yml | 18 +++ api/paths/track.yml | 28 ++++ api/paths/tracks.yml | 39 ++++++ api/spec.yml | 227 ++------------------------------ go.mod | 2 +- go.sum | 8 +- server/api/openapi_api.gen.go | 2 +- server/api/openapi_types.gen.go | 2 +- 12 files changed, 234 insertions(+), 226 deletions(-) create mode 100644 api/paths/album.yml create mode 100644 api/paths/albums.yml create mode 100644 api/paths/artist.yml create mode 100644 api/paths/artists.yml create mode 100644 api/paths/server.yml create mode 100644 api/paths/track.yml create mode 100644 api/paths/tracks.yml diff --git a/api/paths/album.yml b/api/paths/album.yml new file mode 100644 index 000000000..5cfcd4442 --- /dev/null +++ b/api/paths/album.yml @@ -0,0 +1,28 @@ +get: + summary: Retrieve an individual album + operationId: getAlbum + parameters: + - $ref: '#/components/parameters/include' + - name: albumId + in: path + description: The unique identifier of the album + required: true + schema: + type: string + responses: + '200': + description: An album object + content: + application/vnd.api+json: + schema: + type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/Album' + '403': + $ref: '#/components/responses/NotAuthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' diff --git a/api/paths/albums.yml b/api/paths/albums.yml new file mode 100644 index 000000000..1df2835ff --- /dev/null +++ b/api/paths/albums.yml @@ -0,0 +1,39 @@ +get: + summary: Retrieve a list of albums + operationId: getAlbums + parameters: + - $ref: '#/components/parameters/pageLimit' + - $ref: '#/components/parameters/pageOffset' + - $ref: '#/components/parameters/filterEquals' + - $ref: '#/components/parameters/filterContains' + - $ref: '#/components/parameters/filterLessThan' + - $ref: '#/components/parameters/filterLessOrEqual' + - $ref: '#/components/parameters/filterGreaterThan' + - $ref: '#/components/parameters/filterGreaterOrEqual' + - $ref: '#/components/parameters/filterStartsWith' + - $ref: '#/components/parameters/filterEndsWith' + - $ref: '#/components/parameters/sort' + - $ref: '#/components/parameters/include' + responses: + '200': + description: A list of albums + content: + application/vnd.api+json: + schema: + type: object + required: [data, links] + properties: + data: + type: array + items: + $ref: '#/components/schemas/Album' + links: + $ref: '#/components/schemas/PaginationLinks' + meta: + $ref: '#/components/schemas/PaginationMeta' + '400': + $ref: '#/components/responses/BadRequest' + '403': + $ref: '#/components/responses/NotAuthorized' + '500': + $ref: '#/components/responses/InternalServerError' diff --git a/api/paths/artist.yml b/api/paths/artist.yml new file mode 100644 index 000000000..6bd34f11c --- /dev/null +++ b/api/paths/artist.yml @@ -0,0 +1,28 @@ +get: + summary: Retrieve an individual artist + operationId: getArtist + parameters: + - $ref: '#/components/parameters/include' + - name: artistId + in: path + description: The unique identifier of the artist + required: true + schema: + type: string + responses: + '200': + description: An artist object + content: + application/vnd.api+json: + schema: + type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/Artist' + '403': + $ref: '#/components/responses/NotAuthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' diff --git a/api/paths/artists.yml b/api/paths/artists.yml new file mode 100644 index 000000000..9476ff20c --- /dev/null +++ b/api/paths/artists.yml @@ -0,0 +1,39 @@ +get: + summary: Retrieve a list of artists + operationId: getArtists + parameters: + - $ref: '#/components/parameters/pageLimit' + - $ref: '#/components/parameters/pageOffset' + - $ref: '#/components/parameters/filterEquals' + - $ref: '#/components/parameters/filterContains' + - $ref: '#/components/parameters/filterLessThan' + - $ref: '#/components/parameters/filterLessOrEqual' + - $ref: '#/components/parameters/filterGreaterThan' + - $ref: '#/components/parameters/filterGreaterOrEqual' + - $ref: '#/components/parameters/filterStartsWith' + - $ref: '#/components/parameters/filterEndsWith' + - $ref: '#/components/parameters/sort' + - $ref: '#/components/parameters/include' + responses: + '200': + description: A list of artists + content: + application/vnd.api+json: + schema: + type: object + required: [data, links] + properties: + data: + type: array + items: + $ref: '#/components/schemas/Artist' + links: + $ref: '#/components/schemas/PaginationLinks' + meta: + $ref: '#/components/schemas/PaginationMeta' + '400': + $ref: '#/components/responses/BadRequest' + '403': + $ref: '#/components/responses/NotAuthorized' + '500': + $ref: '#/components/responses/InternalServerError' diff --git a/api/paths/server.yml b/api/paths/server.yml new file mode 100644 index 000000000..05a292aa6 --- /dev/null +++ b/api/paths/server.yml @@ -0,0 +1,18 @@ +get: + summary: Get server's global info + operationId: getServerInfo + responses: + '200': + description: The response’s data key maps to a resource object dictionary representing the server. + content: + application/vnd.api+json: + schema: + type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/ServerInfo' + '403': + $ref: '#/components/responses/NotAuthorized' + '500': + $ref: '#/components/responses/InternalServerError' diff --git a/api/paths/track.yml b/api/paths/track.yml new file mode 100644 index 000000000..1b1e8e20f --- /dev/null +++ b/api/paths/track.yml @@ -0,0 +1,28 @@ +get: + summary: Retrieve an individual track + operationId: getTrack + parameters: + - $ref: '#/components/parameters/include' + - name: trackId + in: path + description: The unique identifier of the track + required: true + schema: + type: string + responses: + '200': + description: A track object + content: + application/vnd.api+json: + schema: + type: object + required: [data] + properties: + data: + $ref: '#/components/schemas/Track' + '403': + $ref: '#/components/responses/NotAuthorized' + '404': + $ref: '#/components/responses/NotFound' + '500': + $ref: '#/components/responses/InternalServerError' diff --git a/api/paths/tracks.yml b/api/paths/tracks.yml new file mode 100644 index 000000000..0dba01735 --- /dev/null +++ b/api/paths/tracks.yml @@ -0,0 +1,39 @@ +get: + summary: Get a list of tracks + operationId: getTracks + parameters: + - $ref: '#/components/parameters/pageLimit' + - $ref: '#/components/parameters/pageOffset' + - $ref: '#/components/parameters/filterEquals' + - $ref: '#/components/parameters/filterContains' + - $ref: '#/components/parameters/filterLessThan' + - $ref: '#/components/parameters/filterLessOrEqual' + - $ref: '#/components/parameters/filterGreaterThan' + - $ref: '#/components/parameters/filterGreaterOrEqual' + - $ref: '#/components/parameters/filterStartsWith' + - $ref: '#/components/parameters/filterEndsWith' + - $ref: '#/components/parameters/sort' + - $ref: '#/components/parameters/include' + responses: + '200': + description: A list of tracks + content: + application/vnd.api+json: + schema: + type: object + required: [data, links] + properties: + data: + type: array + items: + $ref: '#/components/schemas/Track' + links: + $ref: '#/components/schemas/PaginationLinks' + meta: + $ref: '#/components/schemas/PaginationMeta' + '400': + $ref: '#/components/responses/BadRequest' + '403': + $ref: '#/components/responses/NotAuthorized' + '500': + $ref: '#/components/responses/InternalServerError' diff --git a/api/spec.yml b/api/spec.yml index 842e358c0..10f5ee789 100644 --- a/api/spec.yml +++ b/api/spec.yml @@ -14,233 +14,22 @@ info: url: https://navidrome.org servers: - url: /api/v2 + paths: /server: - get: - summary: Get server's global info - operationId: getServerInfo - responses: - '200': - description: The response’s data key maps to a resource object dictionary representing the server. - content: - application/vnd.api+json: - schema: - type: object - required: [data] - properties: - data: - $ref: '#/components/schemas/ServerInfo' - '403': - $ref: '#/components/responses/NotAuthorized' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: './paths/server.yml' /tracks: - get: - summary: Get a list of tracks - operationId: getTracks - parameters: - - $ref: '#/components/parameters/pageLimit' - - $ref: '#/components/parameters/pageOffset' - - $ref: '#/components/parameters/filterEquals' - - $ref: '#/components/parameters/filterContains' - - $ref: '#/components/parameters/filterLessThan' - - $ref: '#/components/parameters/filterLessOrEqual' - - $ref: '#/components/parameters/filterGreaterThan' - - $ref: '#/components/parameters/filterGreaterOrEqual' - - $ref: '#/components/parameters/filterStartsWith' - - $ref: '#/components/parameters/filterEndsWith' - - $ref: '#/components/parameters/sort' - - $ref: '#/components/parameters/include' - responses: - '200': - description: A list of tracks - content: - application/vnd.api+json: - schema: - type: object - required: [data, links] - properties: - data: - type: array - items: - $ref: '#/components/schemas/Track' - links: - $ref: '#/components/schemas/PaginationLinks' - meta: - $ref: '#/components/schemas/PaginationMeta' - '400': - $ref: '#/components/responses/BadRequest' - '403': - $ref: '#/components/responses/NotAuthorized' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: './paths/tracks.yml' /tracks/{trackId}: - get: - summary: Retrieve an individual track - operationId: getTrack - parameters: - - $ref: '#/components/parameters/include' - - name: trackId - in: path - description: The unique identifier of the track - required: true - schema: - type: string - responses: - '200': - description: A track object - content: - application/vnd.api+json: - schema: - type: object - required: [data] - properties: - data: - $ref: '#/components/schemas/Track' - '403': - $ref: '#/components/responses/NotAuthorized' - '404': - $ref: '#/components/responses/NotFound' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: './paths/track.yml' /artists: - get: - summary: Retrieve a list of artists - operationId: getArtists - parameters: - - $ref: '#/components/parameters/pageLimit' - - $ref: '#/components/parameters/pageOffset' - - $ref: '#/components/parameters/filterEquals' - - $ref: '#/components/parameters/filterContains' - - $ref: '#/components/parameters/filterLessThan' - - $ref: '#/components/parameters/filterLessOrEqual' - - $ref: '#/components/parameters/filterGreaterThan' - - $ref: '#/components/parameters/filterGreaterOrEqual' - - $ref: '#/components/parameters/filterStartsWith' - - $ref: '#/components/parameters/filterEndsWith' - - $ref: '#/components/parameters/sort' - - $ref: '#/components/parameters/include' - responses: - '200': - description: A list of artists - content: - application/vnd.api+json: - schema: - type: object - required: [data, links] - properties: - data: - type: array - items: - $ref: '#/components/schemas/Artist' - links: - $ref: '#/components/schemas/PaginationLinks' - meta: - $ref: '#/components/schemas/PaginationMeta' - '400': - $ref: '#/components/responses/BadRequest' - '403': - $ref: '#/components/responses/NotAuthorized' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: './paths/artists.yml' /artists/{artistId}: - get: - summary: Retrieve an individual artist - operationId: getArtist - parameters: - - $ref: '#/components/parameters/include' - - name: artistId - in: path - description: The unique identifier of the artist - required: true - schema: - type: string - responses: - '200': - description: An artist object - content: - application/vnd.api+json: - schema: - type: object - required: [data] - properties: - data: - $ref: '#/components/schemas/Artist' - '403': - $ref: '#/components/responses/NotAuthorized' - '404': - $ref: '#/components/responses/NotFound' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: './paths/artist.yml' /albums: - get: - summary: Retrieve a list of albums - operationId: getAlbums - parameters: - - $ref: '#/components/parameters/pageLimit' - - $ref: '#/components/parameters/pageOffset' - - $ref: '#/components/parameters/filterEquals' - - $ref: '#/components/parameters/filterContains' - - $ref: '#/components/parameters/filterLessThan' - - $ref: '#/components/parameters/filterLessOrEqual' - - $ref: '#/components/parameters/filterGreaterThan' - - $ref: '#/components/parameters/filterGreaterOrEqual' - - $ref: '#/components/parameters/filterStartsWith' - - $ref: '#/components/parameters/filterEndsWith' - - $ref: '#/components/parameters/sort' - - $ref: '#/components/parameters/include' - responses: - '200': - description: A list of albums - content: - application/vnd.api+json: - schema: - type: object - required: [data, links] - properties: - data: - type: array - items: - $ref: '#/components/schemas/Album' - links: - $ref: '#/components/schemas/PaginationLinks' - meta: - $ref: '#/components/schemas/PaginationMeta' - '400': - $ref: '#/components/responses/BadRequest' - '403': - $ref: '#/components/responses/NotAuthorized' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: './paths/albums.yml' /albums/{albumId}: - get: - summary: Retrieve an individual album - operationId: getAlbum - parameters: - - $ref: '#/components/parameters/include' - - name: albumId - in: path - description: The unique identifier of the album - required: true - schema: - type: string - responses: - '200': - description: An album object - content: - application/vnd.api+json: - schema: - type: object - required: [data] - properties: - data: - $ref: '#/components/schemas/Album' - '403': - $ref: '#/components/responses/NotAuthorized' - '404': - $ref: '#/components/responses/NotFound' - '500': - $ref: '#/components/responses/InternalServerError' + $ref: './paths/album.yml' components: parameters: diff --git a/go.mod b/go.mod index 164aa4343..749515511 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/ReneKroon/ttlcache/v2 v2.11.0 github.com/beego/beego/v2 v2.0.7 github.com/bradleyjkemp/cupaloy/v2 v2.8.0 - github.com/deepmap/oapi-codegen v1.12.4 + github.com/deepmap/oapi-codegen v1.12.5-0.20230403173426-fd06f5aed350 github.com/deluan/rest v0.0.0-20211101235434-380523c4bb47 github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086 diff --git a/go.sum b/go.sum index 2dd7778f8..21d227b49 100644 --- a/go.sum +++ b/go.sum @@ -82,8 +82,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= -github.com/deepmap/oapi-codegen v1.12.4 h1:pPmn6qI9MuOtCz82WY2Xaw46EQjgvxednXXrP7g5Q2s= -github.com/deepmap/oapi-codegen v1.12.4/go.mod h1:3lgHGMu6myQ2vqbbTXH2H1o4eXFTGnFiDaOaKKl5yas= +github.com/deepmap/oapi-codegen v1.12.5-0.20230403173426-fd06f5aed350 h1:L3ObbQ6GHi8NLNaVWGlyHto/riZZx32M8JNbIcJJqP4= +github.com/deepmap/oapi-codegen v1.12.5-0.20230403173426-fd06f5aed350/go.mod h1:WMhniMLAXaHHNYz+Nk+h35qYieaeUaASStrkdkHnBr8= github.com/deluan/rest v0.0.0-20211101235434-380523c4bb47 h1:IhGAYGDi212gspq0XkYAI+DN5e9lfAIm8Qgu1wj9yN4= github.com/deluan/rest v0.0.0-20211101235434-380523c4bb47/go.mod h1:tSgDythFsl0QgS/PFWfIZqcJKnkADWneY80jaVRlqK8= github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 h1:mGvOb3zxl4vCLv+dbf7JA6CAaM2UH/AGP1KX4DsJmTI= @@ -270,7 +270,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y= +github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= @@ -429,8 +429,8 @@ github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8 github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= -github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= github.com/unrolled/secure v1.13.0 h1:sdr3Phw2+f8Px8HE5sd1EHdj1aV3yUwed/uZXChLFsk= github.com/unrolled/secure v1.13.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= diff --git a/server/api/openapi_api.gen.go b/server/api/openapi_api.gen.go index 7cb3fa302..47fec91a1 100644 --- a/server/api/openapi_api.gen.go +++ b/server/api/openapi_api.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version v1.12.5-0.20230314231417-0cfaaa77a7d2 DO NOT EDIT. +// Code generated by github.com/deepmap/oapi-codegen version v1.12.5-0.20230403173426-fd06f5aed350 DO NOT EDIT. package api import ( diff --git a/server/api/openapi_types.gen.go b/server/api/openapi_types.gen.go index 07e035303..0f5ef82a8 100644 --- a/server/api/openapi_types.gen.go +++ b/server/api/openapi_types.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version v1.12.5-0.20230314231417-0cfaaa77a7d2 DO NOT EDIT. +// Code generated by github.com/deepmap/oapi-codegen version v1.12.5-0.20230403173426-fd06f5aed350 DO NOT EDIT. package api import ( From 3c0f23e3f2f69d998395780b2f01f3ae1bd20c3b Mon Sep 17 00:00:00 2001 From: Deluan Date: Sun, 14 May 2023 10:23:06 -0400 Subject: [PATCH 09/22] Fix spec paths --- api/paths/album.yml | 10 +++++----- api/paths/albums.yml | 36 ++++++++++++++++++------------------ api/paths/artist.yml | 10 +++++----- api/paths/artists.yml | 36 ++++++++++++++++++------------------ api/paths/server.yml | 6 +++--- api/paths/track.yml | 10 +++++----- api/paths/tracks.yml | 36 ++++++++++++++++++------------------ api/spec.yml | 4 ++-- 8 files changed, 74 insertions(+), 74 deletions(-) diff --git a/api/paths/album.yml b/api/paths/album.yml index 5cfcd4442..7dfb7b953 100644 --- a/api/paths/album.yml +++ b/api/paths/album.yml @@ -2,7 +2,7 @@ get: summary: Retrieve an individual album operationId: getAlbum parameters: - - $ref: '#/components/parameters/include' + - $ref: '../spec.yml#/components/parameters/include' - name: albumId in: path description: The unique identifier of the album @@ -19,10 +19,10 @@ get: required: [data] properties: data: - $ref: '#/components/schemas/Album' + $ref: '../spec.yml#/components/schemas/Album' '403': - $ref: '#/components/responses/NotAuthorized' + $ref: '../spec.yml#/components/responses/NotAuthorized' '404': - $ref: '#/components/responses/NotFound' + $ref: '../spec.yml#/components/responses/NotFound' '500': - $ref: '#/components/responses/InternalServerError' + $ref: '../spec.yml#/components/responses/InternalServerError' diff --git a/api/paths/albums.yml b/api/paths/albums.yml index 1df2835ff..7e95ce18a 100644 --- a/api/paths/albums.yml +++ b/api/paths/albums.yml @@ -2,18 +2,18 @@ get: summary: Retrieve a list of albums operationId: getAlbums parameters: - - $ref: '#/components/parameters/pageLimit' - - $ref: '#/components/parameters/pageOffset' - - $ref: '#/components/parameters/filterEquals' - - $ref: '#/components/parameters/filterContains' - - $ref: '#/components/parameters/filterLessThan' - - $ref: '#/components/parameters/filterLessOrEqual' - - $ref: '#/components/parameters/filterGreaterThan' - - $ref: '#/components/parameters/filterGreaterOrEqual' - - $ref: '#/components/parameters/filterStartsWith' - - $ref: '#/components/parameters/filterEndsWith' - - $ref: '#/components/parameters/sort' - - $ref: '#/components/parameters/include' + - $ref: '../spec.yml#/components/parameters/pageLimit' + - $ref: '../spec.yml#/components/parameters/pageOffset' + - $ref: '../spec.yml#/components/parameters/filterEquals' + - $ref: '../spec.yml#/components/parameters/filterContains' + - $ref: '../spec.yml#/components/parameters/filterLessThan' + - $ref: '../spec.yml#/components/parameters/filterLessOrEqual' + - $ref: '../spec.yml#/components/parameters/filterGreaterThan' + - $ref: '../spec.yml#/components/parameters/filterGreaterOrEqual' + - $ref: '../spec.yml#/components/parameters/filterStartsWith' + - $ref: '../spec.yml#/components/parameters/filterEndsWith' + - $ref: '../spec.yml#/components/parameters/sort' + - $ref: '../spec.yml#/components/parameters/include' responses: '200': description: A list of albums @@ -26,14 +26,14 @@ get: data: type: array items: - $ref: '#/components/schemas/Album' + $ref: '../spec.yml#/components/schemas/Album' links: - $ref: '#/components/schemas/PaginationLinks' + $ref: '../spec.yml#/components/schemas/PaginationLinks' meta: - $ref: '#/components/schemas/PaginationMeta' + $ref: '../spec.yml#/components/schemas/PaginationMeta' '400': - $ref: '#/components/responses/BadRequest' + $ref: '../spec.yml#/components/responses/BadRequest' '403': - $ref: '#/components/responses/NotAuthorized' + $ref: '../spec.yml#/components/responses/NotAuthorized' '500': - $ref: '#/components/responses/InternalServerError' + $ref: '../spec.yml#/components/responses/InternalServerError' diff --git a/api/paths/artist.yml b/api/paths/artist.yml index 6bd34f11c..7bf4ed8eb 100644 --- a/api/paths/artist.yml +++ b/api/paths/artist.yml @@ -2,7 +2,7 @@ get: summary: Retrieve an individual artist operationId: getArtist parameters: - - $ref: '#/components/parameters/include' + - $ref: '../spec.yml#/components/parameters/include' - name: artistId in: path description: The unique identifier of the artist @@ -19,10 +19,10 @@ get: required: [data] properties: data: - $ref: '#/components/schemas/Artist' + $ref: '../spec.yml#/components/schemas/Artist' '403': - $ref: '#/components/responses/NotAuthorized' + $ref: '../spec.yml#/components/responses/NotAuthorized' '404': - $ref: '#/components/responses/NotFound' + $ref: '../spec.yml#/components/responses/NotFound' '500': - $ref: '#/components/responses/InternalServerError' + $ref: '../spec.yml#/components/responses/InternalServerError' diff --git a/api/paths/artists.yml b/api/paths/artists.yml index 9476ff20c..17f32feb0 100644 --- a/api/paths/artists.yml +++ b/api/paths/artists.yml @@ -2,18 +2,18 @@ get: summary: Retrieve a list of artists operationId: getArtists parameters: - - $ref: '#/components/parameters/pageLimit' - - $ref: '#/components/parameters/pageOffset' - - $ref: '#/components/parameters/filterEquals' - - $ref: '#/components/parameters/filterContains' - - $ref: '#/components/parameters/filterLessThan' - - $ref: '#/components/parameters/filterLessOrEqual' - - $ref: '#/components/parameters/filterGreaterThan' - - $ref: '#/components/parameters/filterGreaterOrEqual' - - $ref: '#/components/parameters/filterStartsWith' - - $ref: '#/components/parameters/filterEndsWith' - - $ref: '#/components/parameters/sort' - - $ref: '#/components/parameters/include' + - $ref: '../spec.yml#/components/parameters/pageLimit' + - $ref: '../spec.yml#/components/parameters/pageOffset' + - $ref: '../spec.yml#/components/parameters/filterEquals' + - $ref: '../spec.yml#/components/parameters/filterContains' + - $ref: '../spec.yml#/components/parameters/filterLessThan' + - $ref: '../spec.yml#/components/parameters/filterLessOrEqual' + - $ref: '../spec.yml#/components/parameters/filterGreaterThan' + - $ref: '../spec.yml#/components/parameters/filterGreaterOrEqual' + - $ref: '../spec.yml#/components/parameters/filterStartsWith' + - $ref: '../spec.yml#/components/parameters/filterEndsWith' + - $ref: '../spec.yml#/components/parameters/sort' + - $ref: '../spec.yml#/components/parameters/include' responses: '200': description: A list of artists @@ -26,14 +26,14 @@ get: data: type: array items: - $ref: '#/components/schemas/Artist' + $ref: '../spec.yml#/components/schemas/Artist' links: - $ref: '#/components/schemas/PaginationLinks' + $ref: '../spec.yml#/components/schemas/PaginationLinks' meta: - $ref: '#/components/schemas/PaginationMeta' + $ref: '../spec.yml#/components/schemas/PaginationMeta' '400': - $ref: '#/components/responses/BadRequest' + $ref: '../spec.yml#/components/responses/BadRequest' '403': - $ref: '#/components/responses/NotAuthorized' + $ref: '../spec.yml#/components/responses/NotAuthorized' '500': - $ref: '#/components/responses/InternalServerError' + $ref: '../spec.yml#/components/responses/InternalServerError' diff --git a/api/paths/server.yml b/api/paths/server.yml index 05a292aa6..eeb8c79ba 100644 --- a/api/paths/server.yml +++ b/api/paths/server.yml @@ -11,8 +11,8 @@ get: required: [data] properties: data: - $ref: '#/components/schemas/ServerInfo' + $ref: '../spec.yml#/components/schemas/ServerInfo' '403': - $ref: '#/components/responses/NotAuthorized' + $ref: '../spec.yml#/components/responses/NotAuthorized' '500': - $ref: '#/components/responses/InternalServerError' + $ref: '../spec.yml#/components/responses/InternalServerError' diff --git a/api/paths/track.yml b/api/paths/track.yml index 1b1e8e20f..a7c4c8a14 100644 --- a/api/paths/track.yml +++ b/api/paths/track.yml @@ -2,7 +2,7 @@ get: summary: Retrieve an individual track operationId: getTrack parameters: - - $ref: '#/components/parameters/include' + - $ref: '../spec.yml#/components/parameters/include' - name: trackId in: path description: The unique identifier of the track @@ -19,10 +19,10 @@ get: required: [data] properties: data: - $ref: '#/components/schemas/Track' + $ref: '../spec.yml#/components/schemas/Track' '403': - $ref: '#/components/responses/NotAuthorized' + $ref: '../spec.yml#/components/responses/NotAuthorized' '404': - $ref: '#/components/responses/NotFound' + $ref: '../spec.yml#/components/responses/NotFound' '500': - $ref: '#/components/responses/InternalServerError' + $ref: '../spec.yml#/components/responses/InternalServerError' diff --git a/api/paths/tracks.yml b/api/paths/tracks.yml index 0dba01735..3a2a11fc8 100644 --- a/api/paths/tracks.yml +++ b/api/paths/tracks.yml @@ -2,18 +2,18 @@ get: summary: Get a list of tracks operationId: getTracks parameters: - - $ref: '#/components/parameters/pageLimit' - - $ref: '#/components/parameters/pageOffset' - - $ref: '#/components/parameters/filterEquals' - - $ref: '#/components/parameters/filterContains' - - $ref: '#/components/parameters/filterLessThan' - - $ref: '#/components/parameters/filterLessOrEqual' - - $ref: '#/components/parameters/filterGreaterThan' - - $ref: '#/components/parameters/filterGreaterOrEqual' - - $ref: '#/components/parameters/filterStartsWith' - - $ref: '#/components/parameters/filterEndsWith' - - $ref: '#/components/parameters/sort' - - $ref: '#/components/parameters/include' + - $ref: '../spec.yml#/components/parameters/pageLimit' + - $ref: '../spec.yml#/components/parameters/pageOffset' + - $ref: '../spec.yml#/components/parameters/filterEquals' + - $ref: '../spec.yml#/components/parameters/filterContains' + - $ref: '../spec.yml#/components/parameters/filterLessThan' + - $ref: '../spec.yml#/components/parameters/filterLessOrEqual' + - $ref: '../spec.yml#/components/parameters/filterGreaterThan' + - $ref: '../spec.yml#/components/parameters/filterGreaterOrEqual' + - $ref: '../spec.yml#/components/parameters/filterStartsWith' + - $ref: '../spec.yml#/components/parameters/filterEndsWith' + - $ref: '../spec.yml#/components/parameters/sort' + - $ref: '../spec.yml#/components/parameters/include' responses: '200': description: A list of tracks @@ -26,14 +26,14 @@ get: data: type: array items: - $ref: '#/components/schemas/Track' + $ref: '../spec.yml#/components/schemas/Track' links: - $ref: '#/components/schemas/PaginationLinks' + $ref: '../spec.yml#/components/schemas/PaginationLinks' meta: - $ref: '#/components/schemas/PaginationMeta' + $ref: '../spec.yml#/components/schemas/PaginationMeta' '400': - $ref: '#/components/responses/BadRequest' + $ref: '../spec.yml#/components/responses/BadRequest' '403': - $ref: '#/components/responses/NotAuthorized' + $ref: '../spec.yml#/components/responses/NotAuthorized' '500': - $ref: '#/components/responses/InternalServerError' + $ref: '../spec.yml#/components/responses/InternalServerError' diff --git a/api/spec.yml b/api/spec.yml index 10f5ee789..fa8f3c6f7 100644 --- a/api/spec.yml +++ b/api/spec.yml @@ -300,7 +300,7 @@ components: type: object properties: meta: - $ref: #/components/schemas/ArtistMetaObject + $ref: '#/components/schemas/ArtistMetaObject' data: $ref: '#/components/schemas/ResourceObject' required: @@ -423,7 +423,7 @@ components: type: object properties: meta: - $ref: #/components/schemas/ArtistMetaObject + $ref: '#/components/schemas/ArtistMetaObject' data: $ref: '#/components/schemas/ResourceObject' required: From 2650e4c27cd1e938e207b3be6022ab3cfa65aaf4 Mon Sep 17 00:00:00 2001 From: Deluan Date: Sun, 21 May 2023 13:16:49 -0400 Subject: [PATCH 10/22] Finish splitting openapi into multiple files --- .gitignore | 3 +- api/parameters/_index.yml | 24 + api/parameters/query/filterContains.yml | 9 + api/parameters/query/filterEndsWith.yml | 9 + api/parameters/query/filterEquals.yml | 9 + api/parameters/query/filterGreaterOrEqual.yml | 9 + api/parameters/query/filterGreaterThan.yml | 9 + api/parameters/query/filterLessOrEqual.yml | 9 + api/parameters/query/filterLessThan.yml | 9 + api/parameters/query/filterStartsWith.yml | 9 + api/parameters/query/include.yml | 6 + api/parameters/query/pageLimit.yml | 9 + api/parameters/query/pageOffset.yml | 9 + api/parameters/query/sort.yml | 6 + api/paths/albums.yml | 39 -- api/paths/artists.yml | 39 -- api/paths/tracks.yml | 39 -- api/{paths => resources}/album.yml | 10 +- api/resources/albums.yml | 39 ++ api/{paths => resources}/artist.yml | 10 +- api/resources/artists.yml | 39 ++ api/{paths => resources}/server.yml | 6 +- api/{paths => resources}/track.yml | 10 +- api/resources/tracks.yml | 39 ++ api/responses/BadRequest.yml | 5 + api/responses/InternalServerError.yml | 5 + api/responses/NotAuthorized.yml | 5 + api/responses/NotFound.yml | 5 + api/responses/_index.yml | 8 + api/schemas/Album.yml | 20 + api/schemas/AlbumArtistRelationship.yml | 9 + api/schemas/AlbumAttributes.yml | 16 + api/schemas/AlbumTrackRelationship.yml | 6 + api/schemas/Artist.yml | 27 + api/schemas/ArtistAlbumRelationship.yml | 9 + api/schemas/ArtistAttributes.yml | 8 + api/schemas/ArtistMetaObject.yml | 6 + api/schemas/ArtistRole.yml | 5 + api/schemas/ArtistTrackRelationship.yml | 14 + api/schemas/ErrorList.yml | 7 + api/schemas/ErrorObject.yml | 12 + api/schemas/IncludedResource.yml | 9 + api/schemas/PaginationLinks.yml | 14 + api/schemas/PaginationMeta.yml | 14 + api/schemas/ResourceList.yml | 18 + api/schemas/ResourceObject.yml | 8 + api/schemas/ResourceType.yml | 6 + api/schemas/ServerInfo.yml | 24 + api/schemas/Track.yml | 8 + api/schemas/TrackArtistRelationship.yml | 9 + api/schemas/TrackAttributes.yml | 55 ++ api/schemas/TrackRelationships.yml | 12 + api/schemas/_index.yml | 46 ++ api/spec.yml | 494 +----------------- server/api/api.go | 5 +- server/api/openapi_api.gen.go | 92 ++-- server/api/openapi_types.gen.go | 10 +- 57 files changed, 717 insertions(+), 673 deletions(-) create mode 100644 api/parameters/_index.yml create mode 100644 api/parameters/query/filterContains.yml create mode 100644 api/parameters/query/filterEndsWith.yml create mode 100644 api/parameters/query/filterEquals.yml create mode 100644 api/parameters/query/filterGreaterOrEqual.yml create mode 100644 api/parameters/query/filterGreaterThan.yml create mode 100644 api/parameters/query/filterLessOrEqual.yml create mode 100644 api/parameters/query/filterLessThan.yml create mode 100644 api/parameters/query/filterStartsWith.yml create mode 100644 api/parameters/query/include.yml create mode 100644 api/parameters/query/pageLimit.yml create mode 100644 api/parameters/query/pageOffset.yml create mode 100644 api/parameters/query/sort.yml delete mode 100644 api/paths/albums.yml delete mode 100644 api/paths/artists.yml delete mode 100644 api/paths/tracks.yml rename api/{paths => resources}/album.yml (61%) create mode 100644 api/resources/albums.yml rename api/{paths => resources}/artist.yml (61%) create mode 100644 api/resources/artists.yml rename api/{paths => resources}/server.yml (66%) rename api/{paths => resources}/track.yml (61%) create mode 100644 api/resources/tracks.yml create mode 100644 api/responses/BadRequest.yml create mode 100644 api/responses/InternalServerError.yml create mode 100644 api/responses/NotAuthorized.yml create mode 100644 api/responses/NotFound.yml create mode 100644 api/responses/_index.yml create mode 100644 api/schemas/Album.yml create mode 100644 api/schemas/AlbumArtistRelationship.yml create mode 100644 api/schemas/AlbumAttributes.yml create mode 100644 api/schemas/AlbumTrackRelationship.yml create mode 100644 api/schemas/Artist.yml create mode 100644 api/schemas/ArtistAlbumRelationship.yml create mode 100644 api/schemas/ArtistAttributes.yml create mode 100644 api/schemas/ArtistMetaObject.yml create mode 100644 api/schemas/ArtistRole.yml create mode 100644 api/schemas/ArtistTrackRelationship.yml create mode 100644 api/schemas/ErrorList.yml create mode 100644 api/schemas/ErrorObject.yml create mode 100644 api/schemas/IncludedResource.yml create mode 100644 api/schemas/PaginationLinks.yml create mode 100644 api/schemas/PaginationMeta.yml create mode 100644 api/schemas/ResourceList.yml create mode 100644 api/schemas/ResourceObject.yml create mode 100644 api/schemas/ResourceType.yml create mode 100644 api/schemas/ServerInfo.yml create mode 100644 api/schemas/Track.yml create mode 100644 api/schemas/TrackArtistRelationship.yml create mode 100644 api/schemas/TrackAttributes.yml create mode 100644 api/schemas/TrackRelationships.yml create mode 100644 api/schemas/_index.yml diff --git a/.gitignore b/.gitignore index 2243b42c0..6a18b5871 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ navidrome.db-wal tags .gitinfo docker-compose.yml -!contrib/docker-compose.yml \ No newline at end of file +!contrib/docker-compose.yml +/api/openapi.yaml diff --git a/api/parameters/_index.yml b/api/parameters/_index.yml new file mode 100644 index 000000000..d06a9aac7 --- /dev/null +++ b/api/parameters/_index.yml @@ -0,0 +1,24 @@ +pageOffset: + $ref: './query/pageOffset.yml' +pageLimit: + $ref: './query/pageLimit.yml' +filterEquals: + $ref: './query/filterEquals.yml' +filterLessThan: + $ref: './query/filterLessThan.yml' +filterLessOrEqual: + $ref: './query/filterLessOrEqual.yml' +filterGreaterThan: + $ref: './query/filterGreaterThan.yml' +filterGreaterOrEqual: + $ref: './query/filterGreaterOrEqual.yml' +filterContains: + $ref: './query/filterContains.yml' +filterStartsWith: + $ref: './query/filterStartsWith.yml' +filterEndsWith: + $ref: './query/filterEndsWith.yml' +sort: + $ref: './query/sort.yml' +include: + $ref: './query/include.yml' diff --git a/api/parameters/query/filterContains.yml b/api/parameters/query/filterContains.yml new file mode 100644 index 000000000..71cee77c4 --- /dev/null +++ b/api/parameters/query/filterContains.yml @@ -0,0 +1,9 @@ +name: filter[contains] +in: query +description: 'Filter by any property containing text. Usage: filter[contains]=property:value' +required: false +schema: + type: array + items: + type: string + pattern: '^\w+:\w+' diff --git a/api/parameters/query/filterEndsWith.yml b/api/parameters/query/filterEndsWith.yml new file mode 100644 index 000000000..390bd5606 --- /dev/null +++ b/api/parameters/query/filterEndsWith.yml @@ -0,0 +1,9 @@ +name: filter[endsWith] +in: query +description: 'Filter by any property that ends with text. Usage: filter[endsWith]=property:value' +required: false +schema: + type: array + items: + type: string + pattern: '^\w+:\w+' diff --git a/api/parameters/query/filterEquals.yml b/api/parameters/query/filterEquals.yml new file mode 100644 index 000000000..a523e04b8 --- /dev/null +++ b/api/parameters/query/filterEquals.yml @@ -0,0 +1,9 @@ +name: filter[equals] +in: query +description: 'Filter by any property with an exact match. Usage: filter[equals]=property:value' +required: false +schema: + type: array + items: + type: string + pattern: '^\w+:\w+' diff --git a/api/parameters/query/filterGreaterOrEqual.yml b/api/parameters/query/filterGreaterOrEqual.yml new file mode 100644 index 000000000..8a1ed8ec0 --- /dev/null +++ b/api/parameters/query/filterGreaterOrEqual.yml @@ -0,0 +1,9 @@ +name: filter[greaterOrEqual] +in: query +description: 'Filter by any numeric property greater than or equal to a value. Usage: filter[greaterOrEqual]=property:value' +required: false +schema: + type: array + items: + type: string + pattern: '^\w+:\d+' diff --git a/api/parameters/query/filterGreaterThan.yml b/api/parameters/query/filterGreaterThan.yml new file mode 100644 index 000000000..009a70d19 --- /dev/null +++ b/api/parameters/query/filterGreaterThan.yml @@ -0,0 +1,9 @@ +name: filter[greaterThan] +in: query +description: 'Filter by any numeric property greater than a value. Usage: filter[greaterThan]=property:value' +required: false +schema: + type: array + items: + type: string + pattern: '^\w+:\d+' diff --git a/api/parameters/query/filterLessOrEqual.yml b/api/parameters/query/filterLessOrEqual.yml new file mode 100644 index 000000000..33d029983 --- /dev/null +++ b/api/parameters/query/filterLessOrEqual.yml @@ -0,0 +1,9 @@ +name: filter[lessOrEqual] +in: query +description: 'Filter by any numeric property less than or equal to a value. Usage: filter[lessOrEqual]=property:value' +required: false +schema: + type: array + items: + type: string + pattern: '^\w+:\d+' diff --git a/api/parameters/query/filterLessThan.yml b/api/parameters/query/filterLessThan.yml new file mode 100644 index 000000000..5eb0657f6 --- /dev/null +++ b/api/parameters/query/filterLessThan.yml @@ -0,0 +1,9 @@ +name: filter[lessThan] +in: query +description: 'Filter by any numeric property less than a value. Usage: filter[lessThan]=property:value' +required: false +schema: + type: array + items: + type: string + pattern: '^\w+:\d+' diff --git a/api/parameters/query/filterStartsWith.yml b/api/parameters/query/filterStartsWith.yml new file mode 100644 index 000000000..1f7fa0039 --- /dev/null +++ b/api/parameters/query/filterStartsWith.yml @@ -0,0 +1,9 @@ + name: filter[startsWith] + in: query + description: 'Filter by any property that starts with text. Usage: filter[startsWith]=property:value' + required: false + schema: + type: array + items: + type: string + pattern: '^\w+:\w+' diff --git a/api/parameters/query/include.yml b/api/parameters/query/include.yml new file mode 100644 index 000000000..f3b0a579f --- /dev/null +++ b/api/parameters/query/include.yml @@ -0,0 +1,6 @@ +name: include +in: query +description: Related resources to include in the response, separated by commas +required: false +schema: + type: string diff --git a/api/parameters/query/pageLimit.yml b/api/parameters/query/pageLimit.yml new file mode 100644 index 000000000..7f49946cf --- /dev/null +++ b/api/parameters/query/pageLimit.yml @@ -0,0 +1,9 @@ +name: page[limit] +in: query +description: The number of items per page +required: false +schema: + type: integer + format: int32 + minimum: 0 + default: 10 diff --git a/api/parameters/query/pageOffset.yml b/api/parameters/query/pageOffset.yml new file mode 100644 index 000000000..831f6b914 --- /dev/null +++ b/api/parameters/query/pageOffset.yml @@ -0,0 +1,9 @@ +name: page[offset] +in: query +description: The offset for pagination +required: false +schema: + type: integer + format: int32 + minimum: 0 + default: 0 diff --git a/api/parameters/query/sort.yml b/api/parameters/query/sort.yml new file mode 100644 index 000000000..99e439df4 --- /dev/null +++ b/api/parameters/query/sort.yml @@ -0,0 +1,6 @@ +name: sort +in: query +description: Sort the results by one or more properties, separated by commas. Prefix the property with '-' for descending order. +required: false +schema: + type: string diff --git a/api/paths/albums.yml b/api/paths/albums.yml deleted file mode 100644 index 7e95ce18a..000000000 --- a/api/paths/albums.yml +++ /dev/null @@ -1,39 +0,0 @@ -get: - summary: Retrieve a list of albums - operationId: getAlbums - parameters: - - $ref: '../spec.yml#/components/parameters/pageLimit' - - $ref: '../spec.yml#/components/parameters/pageOffset' - - $ref: '../spec.yml#/components/parameters/filterEquals' - - $ref: '../spec.yml#/components/parameters/filterContains' - - $ref: '../spec.yml#/components/parameters/filterLessThan' - - $ref: '../spec.yml#/components/parameters/filterLessOrEqual' - - $ref: '../spec.yml#/components/parameters/filterGreaterThan' - - $ref: '../spec.yml#/components/parameters/filterGreaterOrEqual' - - $ref: '../spec.yml#/components/parameters/filterStartsWith' - - $ref: '../spec.yml#/components/parameters/filterEndsWith' - - $ref: '../spec.yml#/components/parameters/sort' - - $ref: '../spec.yml#/components/parameters/include' - responses: - '200': - description: A list of albums - content: - application/vnd.api+json: - schema: - type: object - required: [data, links] - properties: - data: - type: array - items: - $ref: '../spec.yml#/components/schemas/Album' - links: - $ref: '../spec.yml#/components/schemas/PaginationLinks' - meta: - $ref: '../spec.yml#/components/schemas/PaginationMeta' - '400': - $ref: '../spec.yml#/components/responses/BadRequest' - '403': - $ref: '../spec.yml#/components/responses/NotAuthorized' - '500': - $ref: '../spec.yml#/components/responses/InternalServerError' diff --git a/api/paths/artists.yml b/api/paths/artists.yml deleted file mode 100644 index 17f32feb0..000000000 --- a/api/paths/artists.yml +++ /dev/null @@ -1,39 +0,0 @@ -get: - summary: Retrieve a list of artists - operationId: getArtists - parameters: - - $ref: '../spec.yml#/components/parameters/pageLimit' - - $ref: '../spec.yml#/components/parameters/pageOffset' - - $ref: '../spec.yml#/components/parameters/filterEquals' - - $ref: '../spec.yml#/components/parameters/filterContains' - - $ref: '../spec.yml#/components/parameters/filterLessThan' - - $ref: '../spec.yml#/components/parameters/filterLessOrEqual' - - $ref: '../spec.yml#/components/parameters/filterGreaterThan' - - $ref: '../spec.yml#/components/parameters/filterGreaterOrEqual' - - $ref: '../spec.yml#/components/parameters/filterStartsWith' - - $ref: '../spec.yml#/components/parameters/filterEndsWith' - - $ref: '../spec.yml#/components/parameters/sort' - - $ref: '../spec.yml#/components/parameters/include' - responses: - '200': - description: A list of artists - content: - application/vnd.api+json: - schema: - type: object - required: [data, links] - properties: - data: - type: array - items: - $ref: '../spec.yml#/components/schemas/Artist' - links: - $ref: '../spec.yml#/components/schemas/PaginationLinks' - meta: - $ref: '../spec.yml#/components/schemas/PaginationMeta' - '400': - $ref: '../spec.yml#/components/responses/BadRequest' - '403': - $ref: '../spec.yml#/components/responses/NotAuthorized' - '500': - $ref: '../spec.yml#/components/responses/InternalServerError' diff --git a/api/paths/tracks.yml b/api/paths/tracks.yml deleted file mode 100644 index 3a2a11fc8..000000000 --- a/api/paths/tracks.yml +++ /dev/null @@ -1,39 +0,0 @@ -get: - summary: Get a list of tracks - operationId: getTracks - parameters: - - $ref: '../spec.yml#/components/parameters/pageLimit' - - $ref: '../spec.yml#/components/parameters/pageOffset' - - $ref: '../spec.yml#/components/parameters/filterEquals' - - $ref: '../spec.yml#/components/parameters/filterContains' - - $ref: '../spec.yml#/components/parameters/filterLessThan' - - $ref: '../spec.yml#/components/parameters/filterLessOrEqual' - - $ref: '../spec.yml#/components/parameters/filterGreaterThan' - - $ref: '../spec.yml#/components/parameters/filterGreaterOrEqual' - - $ref: '../spec.yml#/components/parameters/filterStartsWith' - - $ref: '../spec.yml#/components/parameters/filterEndsWith' - - $ref: '../spec.yml#/components/parameters/sort' - - $ref: '../spec.yml#/components/parameters/include' - responses: - '200': - description: A list of tracks - content: - application/vnd.api+json: - schema: - type: object - required: [data, links] - properties: - data: - type: array - items: - $ref: '../spec.yml#/components/schemas/Track' - links: - $ref: '../spec.yml#/components/schemas/PaginationLinks' - meta: - $ref: '../spec.yml#/components/schemas/PaginationMeta' - '400': - $ref: '../spec.yml#/components/responses/BadRequest' - '403': - $ref: '../spec.yml#/components/responses/NotAuthorized' - '500': - $ref: '../spec.yml#/components/responses/InternalServerError' diff --git a/api/paths/album.yml b/api/resources/album.yml similarity index 61% rename from api/paths/album.yml rename to api/resources/album.yml index 7dfb7b953..c04ee0b35 100644 --- a/api/paths/album.yml +++ b/api/resources/album.yml @@ -2,7 +2,7 @@ get: summary: Retrieve an individual album operationId: getAlbum parameters: - - $ref: '../spec.yml#/components/parameters/include' + - $ref: '../parameters/query/include.yml' - name: albumId in: path description: The unique identifier of the album @@ -19,10 +19,10 @@ get: required: [data] properties: data: - $ref: '../spec.yml#/components/schemas/Album' + $ref: '../schemas/Album.yml' '403': - $ref: '../spec.yml#/components/responses/NotAuthorized' + $ref: '../responses/NotAuthorized.yml' '404': - $ref: '../spec.yml#/components/responses/NotFound' + $ref: '../responses/NotFound.yml' '500': - $ref: '../spec.yml#/components/responses/InternalServerError' + $ref: '../responses/InternalServerError.yml' diff --git a/api/resources/albums.yml b/api/resources/albums.yml new file mode 100644 index 000000000..d3beedb08 --- /dev/null +++ b/api/resources/albums.yml @@ -0,0 +1,39 @@ +get: + summary: Retrieve a list of albums + operationId: getAlbums + parameters: + - $ref: '../parameters/query/pageLimit.yml' + - $ref: '../parameters/query/pageOffset.yml' + - $ref: '../parameters/query/filterEquals.yml' + - $ref: '../parameters/query/filterContains.yml' + - $ref: '../parameters/query/filterLessThan.yml' + - $ref: '../parameters/query/filterLessOrEqual.yml' + - $ref: '../parameters/query/filterGreaterThan.yml' + - $ref: '../parameters/query/filterGreaterOrEqual.yml' + - $ref: '../parameters/query/filterStartsWith.yml' + - $ref: '../parameters/query/filterEndsWith.yml' + - $ref: '../parameters/query/sort.yml' + - $ref: '../parameters/query/include.yml' + responses: + '200': + description: A list of albums + content: + application/vnd.api+json: + schema: + type: object + required: [data, links] + properties: + data: + type: array + items: + $ref: '../schemas/Album.yml' + links: + $ref: '../schemas/PaginationLinks.yml' + meta: + $ref: '../schemas/PaginationMeta.yml' + '400': + $ref: '../responses/BadRequest.yml' + '403': + $ref: '../responses/NotAuthorized.yml' + '500': + $ref: '../responses/InternalServerError.yml' diff --git a/api/paths/artist.yml b/api/resources/artist.yml similarity index 61% rename from api/paths/artist.yml rename to api/resources/artist.yml index 7bf4ed8eb..b600c8716 100644 --- a/api/paths/artist.yml +++ b/api/resources/artist.yml @@ -2,7 +2,7 @@ get: summary: Retrieve an individual artist operationId: getArtist parameters: - - $ref: '../spec.yml#/components/parameters/include' + - $ref: '../parameters/query/include.yml' - name: artistId in: path description: The unique identifier of the artist @@ -19,10 +19,10 @@ get: required: [data] properties: data: - $ref: '../spec.yml#/components/schemas/Artist' + $ref: '../schemas/Artist.yml' '403': - $ref: '../spec.yml#/components/responses/NotAuthorized' + $ref: '../responses/NotAuthorized.yml' '404': - $ref: '../spec.yml#/components/responses/NotFound' + $ref: '../responses/NotFound.yml' '500': - $ref: '../spec.yml#/components/responses/InternalServerError' + $ref: '../responses/InternalServerError.yml' diff --git a/api/resources/artists.yml b/api/resources/artists.yml new file mode 100644 index 000000000..44db745e8 --- /dev/null +++ b/api/resources/artists.yml @@ -0,0 +1,39 @@ +get: + summary: Retrieve a list of artists + operationId: getArtists + parameters: + - $ref: '../parameters/query/pageLimit.yml' + - $ref: '../parameters/query/pageOffset.yml' + - $ref: '../parameters/query/filterEquals.yml' + - $ref: '../parameters/query/filterContains.yml' + - $ref: '../parameters/query/filterLessThan.yml' + - $ref: '../parameters/query/filterLessOrEqual.yml' + - $ref: '../parameters/query/filterGreaterThan.yml' + - $ref: '../parameters/query/filterGreaterOrEqual.yml' + - $ref: '../parameters/query/filterStartsWith.yml' + - $ref: '../parameters/query/filterEndsWith.yml' + - $ref: '../parameters/query/sort.yml' + - $ref: '../parameters/query/include.yml' + responses: + '200': + description: A list of artists + content: + application/vnd.api+json: + schema: + type: object + required: [data, links] + properties: + data: + type: array + items: + $ref: '../schemas/Artist.yml' + links: + $ref: '../schemas/PaginationLinks.yml' + meta: + $ref: '../schemas/PaginationMeta.yml' + '400': + $ref: '../responses/BadRequest.yml' + '403': + $ref: '../responses/NotAuthorized.yml' + '500': + $ref: '../responses/InternalServerError.yml' diff --git a/api/paths/server.yml b/api/resources/server.yml similarity index 66% rename from api/paths/server.yml rename to api/resources/server.yml index eeb8c79ba..f57467335 100644 --- a/api/paths/server.yml +++ b/api/resources/server.yml @@ -11,8 +11,8 @@ get: required: [data] properties: data: - $ref: '../spec.yml#/components/schemas/ServerInfo' + $ref: '../schemas/ServerInfo.yml' '403': - $ref: '../spec.yml#/components/responses/NotAuthorized' + $ref: '../responses/NotAuthorized.yml' '500': - $ref: '../spec.yml#/components/responses/InternalServerError' + $ref: '../responses/InternalServerError.yml' diff --git a/api/paths/track.yml b/api/resources/track.yml similarity index 61% rename from api/paths/track.yml rename to api/resources/track.yml index a7c4c8a14..4d2d1c56a 100644 --- a/api/paths/track.yml +++ b/api/resources/track.yml @@ -2,7 +2,7 @@ get: summary: Retrieve an individual track operationId: getTrack parameters: - - $ref: '../spec.yml#/components/parameters/include' + - $ref: '../parameters/query/include.yml' - name: trackId in: path description: The unique identifier of the track @@ -19,10 +19,10 @@ get: required: [data] properties: data: - $ref: '../spec.yml#/components/schemas/Track' + $ref: '../schemas/Track.yml' '403': - $ref: '../spec.yml#/components/responses/NotAuthorized' + $ref: '../responses/NotAuthorized.yml' '404': - $ref: '../spec.yml#/components/responses/NotFound' + $ref: '../responses/NotFound.yml' '500': - $ref: '../spec.yml#/components/responses/InternalServerError' + $ref: '../responses/InternalServerError.yml' diff --git a/api/resources/tracks.yml b/api/resources/tracks.yml new file mode 100644 index 000000000..e13d25613 --- /dev/null +++ b/api/resources/tracks.yml @@ -0,0 +1,39 @@ +get: + summary: Get a list of tracks + operationId: getTracks + parameters: + - $ref: '../parameters/query/pageLimit.yml' + - $ref: '../parameters/query/pageOffset.yml' + - $ref: '../parameters/query/filterEquals.yml' + - $ref: '../parameters/query/filterContains.yml' + - $ref: '../parameters/query/filterLessThan.yml' + - $ref: '../parameters/query/filterLessOrEqual.yml' + - $ref: '../parameters/query/filterGreaterThan.yml' + - $ref: '../parameters/query/filterGreaterOrEqual.yml' + - $ref: '../parameters/query/filterStartsWith.yml' + - $ref: '../parameters/query/filterEndsWith.yml' + - $ref: '../parameters/query/sort.yml' + - $ref: '../parameters/query/include.yml' + responses: + '200': + description: A list of tracks + content: + application/vnd.api+json: + schema: + type: object + required: [data, links] + properties: + data: + type: array + items: + $ref: '../schemas/Track.yml' + links: + $ref: '../schemas/PaginationLinks.yml' + meta: + $ref: '../schemas/PaginationMeta.yml' + '400': + $ref: '../responses/BadRequest.yml' + '403': + $ref: '../responses/NotAuthorized.yml' + '500': + $ref: '../responses/InternalServerError.yml' diff --git a/api/responses/BadRequest.yml b/api/responses/BadRequest.yml new file mode 100644 index 000000000..2b3e88ed7 --- /dev/null +++ b/api/responses/BadRequest.yml @@ -0,0 +1,5 @@ +description: Bad Request +content: + application/vnd.api+json: + schema: + $ref: '../schemas/ErrorList.yml' diff --git a/api/responses/InternalServerError.yml b/api/responses/InternalServerError.yml new file mode 100644 index 000000000..8d0f6bded --- /dev/null +++ b/api/responses/InternalServerError.yml @@ -0,0 +1,5 @@ +description: Internal Server Error +content: + application/vnd.api+json: + schema: + $ref: '../schemas/ErrorList.yml' diff --git a/api/responses/NotAuthorized.yml b/api/responses/NotAuthorized.yml new file mode 100644 index 000000000..70a10441c --- /dev/null +++ b/api/responses/NotAuthorized.yml @@ -0,0 +1,5 @@ +description: Not Authorized +content: + application/vnd.api+json: + schema: + $ref: '../schemas/ErrorList.yml' diff --git a/api/responses/NotFound.yml b/api/responses/NotFound.yml new file mode 100644 index 000000000..426976152 --- /dev/null +++ b/api/responses/NotFound.yml @@ -0,0 +1,5 @@ +description: Not Found +content: + application/vnd.api+json: + schema: + $ref: '../schemas/ErrorList.yml' diff --git a/api/responses/_index.yml b/api/responses/_index.yml new file mode 100644 index 000000000..fe735b55a --- /dev/null +++ b/api/responses/_index.yml @@ -0,0 +1,8 @@ +NotFound: + $ref: './NotFound.yml' +NotAuthorized: + $ref: './NotAuthorized.yml' +BadRequest: + $ref: './BadRequest.yml' +InternalServerError: + $ref: './InternalServerError.yml' diff --git a/api/schemas/Album.yml b/api/schemas/Album.yml new file mode 100644 index 000000000..49c959be8 --- /dev/null +++ b/api/schemas/Album.yml @@ -0,0 +1,20 @@ +allOf: + - $ref: './ResourceObject.yml' + - type: object + properties: + attributes: + $ref: './AlbumAttributes.yml' + relationships: + type: object + properties: + artists: + type: array + items: + $ref: './AlbumArtistRelationship.yml' + tracks: + type: array + items: + $ref: './AlbumTrackRelationship.yml' + required: + - artists + - tracks diff --git a/api/schemas/AlbumArtistRelationship.yml b/api/schemas/AlbumArtistRelationship.yml new file mode 100644 index 000000000..39b688c15 --- /dev/null +++ b/api/schemas/AlbumArtistRelationship.yml @@ -0,0 +1,9 @@ +type: object +properties: + meta: + $ref: './ArtistMetaObject.yml' + data: + $ref: './ResourceObject.yml' +required: + - meta + - data diff --git a/api/schemas/AlbumAttributes.yml b/api/schemas/AlbumAttributes.yml new file mode 100644 index 000000000..e0386adb9 --- /dev/null +++ b/api/schemas/AlbumAttributes.yml @@ -0,0 +1,16 @@ +type: object +properties: + title: + type: string + description: The title of the album + releaseDate: + type: string + format: date + description: The release date of the album + genre: + type: string + description: The genre of the album +required: + - title + - releaseDate + - genre diff --git a/api/schemas/AlbumTrackRelationship.yml b/api/schemas/AlbumTrackRelationship.yml new file mode 100644 index 000000000..a83f5b2b6 --- /dev/null +++ b/api/schemas/AlbumTrackRelationship.yml @@ -0,0 +1,6 @@ +type: object +properties: + data: + $ref: './ResourceObject.yml' +required: + - data diff --git a/api/schemas/Artist.yml b/api/schemas/Artist.yml new file mode 100644 index 000000000..a9b2102bd --- /dev/null +++ b/api/schemas/Artist.yml @@ -0,0 +1,27 @@ +allOf: + - $ref: './ResourceObject.yml' + - type: object + properties: + attributes: + $ref: './ArtistAttributes.yml' + relationships: + type: object + properties: + tracks: + type: object + properties: + data: + type: array + items: + $ref: './ArtistTrackRelationship.yml' + required: + - data + albums: + type: object + properties: + data: + type: array + items: + $ref: './ArtistAlbumRelationship.yml' + required: + - data diff --git a/api/schemas/ArtistAlbumRelationship.yml b/api/schemas/ArtistAlbumRelationship.yml new file mode 100644 index 000000000..39b688c15 --- /dev/null +++ b/api/schemas/ArtistAlbumRelationship.yml @@ -0,0 +1,9 @@ +type: object +properties: + meta: + $ref: './ArtistMetaObject.yml' + data: + $ref: './ResourceObject.yml' +required: + - meta + - data diff --git a/api/schemas/ArtistAttributes.yml b/api/schemas/ArtistAttributes.yml new file mode 100644 index 000000000..2ebbe3729 --- /dev/null +++ b/api/schemas/ArtistAttributes.yml @@ -0,0 +1,8 @@ +type: object +properties: + name: + type: string + description: The name of the artist + bio: + type: string + description: A short biography of the artist diff --git a/api/schemas/ArtistMetaObject.yml b/api/schemas/ArtistMetaObject.yml new file mode 100644 index 000000000..7143815f4 --- /dev/null +++ b/api/schemas/ArtistMetaObject.yml @@ -0,0 +1,6 @@ +type: object +properties: + role: + $ref: './ArtistRole.yml' +required: + - role diff --git a/api/schemas/ArtistRole.yml b/api/schemas/ArtistRole.yml new file mode 100644 index 000000000..61106a350 --- /dev/null +++ b/api/schemas/ArtistRole.yml @@ -0,0 +1,5 @@ +type: string +enum: + - artist + - albumArtist +description: The role of an artist in a track or album diff --git a/api/schemas/ArtistTrackRelationship.yml b/api/schemas/ArtistTrackRelationship.yml new file mode 100644 index 000000000..0c3583e10 --- /dev/null +++ b/api/schemas/ArtistTrackRelationship.yml @@ -0,0 +1,14 @@ +type: object +properties: + meta: + type: object + properties: + role: + $ref: './ArtistRole.yml' + required: + - role + data: + $ref: './ResourceObject.yml' +required: + - meta + - data diff --git a/api/schemas/ErrorList.yml b/api/schemas/ErrorList.yml new file mode 100644 index 000000000..4fbaea236 --- /dev/null +++ b/api/schemas/ErrorList.yml @@ -0,0 +1,7 @@ +type: object +required: [errors] +properties: + errors: + type: array + items: + $ref: './ErrorObject.yml' diff --git a/api/schemas/ErrorObject.yml b/api/schemas/ErrorObject.yml new file mode 100644 index 000000000..db6501970 --- /dev/null +++ b/api/schemas/ErrorObject.yml @@ -0,0 +1,12 @@ +type: object +properties: + id: + type: string + status: + type: string + title: + type: string + detail: + type: string +required: + - errors diff --git a/api/schemas/IncludedResource.yml b/api/schemas/IncludedResource.yml new file mode 100644 index 000000000..db98610a0 --- /dev/null +++ b/api/schemas/IncludedResource.yml @@ -0,0 +1,9 @@ +oneOf: + - $ref: './Track.yml' + - $ref: './Artist.yml' +discriminator: + propertyName: type + mapping: + track: './Track.yml' + album: './Album.yml' + artist: './Artist.yml' diff --git a/api/schemas/PaginationLinks.yml b/api/schemas/PaginationLinks.yml new file mode 100644 index 000000000..31690c6a8 --- /dev/null +++ b/api/schemas/PaginationLinks.yml @@ -0,0 +1,14 @@ +type: object +properties: + first: + type: string + format: uri + prev: + type: string + format: uri + next: + type: string + format: uri + last: + type: string + format: uri diff --git a/api/schemas/PaginationMeta.yml b/api/schemas/PaginationMeta.yml new file mode 100644 index 000000000..877237652 --- /dev/null +++ b/api/schemas/PaginationMeta.yml @@ -0,0 +1,14 @@ +type: object +properties: + currentPage: + type: integer + format: int32 + description: The current page in the collection + totalPages: + type: integer + format: int32 + description: The total number of pages in the collection + totalItems: + type: integer + format: int32 + description: The total number of items in the collection diff --git a/api/schemas/ResourceList.yml b/api/schemas/ResourceList.yml new file mode 100644 index 000000000..45fc844b3 --- /dev/null +++ b/api/schemas/ResourceList.yml @@ -0,0 +1,18 @@ +type: object +properties: + data: + oneOf: + - $ref: './Track.yml' + - $ref: './Album.yml' + - $ref: './Artist.yml' + - type: array + items: + $ref: './ResourceObject.yml' + included: + type: array + items: + $ref: './IncludedResource.yml' + links: + $ref: './PaginationLinks.yml' + meta: + $ref: './PaginationMeta.yml' diff --git a/api/schemas/ResourceObject.yml b/api/schemas/ResourceObject.yml new file mode 100644 index 000000000..adbc88140 --- /dev/null +++ b/api/schemas/ResourceObject.yml @@ -0,0 +1,8 @@ +type: object +required: [id, type] +properties: + id: + type: string + description: The unique identifier for the resource + type: + $ref: './ResourceType.yml' diff --git a/api/schemas/ResourceType.yml b/api/schemas/ResourceType.yml new file mode 100644 index 000000000..70b12e7ec --- /dev/null +++ b/api/schemas/ResourceType.yml @@ -0,0 +1,6 @@ +type: string +description: The type of the resource +enum: + - album + - artist + - track diff --git a/api/schemas/ServerInfo.yml b/api/schemas/ServerInfo.yml new file mode 100644 index 000000000..58e7379e6 --- /dev/null +++ b/api/schemas/ServerInfo.yml @@ -0,0 +1,24 @@ +type: object +required: [server, serverVersion, authRequired, features] +properties: + server: + type: string + description: The name of the server software. + example: "navidrome" + serverVersion: + type: string + description: The version number of the server. + example: "0.60.0" + authRequired: + type: boolean + description: Whether the user has access to the server. + example: true + features: + type: array + description: A list of optional features the server supports. + items: + type: string + enum: + - albums + - artists + - images diff --git a/api/schemas/Track.yml b/api/schemas/Track.yml new file mode 100644 index 000000000..bd730fae7 --- /dev/null +++ b/api/schemas/Track.yml @@ -0,0 +1,8 @@ +allOf: + - $ref: './ResourceObject.yml' + - type: object + properties: + attributes: + $ref: './TrackAttributes.yml' + relationships: + $ref: './TrackRelationships.yml' diff --git a/api/schemas/TrackArtistRelationship.yml b/api/schemas/TrackArtistRelationship.yml new file mode 100644 index 000000000..39b688c15 --- /dev/null +++ b/api/schemas/TrackArtistRelationship.yml @@ -0,0 +1,9 @@ +type: object +properties: + meta: + $ref: './ArtistMetaObject.yml' + data: + $ref: './ResourceObject.yml' +required: + - meta + - data diff --git a/api/schemas/TrackAttributes.yml b/api/schemas/TrackAttributes.yml new file mode 100644 index 000000000..bb238e41d --- /dev/null +++ b/api/schemas/TrackAttributes.yml @@ -0,0 +1,55 @@ +type: object +required: [title, artist, album, albumartist, track, mimetype, duration, channels, bitrate, size] +properties: + title: + type: string + description: The title of the track + artist: # TODO: Remove + type: string + description: The name of the artist who performed the track + albumartist: # TODO: Remove + type: string + description: The primary artist of the album the track belongs to. + album: # TODO Remove + type: string + description: The name of the album the track belongs to + genre: # TODO Remove + type: string + description: The genre of the track. + track: + type: integer + description: The track number within the album. + disc: + type: integer + description: The disc number within a multi-disc album. + year: + type: integer + description: The release year of the track or album. + bpm: + type: integer + description: The beats per minute (BPM) of the track. + recording-mbid: + type: string + description: The MusicBrainz identifier for the recording of the track. + track-mbid: + type: string + description: The MusicBrainz identifier for the track. + comments: + type: string + description: Any additional comments or notes about the track. + mimetype: + type: string + description: The MIME type of the audio file. + duration: + type: number + format: float + description: The duration of the track in seconds + channels: + type: integer + description: The number of audio channels in the track. + bitrate: + type: integer + description: The bitrate of the audio file in kilobits per second (kbps). + size: + type: integer + description: The size of the audio file in bytes. diff --git a/api/schemas/TrackRelationships.yml b/api/schemas/TrackRelationships.yml new file mode 100644 index 000000000..43a69a712 --- /dev/null +++ b/api/schemas/TrackRelationships.yml @@ -0,0 +1,12 @@ +type: object +properties: + artists: + type: array + items: + $ref: './TrackArtistRelationship.yml' + albums: + type: array + items: + $ref: './AlbumTrackRelationship.yml' +required: + - artists diff --git a/api/schemas/_index.yml b/api/schemas/_index.yml new file mode 100644 index 000000000..cb371569f --- /dev/null +++ b/api/schemas/_index.yml @@ -0,0 +1,46 @@ +ServerInfo: + $ref: './ServerInfo.yml' +ResourceObject: + $ref: './ResourceObject.yml' +ResourceType: + $ref: './ResourceType.yml' +ResourceList: + $ref: './ResourceList.yml' +IncludedResource: + $ref: './IncludedResource.yml' +Track: + $ref: './Track.yml' +TrackAttributes: + $ref: './TrackAttributes.yml' +TrackRelationships: + $ref: './TrackRelationships.yml' +TrackArtistRelationship: + $ref: './TrackArtistRelationship.yml' +ArtistRole: + $ref: './ArtistRole.yml' +Artist: + $ref: './Artist.yml' +ArtistAttributes: + $ref: './ArtistAttributes.yml' +ArtistAlbumRelationship: + $ref: './ArtistAlbumRelationship.yml' +ArtistTrackRelationship: + $ref: './ArtistTrackRelationship.yml' +ArtistMetaObject: + $ref: './ArtistMetaObject.yml' +Album: + $ref: './Album.yml' +AlbumAttributes: + $ref: './AlbumAttributes.yml' +AlbumArtistRelationship: + $ref: './AlbumArtistRelationship.yml' +AlbumTrackRelationship: + $ref: './AlbumTrackRelationship.yml' +PaginationLinks: + $ref: './PaginationLinks.yml' +PaginationMeta: + $ref: './PaginationMeta.yml' +ErrorList: + $ref: './ErrorList.yml' +ErrorObject: + $ref: './ErrorObject.yml' diff --git a/api/spec.yml b/api/spec.yml index fa8f3c6f7..9bddd64ac 100644 --- a/api/spec.yml +++ b/api/spec.yml @@ -17,498 +17,24 @@ servers: paths: /server: - $ref: './paths/server.yml' + $ref: './resources/server.yml' /tracks: - $ref: './paths/tracks.yml' + $ref: './resources/tracks.yml' /tracks/{trackId}: - $ref: './paths/track.yml' + $ref: './resources/track.yml' /artists: - $ref: './paths/artists.yml' + $ref: './resources/artists.yml' /artists/{artistId}: - $ref: './paths/artist.yml' + $ref: './resources/artist.yml' /albums: - $ref: './paths/albums.yml' + $ref: './resources/albums.yml' /albums/{albumId}: - $ref: './paths/album.yml' + $ref: './resources/album.yml' components: parameters: - pageOffset: - name: page[offset] - in: query - description: The offset for pagination - required: false - schema: - type: integer - format: int32 - minimum: 0 - default: 0 - pageLimit: - name: page[limit] - in: query - description: The number of items per page - required: false - schema: - type: integer - format: int32 - minimum: 0 - default: 10 - filterEquals: - name: filter[equals] - in: query - description: 'Filter by any property with an exact match. Usage: filter[equals]=property:value' - required: false - schema: - type: array - items: - type: string - pattern: '^\w+:\w+' - filterLessThan: - name: filter[lessThan] - in: query - description: 'Filter by any numeric property less than a value. Usage: filter[lessThan]=property:value' - required: false - schema: - type: array - items: - type: string - pattern: '^\w+:\d+' - filterLessOrEqual: - name: filter[lessOrEqual] - in: query - description: 'Filter by any numeric property less than or equal to a value. Usage: filter[lessOrEqual]=property:value' - required: false - schema: - type: array - items: - type: string - pattern: '^\w+:\d+' - filterGreaterThan: - name: filter[greaterThan] - in: query - description: 'Filter by any numeric property greater than a value. Usage: filter[greaterThan]=property:value' - required: false - schema: - type: array - items: - type: string - pattern: '^\w+:\d+' - filterGreaterOrEqual: - name: filter[greaterOrEqual] - in: query - description: 'Filter by any numeric property greater than or equal to a value. Usage: filter[greaterOrEqual]=property:value' - required: false - schema: - type: array - items: - type: string - pattern: '^\w+:\d+' - filterContains: - name: filter[contains] - in: query - description: 'Filter by any property containing text. Usage: filter[contains]=property:value' - required: false - schema: - type: array - items: - type: string - pattern: '^\w+:\w+' - filterStartsWith: - name: filter[startsWith] - in: query - description: 'Filter by any property that starts with text. Usage: filter[startsWith]=property:value' - required: false - schema: - type: array - items: - type: string - pattern: '^\w+:\w+' - filterEndsWith: - name: filter[endsWith] - in: query - description: 'Filter by any property that ends with text. Usage: filter[endsWith]=property:value' - required: false - schema: - type: array - items: - type: string - pattern: '^\w+:\w+' - sort: - name: sort - in: query - description: Sort the results by one or more properties, separated by commas. Prefix the property with '-' for descending order. - required: false - schema: - type: string - include: - name: include - in: query - description: Related resources to include in the response, separated by commas - required: false - schema: - type: string + $ref: './parameters/_index.yml' schemas: - ServerInfo: - type: object - required: [server, serverVersion, authRequired, features] - properties: - server: - type: string - description: The name of the server software. - example: "navidrome" - serverVersion: - type: string - description: The version number of the server. - example: "0.60.0" - authRequired: - type: boolean - description: Whether the user has access to the server. - example: true - features: - type: array - description: A list of optional features the server supports. - items: - type: string - enum: - - albums - - artists - - images - ResourceObject: - type: object - required: [id, type] - properties: - id: - type: string - description: The unique identifier for the resource - type: - $ref: '#/components/schemas/ResourceType' - ResourceType: - type: string - description: The type of the resource - enum: - - album - - artist - - track - ResourceList: - type: object - properties: - data: - oneOf: - - $ref: '#/components/schemas/Track' - - $ref: '#/components/schemas/Album' - - $ref: '#/components/schemas/Artist' - - type: array - items: - $ref: '#/components/schemas/ResourceObject' - included: - type: array - items: - $ref: '#/components/schemas/IncludedResource' - links: - $ref: '#/components/schemas/PaginationLinks' - meta: - $ref: '#/components/schemas/PaginationMeta' - IncludedResource: - oneOf: - - $ref: '#/components/schemas/Track' - - $ref: '#/components/schemas/Artist' - discriminator: - propertyName: type - mapping: - track: '#/components/schemas/Track' - album: '#/components/schemas/Album' - artist: '#/components/schemas/Artist' - Track: - allOf: - - $ref: '#/components/schemas/ResourceObject' - - type: object - properties: - attributes: - $ref: '#/components/schemas/TrackAttributes' - relationships: - $ref: '#/components/schemas/TrackRelationships' - TrackAttributes: - type: object - required: [title, artist, album, albumartist, track, mimetype, duration, channels, bitrate, size] - properties: - title: - type: string - description: The title of the track - artist: # TODO: Remove - type: string - description: The name of the artist who performed the track - albumartist: # TODO: Remove - type: string - description: The primary artist of the album the track belongs to. - album: # TODO Remove - type: string - description: The name of the album the track belongs to - genre: # TODO Remove - type: string - description: The genre of the track. - track: - type: integer - description: The track number within the album. - disc: - type: integer - description: The disc number within a multi-disc album. - year: - type: integer - description: The release year of the track or album. - bpm: - type: integer - description: The beats per minute (BPM) of the track. - recording-mbid: - type: string - description: The MusicBrainz identifier for the recording of the track. - track-mbid: - type: string - description: The MusicBrainz identifier for the track. - comments: - type: string - description: Any additional comments or notes about the track. - mimetype: - type: string - description: The MIME type of the audio file. - duration: - type: number - format: float - description: The duration of the track in seconds - channels: - type: integer - description: The number of audio channels in the track. - bitrate: - type: integer - description: The bitrate of the audio file in kilobits per second (kbps). - size: - type: integer - description: The size of the audio file in bytes. - TrackRelationships: - type: object - properties: - artists: - type: array - items: - $ref: '#/components/schemas/TrackArtistRelationship' - albums: - type: array - items: - $ref: '#/components/schemas/AlbumTrackRelationship' - required: - - artists - TrackArtistRelationship: - type: object - properties: - meta: - $ref: '#/components/schemas/ArtistMetaObject' - data: - $ref: '#/components/schemas/ResourceObject' - required: - - meta - - data - ArtistRole: - type: string - enum: - - artist - - albumArtist - description: The role of an artist in a track or album - Artist: - allOf: - - $ref: '#/components/schemas/ResourceObject' - - type: object - properties: - attributes: - $ref: '#/components/schemas/ArtistAttributes' - relationships: - type: object - properties: - tracks: - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/ArtistTrackRelationship' - required: - - data - albums: - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/ArtistAlbumRelationship' - required: - - data - ArtistAttributes: - type: object - properties: - name: - type: string - description: The name of the artist - bio: - type: string - description: A short biography of the artist - ArtistAlbumRelationship: - type: object - properties: - meta: - $ref: '#/components/schemas/ArtistMetaObject' - data: - $ref: '#/components/schemas/ResourceObject' - required: - - meta - - data - ArtistTrackRelationship: - type: object - properties: - meta: - type: object - properties: - role: - $ref: '#/components/schemas/ArtistRole' - required: - - role - data: - $ref: '#/components/schemas/ResourceObject' - required: - - meta - - data - ArtistMetaObject: - type: object - properties: - role: - $ref: '#/components/schemas/ArtistRole' - required: - - role - Album: - allOf: - - $ref: '#/components/schemas/ResourceObject' - - type: object - properties: - attributes: - $ref: '#/components/schemas/AlbumAttributes' - relationships: - type: object - properties: - artists: - type: array - items: - $ref: '#/components/schemas/AlbumArtistRelationship' - tracks: - type: array - items: - $ref: '#/components/schemas/AlbumTrackRelationship' - required: - - artists - - tracks - AlbumAttributes: - type: object - properties: - title: - type: string - description: The title of the album - releaseDate: - type: string - format: date - description: The release date of the album - genre: - type: string - description: The genre of the album - required: - - title - - releaseDate - - genre - AlbumArtistRelationship: - type: object - properties: - meta: - $ref: '#/components/schemas/ArtistMetaObject' - data: - $ref: '#/components/schemas/ResourceObject' - required: - - meta - - data - AlbumTrackRelationship: - type: object - properties: - data: - $ref: '#/components/schemas/ResourceObject' - required: - - data - PaginationLinks: - type: object - properties: - first: - type: string - format: uri - prev: - type: string - format: uri - next: - type: string - format: uri - last: - type: string - format: uri - PaginationMeta: - type: object - properties: - currentPage: - type: integer - format: int32 - description: The current page in the collection - totalPages: - type: integer - format: int32 - description: The total numeber of pages in the collection - totalItems: - type: integer - format: int32 - description: The total number of items in the collection - ErrorList: - type: object - required: [errors] - properties: - errors: - type: array - items: - $ref: '#/components/schemas/ErrorObject' - ErrorObject: - type: object - properties: - id: - type: string - status: - type: string - title: - type: string - detail: - type: string - required: - - errors + $ref: './schemas/_index.yml' responses: - NotFound: - description: Not Found - content: - application/vnd.api+json: - schema: - $ref: '#/components/schemas/ErrorList' - NotAuthorized: - description: Not Authorized - content: - application/vnd.api+json: - schema: - $ref: '#/components/schemas/ErrorList' - BadRequest: - description: Bad Request - content: - application/vnd.api+json: - schema: - $ref: '#/components/schemas/ErrorList' - InternalServerError: - description: Internal Server Error - content: - application/vnd.api+json: - schema: - $ref: '#/components/schemas/ErrorList' + $ref: './responses/_index.yml' diff --git a/server/api/api.go b/server/api/api.go index 83fb3d82c..304e6c5c6 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -1,5 +1,6 @@ -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen -config ./openapi_api.cfg.yaml "../../api/spec.yml" -//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen -config ./openapi_types.cfg.yaml "../../api/spec.yml" +//go:generate npx swagger-cli bundle ../../api/spec.yml --outfile ../../api/openapi.yaml --type yaml +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen -config ./openapi_api.cfg.yaml "../../api/openapi.yaml" +//go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen -config ./openapi_types.cfg.yaml "../../api/openapi.yaml" package api diff --git a/server/api/openapi_api.gen.go b/server/api/openapi_api.gen.go index 47fec91a1..d400e53cf 100644 --- a/server/api/openapi_api.gen.go +++ b/server/api/openapi_api.gen.go @@ -1250,52 +1250,52 @@ func (sh *strictHandler) GetTrack(w http.ResponseWriter, r *http.Request, trackI // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/api/openapi_types.gen.go b/server/api/openapi_types.gen.go index 0f5ef82a8..8266c20d1 100644 --- a/server/api/openapi_types.gen.go +++ b/server/api/openapi_types.gen.go @@ -44,8 +44,8 @@ type Album struct { // AlbumArtistRelationship defines model for AlbumArtistRelationship. type AlbumArtistRelationship struct { - Data ResourceObject `json:"data"` - Meta interface{} `json:"meta"` + Data ResourceObject `json:"data"` + Meta ArtistMetaObject `json:"meta"` } // AlbumAttributes defines model for AlbumAttributes. @@ -146,7 +146,7 @@ type PaginationMeta struct { // TotalItems The total number of items in the collection TotalItems *int32 `json:"totalItems,omitempty"` - // TotalPages The total numeber of pages in the collection + // TotalPages The total number of pages in the collection TotalPages *int32 `json:"totalPages,omitempty"` } @@ -194,8 +194,8 @@ type Track struct { // TrackArtistRelationship defines model for TrackArtistRelationship. type TrackArtistRelationship struct { - Data ResourceObject `json:"data"` - Meta interface{} `json:"meta"` + Data ResourceObject `json:"data"` + Meta ArtistMetaObject `json:"meta"` } // TrackAttributes defines model for TrackAttributes. From 09e52eba87d43a8c334c880cafe2b9e2e00d216e Mon Sep 17 00:00:00 2001 From: Deluan Date: Mon, 22 May 2023 11:46:31 -0400 Subject: [PATCH 11/22] Change API name --- Makefile | 2 +- cmd/root.go | 2 +- cmd/wire_gen.go | 2 +- cmd/wire_injectors.go | 2 +- server/api/helpers.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index f834cfcb6..5f3189dc4 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,7 @@ wire: check_go_env ##@Development Update Dependency Injection go run github.com/google/wire/cmd/wire@latest ./... .PHONY: wire -api: check_go_env ##@Development Generate Aura API Server Boilerplate +api: check_go_env ##@Development Generate New Native API Server Boilerplate go generate ./server/api/... .PHONY: api diff --git a/cmd/root.go b/cmd/root.go index 29c4fde68..415cf12f3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -103,7 +103,7 @@ func startServer(ctx context.Context) func() error { if strings.HasPrefix(conf.Server.UILoginBackgroundURL, "/") { a.MountRouter("Background images", consts.DefaultUILoginBackgroundURL, backgrounds.NewHandler()) } - a.MountRouter("Aura API", consts.URLPathAPI, CreateAuraAPIRouter()) + a.MountRouter("New Native API", consts.URLPathAPI, CreateNewNativeAPIRouter()) return a.Run(ctx, conf.Server.Address, conf.Server.Port, conf.Server.TLSCert, conf.Server.TLSKey) } } diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index 3fc249262..47c01d948 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -45,7 +45,7 @@ func CreateNativeAPIRouter() *nativeapi.Router { return router } -func CreateAuraAPIRouter() *api.Router { +func CreateNewNativeAPIRouter() *api.Router { sqlDB := db.Db() dataStore := persistence.New(sqlDB) router := api.New(dataStore) diff --git a/cmd/wire_injectors.go b/cmd/wire_injectors.go index dea1ce9eb..ad444ee5c 100644 --- a/cmd/wire_injectors.go +++ b/cmd/wire_injectors.go @@ -48,7 +48,7 @@ func CreateNativeAPIRouter() *nativeapi.Router { )) } -func CreateAuraAPIRouter() *api.Router { +func CreateNewNativeAPIRouter() *api.Router { panic(wire.Build( allProviders, )) diff --git a/server/api/helpers.go b/server/api/helpers.go index 36d1bf078..632e8471b 100644 --- a/server/api/helpers.go +++ b/server/api/helpers.go @@ -76,7 +76,7 @@ func toArtistRelationship(id string, artist ArtistRole) TrackArtistRelationship Type: ResourceTypeArtist, Id: id, }, - Meta: struct{ Role ArtistRole }{Role: artist}, + Meta: ArtistMetaObject{Role: artist}, } } From ea231fe265d9485567bc41361ba7f698019a42ef Mon Sep 17 00:00:00 2001 From: Deluan Date: Mon, 22 May 2023 11:59:33 -0400 Subject: [PATCH 12/22] Refactor --- Makefile | 15 +++++++++--- server/api/api.go | 1 - server/api/helpers.go | 49 +++++++++++++------------------------- server/api/helpers_test.go | 35 ++++++++++++++------------- server/middlewares.go | 30 ++++++++--------------- server/middlewares_test.go | 15 +++++++++++- utils/gg/gg.go | 39 +++++++++++++++++++++--------- utils/gg/gg_test.go | 48 +++++++++++++++++++++++++++++++++---- 8 files changed, 142 insertions(+), 90 deletions(-) diff --git a/Makefile b/Makefile index 5f3189dc4..de9efaf2a 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,11 @@ test: ##@Development Run Go tests go test -race -shuffle=on ./... .PHONY: test -testall: test ##@Development Run Go and JS tests +testapi: api/openapi.yaml ##@Development Validate OpenAPI spec + @npx swagger-cli validate api/openapi.yaml +.PHONY: testapi + +testall: testapi test ##@Development Run Go and JS tests, and validate OpenAPI spec @(cd ./ui && npm test -- --watchAll=false) .PHONY: testall @@ -45,14 +49,19 @@ lintall: lint ##@Development Lint Go and JS code @(cd ./ui && npm run lint) .PHONY: lintall -wire: check_go_env ##@Development Update Dependency Injection +gen: check_go_env api ##@Development Update Generated Code (wire, mocks, openapi, etc) go run github.com/google/wire/cmd/wire@latest ./... .PHONY: wire -api: check_go_env ##@Development Generate New Native API Server Boilerplate +api: check_go_env api/openapi.yaml go generate ./server/api/... .PHONY: api +spec_parts=$(shell find api -name '*.yml') +api/openapi.yaml: $(spec_parts) + @echo "Bundling OpenAPI spec..." + npx swagger-cli bundle api/spec.yml --outfile api/openapi.yaml --type yaml + snapshots: ##@Development Update (GoLang) Snapshot tests UPDATE_SNAPSHOTS=true go run github.com/onsi/ginkgo/v2/ginkgo@latest ./server/subsonic/... .PHONY: snapshots diff --git a/server/api/api.go b/server/api/api.go index 304e6c5c6..bc3e2e619 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -1,4 +1,3 @@ -//go:generate npx swagger-cli bundle ../../api/spec.yml --outfile ../../api/openapi.yaml --type yaml //go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen -config ./openapi_api.cfg.yaml "../../api/openapi.yaml" //go:generate go run github.com/deepmap/oapi-codegen/cmd/oapi-codegen -config ./openapi_types.cfg.yaml "../../api/openapi.yaml" diff --git a/server/api/helpers.go b/server/api/helpers.go index 632e8471b..5bd2e51e7 100644 --- a/server/api/helpers.go +++ b/server/api/helpers.go @@ -15,6 +15,7 @@ import ( "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/server" + . "github.com/navidrome/navidrome/utils/gg" ) type contextKey string @@ -38,19 +39,19 @@ func toAPITrack(mf model.MediaFile) Track { Albumartist: mf.AlbumArtist, Artist: mf.Artist, Bitrate: mf.BitRate, - Bpm: p(mf.Bpm), + Bpm: P(mf.Bpm), Channels: mf.Channels, - Comments: p(mf.Comment), - Disc: p(mf.DiscNumber), + Comments: P(mf.Comment), + Disc: P(mf.DiscNumber), Duration: mf.Duration, - Genre: p(mf.Genre), + Genre: P(mf.Genre), Mimetype: mf.ContentType(), - RecordingMbid: p(mf.MbzTrackID), + RecordingMbid: P(mf.MbzTrackID), Size: int(mf.Size), Title: mf.Title, Track: mf.TrackNumber, - TrackMbid: p(mf.MbzReleaseTrackID), - Year: p(mf.Year), + TrackMbid: P(mf.MbzReleaseTrackID), + Year: P(mf.Year), }, Relationships: &TrackRelationships{ Albums: &[]AlbumTrackRelationship{toAlbumRelationship(mf)}, @@ -97,22 +98,6 @@ func toAPITracks(mfs model.MediaFiles) []Track { return tracks } -func p[T comparable](t T) *T { - var zero T - if t == zero { - return nil - } - return &t -} - -func v[T comparable](p *T) T { - var zero T - if p == nil { - return zero - } - return *p -} - // toQueryOptions convert a params struct to a model.QueryOptions struct, to be used by the // GetAll and CountAll functions. It assumes all GetXxxxParams functions have the exact same structure. func toQueryOptions(ctx context.Context, params GetTracksParams) model.QueryOptions { @@ -133,8 +118,8 @@ func toQueryOptions(ctx context.Context, params GetTracksParams) model.QueryOpti parseFilter(params.FilterGreaterOrEqual, func(f, v string) squirrel.Sqlizer { return squirrel.GtOrEq{f: v} }) parseFilter(params.FilterLessThan, func(f, v string) squirrel.Sqlizer { return squirrel.Lt{f: v} }) parseFilter(params.FilterLessOrEqual, func(f, v string) squirrel.Sqlizer { return squirrel.LtOrEq{f: v} }) - offset := v(params.PageOffset) - limit := v(params.PageLimit) + offset := V(params.PageOffset) + limit := V(params.PageLimit) sort, err := toSortParams(params.Sort) if err != nil { log.Warn(ctx, "Ignoring invalid sort parameter", err) @@ -177,15 +162,15 @@ func toSortParams(sort *string) (string, error) { return strings.Join(resultCols, ","), nil } -func apiErrorHandler(w http.ResponseWriter, r *http.Request, err error) { +func apiErrorHandler(w http.ResponseWriter, _ *http.Request, err error) { var res ErrorObject switch { case errors.Is(err, model.ErrNotAuthorized): - res = ErrorObject{Status: p(strconv.Itoa(http.StatusForbidden)), Title: p(http.StatusText(http.StatusForbidden))} + res = ErrorObject{Status: P(strconv.Itoa(http.StatusForbidden)), Title: P(http.StatusText(http.StatusForbidden))} case errors.Is(err, model.ErrNotFound): - res = ErrorObject{Status: p(strconv.Itoa(http.StatusNotFound)), Title: p(http.StatusText(http.StatusNotFound))} + res = ErrorObject{Status: P(strconv.Itoa(http.StatusNotFound)), Title: P(http.StatusText(http.StatusNotFound))} default: - res = ErrorObject{Status: p(strconv.Itoa(http.StatusInternalServerError)), Title: p(http.StatusText(http.StatusInternalServerError))} + res = ErrorObject{Status: P(strconv.Itoa(http.StatusInternalServerError)), Title: P(http.StatusText(http.StatusInternalServerError))} } w.Header().Set("Content-Type", "application/vnd.api+json") w.WriteHeader(403) @@ -196,9 +181,9 @@ func apiErrorHandler(w http.ResponseWriter, r *http.Request, err error) { func validationErrorHandler(w http.ResponseWriter, message string, statusCode int) { _ = GetTracks400JSONResponse{BadRequestJSONResponse{Errors: []ErrorObject{ { - Status: p(strconv.Itoa(statusCode)), - Title: p(http.StatusText(statusCode)), - Detail: p(message), + Status: P(strconv.Itoa(statusCode)), + Title: P(http.StatusText(statusCode)), + Detail: P(message), }, }}}.VisitGetTracksResponse(w) } diff --git a/server/api/helpers_test.go b/server/api/helpers_test.go index 57afbe3bc..fddb7b70f 100644 --- a/server/api/helpers_test.go +++ b/server/api/helpers_test.go @@ -6,6 +6,7 @@ import ( "net/http/httptest" "net/url" + . "github.com/navidrome/navidrome/utils/gg" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" ) @@ -34,44 +35,44 @@ var _ = Describe("BuildPaginationLinksAndMeta", func() { It("returns correct pagination links and meta", func() { links, meta := buildPaginationLinksAndMeta(totalItems, params, resourceName) - testLinkEquality(links.First, p("api/resource?page[offset]=0&page[limit]=10")) - testLinkEquality(links.Last, p("api/resource?page[offset]=140&page[limit]=10")) - testLinkEquality(links.Next, p("api/resource?page[offset]=10&page[limit]=10")) + testLinkEquality(links.First, P("api/resource?page[offset]=0&page[limit]=10")) + testLinkEquality(links.Last, P("api/resource?page[offset]=140&page[limit]=10")) + testLinkEquality(links.Next, P("api/resource?page[offset]=10&page[limit]=10")) Expect(links.Prev).To(BeNil()) - Expect(meta.CurrentPage).To(Equal(p(int32(1)))) - Expect(meta.TotalItems).To(Equal(p(int32(150)))) - Expect(meta.TotalPages).To(Equal(p(int32(15)))) + Expect(meta.CurrentPage).To(Equal(P(int32(1)))) + Expect(meta.TotalItems).To(Equal(P(int32(150)))) + Expect(meta.TotalPages).To(Equal(P(int32(15)))) }) }) Context("with custom page limit and offset", func() { BeforeEach(func() { params = GetTracksParams{ - PageLimit: p((PageLimit)(20)), - PageOffset: p((PageOffset)(40)), + PageLimit: P((PageLimit)(20)), + PageOffset: P((PageOffset)(40)), } }) It("returns correct pagination links and meta", func() { links, meta := buildPaginationLinksAndMeta(totalItems, params, resourceName) - testLinkEquality(links.First, p("api/resource?page[offset]=0&page[limit]=20")) - testLinkEquality(links.Last, p("api/resource?page[offset]=140&page[limit]=20")) - testLinkEquality(links.Next, p("api/resource?page[offset]=60&page[limit]=20")) - testLinkEquality(links.Prev, p("api/resource?page[offset]=20&page[limit]=20")) + testLinkEquality(links.First, P("api/resource?page[offset]=0&page[limit]=20")) + testLinkEquality(links.Last, P("api/resource?page[offset]=140&page[limit]=20")) + testLinkEquality(links.Next, P("api/resource?page[offset]=60&page[limit]=20")) + testLinkEquality(links.Prev, P("api/resource?page[offset]=20&page[limit]=20")) - Expect(meta.CurrentPage).To(Equal(p(int32(3)))) - Expect(meta.TotalItems).To(Equal(p(int32(150)))) - Expect(meta.TotalPages).To(Equal(p(int32(8)))) + Expect(meta.CurrentPage).To(Equal(P(int32(3)))) + Expect(meta.TotalItems).To(Equal(P(int32(150)))) + Expect(meta.TotalPages).To(Equal(P(int32(8)))) }) }) Context("with various filter params", func() { BeforeEach(func() { params = GetTracksParams{ - PageLimit: p((PageLimit)(20)), - PageOffset: p((PageOffset)(40)), + PageLimit: P((PageLimit)(20)), + PageOffset: P((PageOffset)(40)), FilterEquals: &[]string{"property1:value1", "property2:value2"}, FilterContains: &[]string{"property3:value3"}, FilterLessThan: &[]string{"property4:value4"}, diff --git a/server/middlewares.go b/server/middlewares.go index cdbefacb8..8591d3de0 100644 --- a/server/middlewares.go +++ b/server/middlewares.go @@ -190,41 +190,31 @@ var ( // handling the given request, as determined by the presence of X-Forwarded-* headers // or the scheme and host of the request URL. func serverAddress(r *http.Request) (scheme, host string) { - // Save the original request host for later comparison. - origHost := r.Host - // Determine the protocol of the request based on the presence of a TLS connection. protocol := "http" if r.TLS != nil { protocol = "https" } - // Get the X-Forwarded-Host header and extract the first host name if there are - // multiple hosts listed. If there is no X-Forwarded-Host header, use the original - // request host as the default. - xfh := r.Header.Get(xForwardedHost) - if xfh != "" { - i := strings.Index(xfh, ",") - if i == -1 { - i = len(xfh) - } - xfh = xfh[:i] - } - host = FirstOr(r.Host, xfh) - // Determine the protocol and scheme of the request based on the presence of // X-Forwarded-* headers or the scheme of the request URL. - scheme = FirstOr( - protocol, + scheme = Coalesce( r.Header.Get(xForwardedProto), r.Header.Get(xForwardedScheme), r.URL.Scheme, + protocol, ) + // Get the X-Forwarded-Host header and extract the first host name if there are + // multiple hosts listed. If there is no X-Forwarded-Host header, use the original + // request host as the default. + parts := strings.Split(r.Header.Get(xForwardedHost), ",") + host = Coalesce(parts[0], r.Host) + // If the request host has changed due to the X-Forwarded-Host header, log a trace // message with the original and new host values, as well as the scheme and URL. - if host != origHost { - log.Trace(r.Context(), "Request host has changed", "origHost", origHost, "host", host, "scheme", scheme, "url", r.URL) + if host != r.Host { + log.Trace(r.Context(), "Request host has changed", "origHost", r.Host, "host", host, "scheme", scheme, "url", r.URL) } // Return the scheme and host of the server handling the request. diff --git a/server/middlewares_test.go b/server/middlewares_test.go index 7823b1ef8..28ebe0971 100644 --- a/server/middlewares_test.go +++ b/server/middlewares_test.go @@ -86,7 +86,7 @@ var _ = Describe("middlewares", func() { }) }) - Context("with X-Forwarded-Host header", func() { + Context("with X-Forwarded-Host header with one host", func() { BeforeEach(func() { req, _ = http.NewRequest("GET", "http://example.com", nil) req.Header.Set("X-Forwarded-Host", "forwarded.example.com") @@ -99,6 +99,19 @@ var _ = Describe("middlewares", func() { }) }) + Context("with X-Forwarded-Host header with multiple hosts", func() { + BeforeEach(func() { + req, _ = http.NewRequest("GET", "http://example.com", nil) + req.Header.Set("X-Forwarded-Host", "forwarded.example.com,forwarded2.example.com") + }) + + It("should modify the request with the X-Forwarded-Host header value", func() { + middleware.ServeHTTP(recorder, req) + Expect(req.Host).To(Equal("forwarded.example.com")) + Expect(req.URL.Scheme).To(Equal("http")) + }) + }) + Context("with X-Forwarded-Proto header", func() { BeforeEach(func() { req, _ = http.NewRequest("GET", "http://example.com", nil) diff --git a/utils/gg/gg.go b/utils/gg/gg.go index 6da9082a1..95b2aa1e3 100644 --- a/utils/gg/gg.go +++ b/utils/gg/gg.go @@ -13,20 +13,37 @@ func If[T comparable](v T, orElse T) T { return orElse } -// FirstOr is a generic helper function that returns the first non-zero value from -// a list of comparable values, or a default value if all the values are zero. -func FirstOr[T comparable](or T, values ...T) T { - // Initialize a zero value of the same type as the input values. +// P returns a pointer to a value, or nil if the value is the zero value of the type. +func P[T comparable](t T) *T { var zero T + if t == zero { + return nil + } + return &t +} - // Loop through each input value and check if it is non-zero. If a non-zero value - // is found, return it immediately. - for _, v := range values { +// V returns the value of a pointer, or the zero value of the type if the pointer is nil. +func V[T comparable](p *T) T { + var zero T + if p == nil { + return zero + } + return *p +} + +// Coalesce returns the first non-zero value from listed arguments. +// Returns the zero value of the type parameter if no arguments are given or all are the zero value. +// Useful when you want to initialize a variable to the first non-zero value from a list of fallback values. +// +// For example: +// +// hostVal := Coalesce(hostName, os.Getenv("HOST"), "localhost") +func Coalesce[T comparable](values ...T) (v T) { + var zero T + for _, v = range values { if v != zero { - return v + return } } - - // If all the input values are zero, return the default value. - return or + return } diff --git a/utils/gg/gg_test.go b/utils/gg/gg_test.go index e492fae9d..c82079b69 100644 --- a/utils/gg/gg_test.go +++ b/utils/gg/gg_test.go @@ -43,16 +43,54 @@ var _ = Describe("GG", func() { ) }) - Describe("FirstOr", func() { + Describe("Coalesce", func() { Context("when given a list of strings", func() { It("returns the first non-empty value", func() { - Expect(gg.FirstOr("default", "foo", "bar", "baz")).To(Equal("foo")) - Expect(gg.FirstOr("default", "", "", "qux")).To(Equal("qux")) + Expect(gg.Coalesce("foo", "bar", "baz", "default")).To(Equal("foo")) + Expect(gg.Coalesce("", "", "qux", "default")).To(Equal("qux")) }) It("returns the default value if all values are empty", func() { - Expect(gg.FirstOr("default", "", "", "")).To(Equal("default")) - Expect(gg.FirstOr("", "", "", "")).To(Equal("")) + Expect(gg.Coalesce("", "", "", "default")).To(Equal("default")) + Expect(gg.Coalesce("", "", "", "")).To(Equal("")) + }) + }) + }) + + Describe("P", func() { + Context("when given a non-zero value", func() { + It("should return a non-nil pointer to that value", func() { + value := 42 + result := gg.P(value) + Expect(result).ToNot(BeNil()) + Expect(*result).To(Equal(value)) + }) + }) + + Context("when given the zero value of a type", func() { + It("should return nil", func() { + var value string + result := gg.P(value) + Expect(result).To(BeNil()) + }) + }) + }) + + Describe("V", func() { + Context("when given a non-nil pointer", func() { + It("should return the value of that pointer", func() { + value := 42 + pointer := &value + result := gg.V(pointer) + Expect(result).To(Equal(value)) + }) + }) + + Context("when given a nil pointer", func() { + It("should return the zero value of the type", func() { + var pointer *string + result := gg.V(pointer) + Expect(result).To(Equal("")) }) }) }) From 960415ed95cef7b86e376136abfe5a7c3369a97f Mon Sep 17 00:00:00 2001 From: Deluan Date: Mon, 22 May 2023 13:11:16 -0400 Subject: [PATCH 13/22] Update oapi-codegen --- go.mod | 25 +++++++++++++-- go.sum | 57 +++++++++++++++++++++++++++++---- server/api/openapi_api.gen.go | 7 ++-- server/api/openapi_types.gen.go | 2 +- 4 files changed, 77 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index 749515511..27c4b0e63 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/ReneKroon/ttlcache/v2 v2.11.0 github.com/beego/beego/v2 v2.0.7 github.com/bradleyjkemp/cupaloy/v2 v2.8.0 - github.com/deepmap/oapi-codegen v1.12.5-0.20230403173426-fd06f5aed350 + github.com/deepmap/oapi-codegen v1.12.5-0.20230513000919-14548c7e7bbe github.com/deluan/rest v0.0.0-20211101235434-380523c4bb47 github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086 @@ -18,7 +18,7 @@ require ( github.com/djherbis/stream v1.4.0 github.com/dustin/go-humanize v1.0.1 github.com/fatih/structs v1.1.0 - github.com/getkin/kin-openapi v0.115.0 + github.com/getkin/kin-openapi v0.116.0 github.com/go-chi/chi/v5 v5.0.8 github.com/go-chi/cors v1.2.1 github.com/go-chi/httprate v0.7.1 @@ -56,13 +56,20 @@ require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/bytedance/sonic v1.8.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/gin-gonic/gin v1.9.0 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/swag v0.21.1 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.11.2 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/golang/protobuf v1.5.3 // indirect @@ -76,9 +83,14 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/yaml v0.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect github.com/kr/text v0.2.0 // indirect + github.com/labstack/echo/v4 v4.10.2 // indirect + github.com/labstack/gommon v0.4.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect + github.com/leodido/go-urn v1.2.1 // indirect github.com/lestrrat-go/blackmagic v1.0.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc v1.0.4 // indirect @@ -86,8 +98,12 @@ require ( github.com/lestrrat-go/option v1.0.1 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect @@ -105,7 +121,12 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.9 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect go.uber.org/goleak v1.1.11 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect golang.org/x/crypto v0.7.0 // indirect golang.org/x/mod v0.10.0 // indirect golang.org/x/net v0.9.0 // indirect diff --git a/go.sum b/go.sum index 21d227b49..bfdaa2678 100644 --- a/go.sum +++ b/go.sum @@ -63,10 +63,16 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= +github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -82,8 +88,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= -github.com/deepmap/oapi-codegen v1.12.5-0.20230403173426-fd06f5aed350 h1:L3ObbQ6GHi8NLNaVWGlyHto/riZZx32M8JNbIcJJqP4= -github.com/deepmap/oapi-codegen v1.12.5-0.20230403173426-fd06f5aed350/go.mod h1:WMhniMLAXaHHNYz+Nk+h35qYieaeUaASStrkdkHnBr8= +github.com/deepmap/oapi-codegen v1.12.5-0.20230513000919-14548c7e7bbe h1:lgPRUK8nlnjJeXJ63G3zKxk0n1SLr90cLGWerslmFQE= +github.com/deepmap/oapi-codegen v1.12.5-0.20230513000919-14548c7e7bbe/go.mod h1:rey/E8Zmlg0o3jo02vrDZMSv6YeWY/I8j3FTeR+78EU= github.com/deluan/rest v0.0.0-20211101235434-380523c4bb47 h1:IhGAYGDi212gspq0XkYAI+DN5e9lfAIm8Qgu1wj9yN4= github.com/deluan/rest v0.0.0-20211101235434-380523c4bb47/go.mod h1:tSgDythFsl0QgS/PFWfIZqcJKnkADWneY80jaVRlqK8= github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 h1:mGvOb3zxl4vCLv+dbf7JA6CAaM2UH/AGP1KX4DsJmTI= @@ -113,8 +119,12 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/getkin/kin-openapi v0.115.0 h1:c8WHRLVY3G8m9jQTy0/DnIuljgRwTCB5twZytQS4JyU= -github.com/getkin/kin-openapi v0.115.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= +github.com/getkin/kin-openapi v0.116.0 h1:o986hwgMzR972JzOG5j6+WTwWqllZLs1EJKMKCivs2E= +github.com/getkin/kin-openapi v0.116.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= +github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= @@ -141,6 +151,13 @@ github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= +github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -249,6 +266,7 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -259,6 +277,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -271,11 +291,15 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= +github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80= github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= @@ -300,8 +324,13 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U= github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0= github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-zglob v0.0.3 h1:6Ry4EYsScDyt5di4OI6xw1bYhOqfE5S33Z1OPy+d+To= @@ -315,15 +344,16 @@ github.com/mileusna/useragent v1.2.1/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= @@ -427,14 +457,19 @@ github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= +github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/unrolled/secure v1.13.0 h1:sdr3Phw2+f8Px8HE5sd1EHdj1aV3yUwed/uZXChLFsk= github.com/unrolled/secure v1.13.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/xrash/smetrics v0.0.0-20200730060457-89a2a8a1fb0b h1:tnWgqoOBmInkt5pbLjagwNVjjT4RdJhFHzL1ebCSRh8= github.com/xrash/smetrics v0.0.0-20200730060457-89a2a8a1fb0b/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -452,6 +487,8 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -625,11 +662,15 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -811,8 +852,8 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= @@ -828,6 +869,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -849,5 +891,6 @@ modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/server/api/openapi_api.gen.go b/server/api/openapi_api.gen.go index d400e53cf..a07cd90bb 100644 --- a/server/api/openapi_api.gen.go +++ b/server/api/openapi_api.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version v1.12.5-0.20230403173426-fd06f5aed350 DO NOT EDIT. +// Code generated by github.com/deepmap/oapi-codegen version v1.12.5-0.20230513000919-14548c7e7bbe DO NOT EDIT. package api import ( @@ -1034,9 +1034,8 @@ type StrictServerInterface interface { GetTrack(ctx context.Context, request GetTrackRequestObject) (GetTrackResponseObject, error) } -type StrictHandlerFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, args interface{}) (interface{}, error) - -type StrictMiddlewareFunc func(f StrictHandlerFunc, operationID string) StrictHandlerFunc +type StrictHandlerFunc = runtime.StrictHttpHandlerFunc +type StrictMiddlewareFunc = runtime.StrictHttpMiddlewareFunc type StrictHTTPServerOptions struct { RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) diff --git a/server/api/openapi_types.gen.go b/server/api/openapi_types.gen.go index 8266c20d1..1720a598f 100644 --- a/server/api/openapi_types.gen.go +++ b/server/api/openapi_types.gen.go @@ -1,6 +1,6 @@ // Package api provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/deepmap/oapi-codegen version v1.12.5-0.20230403173426-fd06f5aed350 DO NOT EDIT. +// Code generated by github.com/deepmap/oapi-codegen version v1.12.5-0.20230513000919-14548c7e7bbe DO NOT EDIT. package api import ( From 8e03d7d013d22ffbed99bab5b30336b1ca717de0 Mon Sep 17 00:00:00 2001 From: Deluan Date: Mon, 22 May 2023 16:31:10 -0400 Subject: [PATCH 14/22] Add authorization to new API --- api/schemas/ErrorObject.yml | 2 - api/spec.yml | 8 +++ server/api/api.go | 3 + server/api/openapi_api.gen.go | 107 ++++++++++++++++++-------------- server/api/openapi_types.gen.go | 4 ++ server/auth.go | 4 +- server/auth_test.go | 12 ++++ 7 files changed, 91 insertions(+), 49 deletions(-) diff --git a/api/schemas/ErrorObject.yml b/api/schemas/ErrorObject.yml index db6501970..bc13d16f3 100644 --- a/api/schemas/ErrorObject.yml +++ b/api/schemas/ErrorObject.yml @@ -8,5 +8,3 @@ properties: type: string detail: type: string -required: - - errors diff --git a/api/spec.yml b/api/spec.yml index 9bddd64ac..d47cccb39 100644 --- a/api/spec.yml +++ b/api/spec.yml @@ -15,6 +15,9 @@ info: servers: - url: /api/v2 +security: + - bearerAuth: [] + paths: /server: $ref: './resources/server.yml' @@ -38,3 +41,8 @@ components: $ref: './schemas/_index.yml' responses: $ref: './responses/_index.yml' + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT diff --git a/server/api/api.go b/server/api/api.go index bc3e2e619..51aecfa29 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -12,6 +12,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/model" + "github.com/navidrome/navidrome/server" ) var spec = func() *openapi3.T { @@ -25,6 +26,8 @@ var spec = func() *openapi3.T { func New(ds model.DataStore) *Router { r := &Router{ds: ds} mux := chi.NewRouter() + mux.Use(server.Authenticator(ds)) + mux.Use(server.JWTRefresher) mux.Use(middleware.OapiRequestValidatorWithOptions(spec, &middleware.Options{ ErrorHandler: validationErrorHandler, })) diff --git a/server/api/openapi_api.gen.go b/server/api/openapi_api.gen.go index a07cd90bb..3ae9c5e50 100644 --- a/server/api/openapi_api.gen.go +++ b/server/api/openapi_api.gen.go @@ -60,6 +60,8 @@ func (siw *ServerInterfaceWrapper) GetAlbums(w http.ResponseWriter, r *http.Requ var err error + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetAlbumsParams @@ -185,6 +187,8 @@ func (siw *ServerInterfaceWrapper) GetAlbum(w http.ResponseWriter, r *http.Reque return } + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetAlbumParams @@ -213,6 +217,8 @@ func (siw *ServerInterfaceWrapper) GetArtists(w http.ResponseWriter, r *http.Req var err error + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetArtistsParams @@ -338,6 +344,8 @@ func (siw *ServerInterfaceWrapper) GetArtist(w http.ResponseWriter, r *http.Requ return } + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetArtistParams @@ -364,6 +372,8 @@ func (siw *ServerInterfaceWrapper) GetArtist(w http.ResponseWriter, r *http.Requ func (siw *ServerInterfaceWrapper) GetServerInfo(w http.ResponseWriter, r *http.Request) { ctx := r.Context() + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { siw.Handler.GetServerInfo(w, r) }) @@ -381,6 +391,8 @@ func (siw *ServerInterfaceWrapper) GetTracks(w http.ResponseWriter, r *http.Requ var err error + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetTracksParams @@ -506,6 +518,8 @@ func (siw *ServerInterfaceWrapper) GetTrack(w http.ResponseWriter, r *http.Reque return } + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + // Parameter object where we will unmarshal all parameters from the context var params GetTrackParams @@ -1249,52 +1263,53 @@ func (sh *strictHandler) GetTrack(w http.ResponseWriter, r *http.Request, trackI // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "zko+Xz+h25vt/wMAAP//BBDs6CVBAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/api/openapi_types.gen.go b/server/api/openapi_types.gen.go index 1720a598f..1404653ab 100644 --- a/server/api/openapi_types.gen.go +++ b/server/api/openapi_types.gen.go @@ -7,6 +7,10 @@ import ( openapi_types "github.com/deepmap/oapi-codegen/pkg/types" ) +const ( + BearerAuthScopes = "bearerAuth.Scopes" +) + // Defines values for ArtistRole. const ( ArtistRoleAlbumArtist ArtistRole = "albumArtist" diff --git a/server/auth.go b/server/auth.go index cc59fd195..e36324168 100644 --- a/server/auth.go +++ b/server/auth.go @@ -174,7 +174,9 @@ func validateLogin(userRepo model.UserRepository, userName, password string) (*m func authHeaderMapper(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { bearer := r.Header.Get(consts.UIAuthorizationHeader) - r.Header.Set("Authorization", bearer) + if bearer != "" { + r.Header.Set("Authorization", bearer) + } next.ServeHTTP(w, r) }) } diff --git a/server/auth_test.go b/server/auth_test.go index dbaa79e12..a23c12a1c 100644 --- a/server/auth_test.go +++ b/server/auth_test.go @@ -176,6 +176,18 @@ var _ = Describe("Auth", func() { w.WriteHeader(200) })).ServeHTTP(w, r) + Expect(w.Code).To(Equal(200)) + }) + It("does not override the Authorization header if x-nd-authorizer is not present", func() { + r := httptest.NewRequest("GET", "/index.html", nil) + r.Header.Set("Authorization", "original bearer") + w := httptest.NewRecorder() + + authHeaderMapper(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + Expect(r.Header.Get("Authorization")).To(Equal("original bearer")) + w.WriteHeader(200) + })).ServeHTTP(w, r) + Expect(w.Code).To(Equal(200)) }) }) From 7dfe29af5e73494ba158c3a2671a4c9514bf9eaf Mon Sep 17 00:00:00 2001 From: Deluan Date: Mon, 22 May 2023 16:40:57 -0400 Subject: [PATCH 15/22] Build OpenAPI.yaml --- .github/workflows/pipeline.yml | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index a7ec34c30..bc7b41d67 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -108,7 +108,24 @@ jobs: with: name: js-bundle path: ui/build - retention-days: 7 + + api: + name: Test and build API spec + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + + - run: make testapi + + - uses: actions/upload-artifact@v3 + with: + name: openapi.yaml + path: api/openapi.yaml binaries: name: Build binaries @@ -155,7 +172,6 @@ jobs: dist !dist/*.tar.gz !dist/*.zip - retention-days: 7 docker: name: Build and publish Docker images From fd1e06049f369ac4a36cd293fe8529a4289e8846 Mon Sep 17 00:00:00 2001 From: Deluan Date: Mon, 22 May 2023 17:46:09 -0400 Subject: [PATCH 16/22] Use redocly/cli to bundle/lint OpenAPI spec --- .github/workflows/pipeline.yml | 16 +++++++++++++--- Makefile | 14 +++++++------- api/spec.yml | 4 ++++ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index bc7b41d67..426deaa9a 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -110,7 +110,7 @@ jobs: path: ui/build api: - name: Test and build API spec + name: Build OpenAPI spec runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -120,7 +120,17 @@ jobs: cache: 'npm' cache-dependency-path: '**/package-lock.json' - - run: make testapi + - run: make lintapi + + - run: make gen + + - name: Verify no changes + run: | + git status --porcelain + if [ -n "$(git status --porcelain)" ]; then + echo 'Changes to OpenAPI spec caused changes to the code. Please review and commit the changes.' + exit 1 + fi - uses: actions/upload-artifact@v3 with: @@ -129,7 +139,7 @@ jobs: binaries: name: Build binaries - needs: [js, go, go-lint] + needs: [js, go, go-lint, api] runs-on: ubuntu-latest steps: - name: Checkout Code diff --git a/Makefile b/Makefile index de9efaf2a..eb3b05df0 100644 --- a/Makefile +++ b/Makefile @@ -32,11 +32,7 @@ test: ##@Development Run Go tests go test -race -shuffle=on ./... .PHONY: test -testapi: api/openapi.yaml ##@Development Validate OpenAPI spec - @npx swagger-cli validate api/openapi.yaml -.PHONY: testapi - -testall: testapi test ##@Development Run Go and JS tests, and validate OpenAPI spec +testall: test ##@Development Run Go and JS tests, and validate OpenAPI spec @(cd ./ui && npm test -- --watchAll=false) .PHONY: testall @@ -44,7 +40,11 @@ lint: ##@Development Lint Go code go run github.com/golangci/golangci-lint/cmd/golangci-lint@latest run -v --timeout 5m .PHONY: lint -lintall: lint ##@Development Lint Go and JS code +lintapi: api/openapi.yaml ##@Development Lint OpenAPI spec + npx @redocly/cli lint api/openapi.yaml +.PHONY: lintapi + +lintall: lint lintapi ##@Development Lint Go and JS code @(cd ./ui && npm run check-formatting) || (echo "\n\nPlease run 'npm run prettier' to fix formatting issues." && exit 1) @(cd ./ui && npm run lint) .PHONY: lintall @@ -60,7 +60,7 @@ api: check_go_env api/openapi.yaml spec_parts=$(shell find api -name '*.yml') api/openapi.yaml: $(spec_parts) @echo "Bundling OpenAPI spec..." - npx swagger-cli bundle api/spec.yml --outfile api/openapi.yaml --type yaml + npx @redocly/cli bundle api/spec.yml -o api/openapi.yaml snapshots: ##@Development Update (GoLang) Snapshot tests UPDATE_SNAPSHOTS=true go run github.com/onsi/ginkgo/v2/ginkgo@latest ./server/subsonic/... diff --git a/api/spec.yml b/api/spec.yml index d47cccb39..58f671aad 100644 --- a/api/spec.yml +++ b/api/spec.yml @@ -12,6 +12,10 @@ info: contact: name: Navidrome url: https://navidrome.org + license: + name: GNU General Public License v3.0 + url: https://github.com/navidrome/navidrome/blob/master/LICENSE + servers: - url: /api/v2 From f62231f728789b734dcf5923ba5bc61cd5c855d5 Mon Sep 17 00:00:00 2001 From: Deluan Date: Mon, 22 May 2023 17:53:00 -0400 Subject: [PATCH 17/22] Fix pipeline --- .github/workflows/pipeline.yml | 53 +++++++++---------- server/api/openapi_api.gen.go | 95 +++++++++++++++++----------------- 2 files changed, 72 insertions(+), 76 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 426deaa9a..fd3c33865 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -8,6 +8,7 @@ on: pull_request: branches: - master + jobs: go-lint: name: Lint Go code @@ -43,6 +44,28 @@ jobs: exit 1 fi + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + + - name: Build and Lint OpenAPI spec + run: make lintapi gen + + - name: Verify no changes + run: | + git status --porcelain + if [ -n "$(git status --porcelain)" ]; then + echo 'Changes to OpenAPI spec caused changes to the code. Please review and commit the changes.' + exit 1 + fi + + - uses: actions/upload-artifact@v3 + with: + name: openapi.yaml + path: api/openapi.yaml + go: name: Test with Go ${{ matrix.go_version }} runs-on: ubuntu-latest @@ -109,37 +132,9 @@ jobs: name: js-bundle path: ui/build - api: - name: Build OpenAPI spec - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 18 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - - - run: make lintapi - - - run: make gen - - - name: Verify no changes - run: | - git status --porcelain - if [ -n "$(git status --porcelain)" ]; then - echo 'Changes to OpenAPI spec caused changes to the code. Please review and commit the changes.' - exit 1 - fi - - - uses: actions/upload-artifact@v3 - with: - name: openapi.yaml - path: api/openapi.yaml - binaries: name: Build binaries - needs: [js, go, go-lint, api] + needs: [js, go, go-lint] runs-on: ubuntu-latest steps: - name: Checkout Code diff --git a/server/api/openapi_api.gen.go b/server/api/openapi_api.gen.go index 3ae9c5e50..7e8ba9531 100644 --- a/server/api/openapi_api.gen.go +++ b/server/api/openapi_api.gen.go @@ -1263,53 +1263,54 @@ func (sh *strictHandler) GetTrack(w http.ResponseWriter, r *http.Request, trackI // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "zko+Xz+h25vt/wMAAP//BBDs6CVBAAA=", + "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", + "CjpdPsPr2/X/AgAA//8ulwADnUEAAA==", } // GetSwagger returns the content of the embedded swagger specification file From 90c2d7d1cff6fcd2ca899dce104b0cb5617d618f Mon Sep 17 00:00:00 2001 From: Deluan Date: Mon, 22 May 2023 20:45:18 -0400 Subject: [PATCH 18/22] Add Albums endpoints --- api/parameters/_index.yml | 4 + api/parameters/query/includeForAlbums.yml | 13 +++ api/parameters/query/includeForTracks.yml | 13 +++ api/resources/album.yml | 2 +- api/resources/albums.yml | 2 +- api/resources/track.yml | 2 +- api/resources/tracks.yml | 4 +- api/schemas/AlbumAttributes.yml | 13 ++- server/api/api.go | 57 +++++++---- server/api/helpers.go | 48 +++++++++- server/api/openapi_api.gen.go | 109 +++++++++++----------- server/api/openapi_types.gen.go | 31 ++++-- 12 files changed, 203 insertions(+), 95 deletions(-) create mode 100644 api/parameters/query/includeForAlbums.yml create mode 100644 api/parameters/query/includeForTracks.yml diff --git a/api/parameters/_index.yml b/api/parameters/_index.yml index d06a9aac7..4280002ed 100644 --- a/api/parameters/_index.yml +++ b/api/parameters/_index.yml @@ -22,3 +22,7 @@ sort: $ref: './query/sort.yml' include: $ref: './query/include.yml' +includeForTracks: + $ref: './query/includeForTracks.yml' +includeForAlbums: + $ref: './query/includeForAlbums.yml' diff --git a/api/parameters/query/includeForAlbums.yml b/api/parameters/query/includeForAlbums.yml new file mode 100644 index 000000000..d5c24a46f --- /dev/null +++ b/api/parameters/query/includeForAlbums.yml @@ -0,0 +1,13 @@ +name: include +in: query +description: Related resources to include in the response, separated by commas +required: false +explode: false +schema: + type: array + x-go-type: includeSlice + items: + type: string + enum: + - track + - artist diff --git a/api/parameters/query/includeForTracks.yml b/api/parameters/query/includeForTracks.yml new file mode 100644 index 000000000..2e5b2125f --- /dev/null +++ b/api/parameters/query/includeForTracks.yml @@ -0,0 +1,13 @@ +name: include +in: query +description: Related resources to include in the response, separated by commas +required: false +explode: false +schema: + type: array + x-go-type: includeSlice + items: + type: string + enum: + - album + - artist diff --git a/api/resources/album.yml b/api/resources/album.yml index c04ee0b35..dfb3a85e3 100644 --- a/api/resources/album.yml +++ b/api/resources/album.yml @@ -2,7 +2,7 @@ get: summary: Retrieve an individual album operationId: getAlbum parameters: - - $ref: '../parameters/query/include.yml' + - $ref: '../parameters/query/includeForAlbums.yml' - name: albumId in: path description: The unique identifier of the album diff --git a/api/resources/albums.yml b/api/resources/albums.yml index d3beedb08..7e9e7d5f6 100644 --- a/api/resources/albums.yml +++ b/api/resources/albums.yml @@ -13,7 +13,7 @@ get: - $ref: '../parameters/query/filterStartsWith.yml' - $ref: '../parameters/query/filterEndsWith.yml' - $ref: '../parameters/query/sort.yml' - - $ref: '../parameters/query/include.yml' + - $ref: '../parameters/query/includeForAlbums.yml' responses: '200': description: A list of albums diff --git a/api/resources/track.yml b/api/resources/track.yml index 4d2d1c56a..9ab27f812 100644 --- a/api/resources/track.yml +++ b/api/resources/track.yml @@ -2,7 +2,7 @@ get: summary: Retrieve an individual track operationId: getTrack parameters: - - $ref: '../parameters/query/include.yml' + - $ref: '../parameters/query/includeForTracks.yml' - name: trackId in: path description: The unique identifier of the track diff --git a/api/resources/tracks.yml b/api/resources/tracks.yml index e13d25613..931744704 100644 --- a/api/resources/tracks.yml +++ b/api/resources/tracks.yml @@ -1,5 +1,5 @@ get: - summary: Get a list of tracks + summary: Retrieve a list of tracks operationId: getTracks parameters: - $ref: '../parameters/query/pageLimit.yml' @@ -13,7 +13,7 @@ get: - $ref: '../parameters/query/filterStartsWith.yml' - $ref: '../parameters/query/filterEndsWith.yml' - $ref: '../parameters/query/sort.yml' - - $ref: '../parameters/query/include.yml' + - $ref: '../parameters/query/includeForTracks.yml' responses: '200': description: A list of tracks diff --git a/api/schemas/AlbumAttributes.yml b/api/schemas/AlbumAttributes.yml index e0386adb9..00ce3fb1e 100644 --- a/api/schemas/AlbumAttributes.yml +++ b/api/schemas/AlbumAttributes.yml @@ -3,14 +3,21 @@ properties: title: type: string description: The title of the album + artist: + type: string + description: The artist of the album releaseDate: type: string - format: date description: The release date of the album + tracktotal: + type: integer + description: The number of tracks on the album + disctotal: + type: integer + description: The number of discs in the album genre: type: string description: The genre of the album required: - title - - releaseDate - - genre + - artist diff --git a/server/api/api.go b/server/api/api.go index 51aecfa29..5de817923 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -50,26 +50,6 @@ type Router struct { ds model.DataStore } -func (a *Router) GetAlbums(ctx context.Context, request GetAlbumsRequestObject) (GetAlbumsResponseObject, error) { - //TODO implement me - panic("implement me") -} - -func (a *Router) GetAlbum(ctx context.Context, request GetAlbumRequestObject) (GetAlbumResponseObject, error) { - //TODO implement me - panic("implement me") -} - -func (a *Router) GetArtists(ctx context.Context, request GetArtistsRequestObject) (GetArtistsResponseObject, error) { - //TODO implement me - panic("implement me") -} - -func (a *Router) GetArtist(ctx context.Context, request GetArtistRequestObject) (GetArtistResponseObject, error) { - //TODO implement me - panic("implement me") -} - func (a *Router) GetServerInfo(_ context.Context, _ GetServerInfoRequestObject) (GetServerInfoResponseObject, error) { return GetServerInfo200JSONResponse{ Data: ServerInfo{ @@ -107,3 +87,40 @@ func (a *Router) GetTrack(ctx context.Context, request GetTrackRequestObject) (G } return GetTrack200JSONResponse{Data: toAPITrack(*mf)}, nil } + +func (a *Router) GetAlbums(ctx context.Context, request GetAlbumsRequestObject) (GetAlbumsResponseObject, error) { + options := toQueryOptions(ctx, request.Params) + albums, err := a.ds.Album(ctx).GetAll(options) + if err != nil { + return nil, err + } + cnt, err := a.ds.MediaFile(ctx).CountAll(options) + if err != nil { + return nil, err + } + baseUrl := baseResourceUrl(ctx, "albums") + links, meta := buildPaginationLinksAndMeta(int32(cnt), request.Params, baseUrl) + return GetAlbums200JSONResponse{ + Data: toAPIAlbums(albums), + Links: links, + Meta: &meta, + }, nil +} + +func (a *Router) GetAlbum(ctx context.Context, request GetAlbumRequestObject) (GetAlbumResponseObject, error) { + album, err := a.ds.Album(ctx).Get(request.AlbumId) + if err != nil { + return nil, err + } + return GetAlbum200JSONResponse{Data: toAPIAlbum(*album)}, nil +} + +func (a *Router) GetArtists(ctx context.Context, request GetArtistsRequestObject) (GetArtistsResponseObject, error) { + //TODO implement me + panic("implement me") +} + +func (a *Router) GetArtist(ctx context.Context, request GetArtistRequestObject) (GetArtistResponseObject, error) { + //TODO implement me + panic("implement me") +} diff --git a/server/api/helpers.go b/server/api/helpers.go index 5bd2e51e7..c1b09276f 100644 --- a/server/api/helpers.go +++ b/server/api/helpers.go @@ -22,6 +22,8 @@ type contextKey string const requestInContext contextKey = "request" +type includeSlice []string + // storeRequestInContext is a middleware function that adds the full request object to the context. func storeRequestInContext(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -98,9 +100,40 @@ func toAPITracks(mfs model.MediaFiles) []Track { return tracks } +func toAPIAlbum(ma model.Album) Album { + return Album{ + Type: ResourceTypeAlbum, + Id: ma.ID, + Attributes: &AlbumAttributes{ + Artist: ma.AlbumArtist, + Genre: P(ma.Genre), + ReleaseDate: P(ma.ReleaseDate), + Title: ma.Name, + Tracktotal: P(ma.SongCount), + }, + } +} + +func toAPIAlbums(mas model.Albums) []Album { + albums := make([]Album, len(mas)) + for i := range mas { + albums[i] = toAPIAlbum(mas[i]) + } + return albums +} + +type GetParams interface { + GetParams() GetTracksParams +} + +func (p GetTracksParams) GetParams() GetTracksParams { return p } + +func (p GetAlbumsParams) GetParams() GetTracksParams { return GetTracksParams(p) } + // toQueryOptions convert a params struct to a model.QueryOptions struct, to be used by the // GetAll and CountAll functions. It assumes all GetXxxxParams functions have the exact same structure. -func toQueryOptions(ctx context.Context, params GetTracksParams) model.QueryOptions { +func toQueryOptions(ctx context.Context, p GetParams) model.QueryOptions { + params := p.GetParams() var filters squirrel.And parseFilter := func(fs *[]string, op func(f, v string) squirrel.Sqlizer) { if fs != nil { @@ -188,7 +221,8 @@ func validationErrorHandler(w http.ResponseWriter, message string, statusCode in }}}.VisitGetTracksResponse(w) } -func buildPaginationLinksAndMeta(totalItems int32, params GetTracksParams, resourceName string) (PaginationLinks, PaginationMeta) { +func buildPaginationLinksAndMeta(totalItems int32, p GetParams, resourceName string) (PaginationLinks, PaginationMeta) { + params := p.GetParams() pageLimit := *params.PageLimit pageOffset := *params.PageOffset @@ -230,7 +264,7 @@ func buildPaginationLinksAndMeta(totalItems int32, params GetTracksParams, resou query.Add("sort", *params.Sort) } if params.Include != nil { - query.Add("include", *params.Include) + query.Add("include", A(*params.Include)) } link := resourceName @@ -268,6 +302,14 @@ func buildPaginationLinksAndMeta(totalItems int32, params GetTracksParams, resou return links, meta } +func A[T any](slice []T) string { + var buf []string + for _, v := range slice { + buf = append(buf, fmt.Sprintf("%v", v)) + } + return strings.Join(buf, ",") +} + func baseResourceUrl(ctx context.Context, resourceName string) string { r := ctx.Value(requestInContext).(*http.Request) baseUrl, _ := url.JoinPath(spec.Servers[0].URL, resourceName) diff --git a/server/api/openapi_api.gen.go b/server/api/openapi_api.gen.go index 7e8ba9531..1f9d681c9 100644 --- a/server/api/openapi_api.gen.go +++ b/server/api/openapi_api.gen.go @@ -37,7 +37,7 @@ type ServerInterface interface { // Get server's global info // (GET /server) GetServerInfo(w http.ResponseWriter, r *http.Request) - // Get a list of tracks + // Retrieve a list of tracks // (GET /tracks) GetTracks(w http.ResponseWriter, r *http.Request, params GetTracksParams) // Retrieve an individual track @@ -155,7 +155,7 @@ func (siw *ServerInterfaceWrapper) GetAlbums(w http.ResponseWriter, r *http.Requ // ------------- Optional query parameter "include" ------------- - err = runtime.BindQueryParameter("form", true, false, "include", r.URL.Query(), ¶ms.Include) + err = runtime.BindQueryParameter("form", false, false, "include", r.URL.Query(), ¶ms.Include) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "include", Err: err}) return @@ -194,7 +194,7 @@ func (siw *ServerInterfaceWrapper) GetAlbum(w http.ResponseWriter, r *http.Reque // ------------- Optional query parameter "include" ------------- - err = runtime.BindQueryParameter("form", true, false, "include", r.URL.Query(), ¶ms.Include) + err = runtime.BindQueryParameter("form", false, false, "include", r.URL.Query(), ¶ms.Include) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "include", Err: err}) return @@ -486,7 +486,7 @@ func (siw *ServerInterfaceWrapper) GetTracks(w http.ResponseWriter, r *http.Requ // ------------- Optional query parameter "include" ------------- - err = runtime.BindQueryParameter("form", true, false, "include", r.URL.Query(), ¶ms.Include) + err = runtime.BindQueryParameter("form", false, false, "include", r.URL.Query(), ¶ms.Include) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "include", Err: err}) return @@ -525,7 +525,7 @@ func (siw *ServerInterfaceWrapper) GetTrack(w http.ResponseWriter, r *http.Reque // ------------- Optional query parameter "include" ------------- - err = runtime.BindQueryParameter("form", true, false, "include", r.URL.Query(), ¶ms.Include) + err = runtime.BindQueryParameter("form", false, false, "include", r.URL.Query(), ¶ms.Include) if err != nil { siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "include", Err: err}) return @@ -1040,7 +1040,7 @@ type StrictServerInterface interface { // Get server's global info // (GET /server) GetServerInfo(ctx context.Context, request GetServerInfoRequestObject) (GetServerInfoResponseObject, error) - // Get a list of tracks + // Retrieve a list of tracks // (GET /tracks) GetTracks(ctx context.Context, request GetTracksRequestObject) (GetTracksResponseObject, error) // Retrieve an individual track @@ -1263,54 +1263,55 @@ func (sh *strictHandler) GetTrack(w http.ResponseWriter, r *http.Request, trackI // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "CjpdPsPr2/X/AgAA//8ulwADnUEAAA==", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/api/openapi_types.gen.go b/server/api/openapi_types.gen.go index 1404653ab..fd8a0ab33 100644 --- a/server/api/openapi_types.gen.go +++ b/server/api/openapi_types.gen.go @@ -3,10 +3,6 @@ // Code generated by github.com/deepmap/oapi-codegen version v1.12.5-0.20230513000919-14548c7e7bbe DO NOT EDIT. package api -import ( - openapi_types "github.com/deepmap/oapi-codegen/pkg/types" -) - const ( BearerAuthScopes = "bearerAuth.Scopes" ) @@ -54,14 +50,23 @@ type AlbumArtistRelationship struct { // AlbumAttributes defines model for AlbumAttributes. type AlbumAttributes struct { + // Artist The artist of the album + Artist string `json:"artist"` + + // Disctotal The number of discs in the album + Disctotal *int `json:"disctotal,omitempty"` + // Genre The genre of the album - Genre string `json:"genre"` + Genre *string `json:"genre,omitempty"` // ReleaseDate The release date of the album - ReleaseDate openapi_types.Date `json:"releaseDate"` + ReleaseDate *string `json:"releaseDate,omitempty"` // Title The title of the album Title string `json:"title"` + + // Tracktotal The number of tracks on the album + Tracktotal *int `json:"tracktotal,omitempty"` } // AlbumTrackRelationship defines model for AlbumTrackRelationship. @@ -289,6 +294,12 @@ type FilterStartsWith = []string // Include defines model for include. type Include = string +// IncludeForAlbums defines model for includeForAlbums. +type IncludeForAlbums = includeSlice + +// IncludeForTracks defines model for includeForTracks. +type IncludeForTracks = includeSlice + // PageLimit defines model for pageLimit. type PageLimit = int32 @@ -334,13 +345,13 @@ type GetAlbumsParams struct { Sort *Sort `form:"sort,omitempty" json:"sort,omitempty"` // Include Related resources to include in the response, separated by commas - Include *Include `form:"include,omitempty" json:"include,omitempty"` + Include *IncludeForAlbums `form:"include,omitempty" json:"include,omitempty"` } // GetAlbumParams defines parameters for GetAlbum. type GetAlbumParams struct { // Include Related resources to include in the response, separated by commas - Include *Include `form:"include,omitempty" json:"include,omitempty"` + Include *IncludeForAlbums `form:"include,omitempty" json:"include,omitempty"` } // GetArtistsParams defines parameters for GetArtists. @@ -424,11 +435,11 @@ type GetTracksParams struct { Sort *Sort `form:"sort,omitempty" json:"sort,omitempty"` // Include Related resources to include in the response, separated by commas - Include *Include `form:"include,omitempty" json:"include,omitempty"` + Include *IncludeForTracks `form:"include,omitempty" json:"include,omitempty"` } // GetTrackParams defines parameters for GetTrack. type GetTrackParams struct { // Include Related resources to include in the response, separated by commas - Include *Include `form:"include,omitempty" json:"include,omitempty"` + Include *IncludeForTracks `form:"include,omitempty" json:"include,omitempty"` } From 4507895d58002ac9305eaad5e9e147992de37d1d Mon Sep 17 00:00:00 2001 From: Deluan Date: Tue, 23 May 2023 18:36:38 -0400 Subject: [PATCH 19/22] Add includes for /tracks --- api/resources/track.yml | 4 + api/resources/tracks.yml | 4 + api/schemas/ArtistAttributes.yml | 2 + api/schemas/IncludedResource.yml | 1 + model/mediafile.go | 16 ++++ server/api/api.go | 30 ++++++- server/api/helpers.go | 19 +++++ server/api/includes.go | 60 ++++++++++++++ server/api/openapi_api.gen.go | 110 +++++++++++++------------ server/api/openapi_types.gen.go | 133 ++++++++++++++++++++++++++++++- 10 files changed, 321 insertions(+), 58 deletions(-) create mode 100644 server/api/includes.go diff --git a/api/resources/track.yml b/api/resources/track.yml index 9ab27f812..86b701058 100644 --- a/api/resources/track.yml +++ b/api/resources/track.yml @@ -20,6 +20,10 @@ get: properties: data: $ref: '../schemas/Track.yml' + included: + type: array + items: + $ref: '../schemas/IncludedResource.yml' '403': $ref: '../responses/NotAuthorized.yml' '404': diff --git a/api/resources/tracks.yml b/api/resources/tracks.yml index 931744704..f3a8a2a71 100644 --- a/api/resources/tracks.yml +++ b/api/resources/tracks.yml @@ -31,6 +31,10 @@ get: $ref: '../schemas/PaginationLinks.yml' meta: $ref: '../schemas/PaginationMeta.yml' + included: + type: array + items: + $ref: '../schemas/IncludedResource.yml' '400': $ref: '../responses/BadRequest.yml' '403': diff --git a/api/schemas/ArtistAttributes.yml b/api/schemas/ArtistAttributes.yml index 2ebbe3729..252668a3c 100644 --- a/api/schemas/ArtistAttributes.yml +++ b/api/schemas/ArtistAttributes.yml @@ -6,3 +6,5 @@ properties: bio: type: string description: A short biography of the artist +required: + - name diff --git a/api/schemas/IncludedResource.yml b/api/schemas/IncludedResource.yml index db98610a0..8887e0554 100644 --- a/api/schemas/IncludedResource.yml +++ b/api/schemas/IncludedResource.yml @@ -1,5 +1,6 @@ oneOf: - $ref: './Track.yml' + - $ref: './Album.yml' - $ref: './Artist.yml' discriminator: propertyName: type diff --git a/model/mediafile.go b/model/mediafile.go index 9caf54e94..891193e80 100644 --- a/model/mediafile.go +++ b/model/mediafile.go @@ -183,6 +183,22 @@ func (mfs MediaFiles) ToAlbum() Album { return a } +func (mfs MediaFiles) ArtistIDs() []string { + var ids []string + for _, mf := range mfs { + ids = append(ids, mf.ArtistID, mf.AlbumArtistID) + } + return ids +} + +func (mfs MediaFiles) AlbumIDs() []string { + var ids []string + for _, mf := range mfs { + ids = append(ids, mf.AlbumID) + } + return ids +} + func allOrNothing(items []string) (string, int) { items = slices.Compact(items) if len(items) == 1 { diff --git a/server/api/api.go b/server/api/api.go index 5de817923..5dc0ab4da 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -73,10 +73,20 @@ func (a *Router) GetTracks(ctx context.Context, request GetTracksRequestObject) } baseUrl := baseResourceUrl(ctx, "tracks") links, meta := buildPaginationLinksAndMeta(int32(cnt), request.Params, baseUrl) + resources := includedResources{ctx: ctx, ds: a.ds, includes: request.Params.Include} + err = resources.AddArtists(mfs.ArtistIDs()...) + if err != nil { + return nil, err + } + err = resources.AddAlbums(mfs.AlbumIDs()...) + if err != nil { + return nil, err + } return GetTracks200JSONResponse{ - Data: toAPITracks(mfs), - Links: links, - Meta: &meta, + Data: toAPITracks(mfs), + Links: links, + Meta: &meta, + Included: resources.Build(), }, nil } @@ -85,7 +95,19 @@ func (a *Router) GetTrack(ctx context.Context, request GetTrackRequestObject) (G if err != nil { return nil, err } - return GetTrack200JSONResponse{Data: toAPITrack(*mf)}, nil + resources := includedResources{ctx: ctx, ds: a.ds, includes: request.Params.Include} + err = resources.AddArtists(mf.ArtistID, mf.AlbumArtistID) + if err != nil { + return nil, err + } + err = resources.AddAlbums(mf.AlbumID) + if err != nil { + return nil, err + } + return GetTrack200JSONResponse{ + Data: toAPITrack(*mf), + Included: resources.Build(), + }, nil } func (a *Router) GetAlbums(ctx context.Context, request GetAlbumsRequestObject) (GetAlbumsResponseObject, error) { diff --git a/server/api/helpers.go b/server/api/helpers.go index c1b09276f..bf7fe6f5a 100644 --- a/server/api/helpers.go +++ b/server/api/helpers.go @@ -122,6 +122,25 @@ func toAPIAlbums(mas model.Albums) []Album { return albums } +func toAPIArtist(ma model.Artist) Artist { + return Artist{ + Type: ResourceTypeArtist, + Id: ma.ID, + Attributes: &ArtistAttributes{ + Name: ma.Name, + Bio: P(ma.Biography), + }, + } +} + +func toAPIArtists(mas model.Artists) []Artist { + artists := make([]Artist, len(mas)) + for i := range mas { + artists[i] = toAPIArtist(mas[i]) + } + return artists +} + type GetParams interface { GetParams() GetTracksParams } diff --git a/server/api/includes.go b/server/api/includes.go new file mode 100644 index 000000000..d93b2a81c --- /dev/null +++ b/server/api/includes.go @@ -0,0 +1,60 @@ +package api + +import ( + "context" + "sort" + + "github.com/Masterminds/squirrel" + "github.com/navidrome/navidrome/model" + "golang.org/x/exp/slices" +) + +type includedResources struct { + ctx context.Context + ds model.DataStore + includes *includeSlice + resources []IncludedResource +} + +func (i *includedResources) AddAlbums(albumIds ...string) error { + if i.includes == nil || !slices.Contains(*i.includes, "album") { + return nil + } + sort.Strings(albumIds) + slices.Compact(albumIds) + albums, err := i.ds.Album(i.ctx).GetAll(model.QueryOptions{Filters: squirrel.Eq{"id": albumIds}}) + if err != nil { + return err + } + for _, al := range albums { + inc := &IncludedResource{} + _ = inc.FromAlbum(toAPIAlbum(al)) + i.resources = append(i.resources, *inc) + } + return nil +} + +func (i *includedResources) AddArtists(artistIds ...string) error { + if i.includes == nil || !slices.Contains(*i.includes, "artist") { + return nil + } + sort.Strings(artistIds) + slices.Compact(artistIds) + artists, err := i.ds.Artist(i.ctx).GetAll(model.QueryOptions{Filters: squirrel.Eq{"artist.id": artistIds}}) + if err != nil { + return err + } + for _, ar := range artists { + inc := &IncludedResource{} + _ = inc.FromArtist(toAPIArtist(ar)) + i.resources = append(i.resources, *inc) + } + return nil +} + +func (i *includedResources) Build() *[]IncludedResource { + if len(i.resources) == 0 { + return nil + } + return &i.resources +} diff --git a/server/api/openapi_api.gen.go b/server/api/openapi_api.gen.go index 1f9d681c9..248266235 100644 --- a/server/api/openapi_api.gen.go +++ b/server/api/openapi_api.gen.go @@ -933,9 +933,10 @@ type GetTracksResponseObject interface { } type GetTracks200JSONResponse struct { - Data []Track `json:"data"` - Links PaginationLinks `json:"links"` - Meta *PaginationMeta `json:"meta,omitempty"` + Data []Track `json:"data"` + Included *[]IncludedResource `json:"included,omitempty"` + Links PaginationLinks `json:"links"` + Meta *PaginationMeta `json:"meta,omitempty"` } func (response GetTracks200JSONResponse) VisitGetTracksResponse(w http.ResponseWriter) error { @@ -984,7 +985,8 @@ type GetTrackResponseObject interface { } type GetTrack200JSONResponse struct { - Data Track `json:"data"` + Data Track `json:"data"` + Included *[]IncludedResource `json:"included,omitempty"` } func (response GetTrack200JSONResponse) VisitGetTrackResponse(w http.ResponseWriter) error { @@ -1263,55 +1265,57 @@ func (sh *strictHandler) GetTrack(w http.ResponseWriter, r *http.Request, trackI // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "///+0r9yf0YAAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/api/openapi_types.gen.go b/server/api/openapi_types.gen.go index fd8a0ab33..7d5a9b58c 100644 --- a/server/api/openapi_types.gen.go +++ b/server/api/openapi_types.gen.go @@ -3,6 +3,13 @@ // Code generated by github.com/deepmap/oapi-codegen version v1.12.5-0.20230513000919-14548c7e7bbe DO NOT EDIT. package api +import ( + "encoding/json" + "errors" + + "github.com/deepmap/oapi-codegen/pkg/runtime" +) + const ( BearerAuthScopes = "bearerAuth.Scopes" ) @@ -105,7 +112,7 @@ type ArtistAttributes struct { Bio *string `json:"bio,omitempty"` // Name The name of the artist - Name *string `json:"name,omitempty"` + Name string `json:"name"` } // ArtistMetaObject defines model for ArtistMetaObject. @@ -139,6 +146,11 @@ type ErrorObject struct { Title *string `json:"title,omitempty"` } +// IncludedResource defines model for IncludedResource. +type IncludedResource struct { + union json.RawMessage +} + // PaginationLinks defines model for PaginationLinks. type PaginationLinks struct { First *string `json:"first,omitempty"` @@ -443,3 +455,122 @@ type GetTrackParams struct { // Include Related resources to include in the response, separated by commas Include *IncludeForTracks `form:"include,omitempty" json:"include,omitempty"` } + +// AsTrack returns the union data inside the IncludedResource as a Track +func (t IncludedResource) AsTrack() (Track, error) { + var body Track + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromTrack overwrites any union data inside the IncludedResource as the provided Track +func (t *IncludedResource) FromTrack(v Track) error { + v.Type = "track" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeTrack performs a merge with any union data inside the IncludedResource, using the provided Track +func (t *IncludedResource) MergeTrack(v Track) error { + v.Type = "track" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsAlbum returns the union data inside the IncludedResource as a Album +func (t IncludedResource) AsAlbum() (Album, error) { + var body Album + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromAlbum overwrites any union data inside the IncludedResource as the provided Album +func (t *IncludedResource) FromAlbum(v Album) error { + v.Type = "album" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeAlbum performs a merge with any union data inside the IncludedResource, using the provided Album +func (t *IncludedResource) MergeAlbum(v Album) error { + v.Type = "album" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +// AsArtist returns the union data inside the IncludedResource as a Artist +func (t IncludedResource) AsArtist() (Artist, error) { + var body Artist + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromArtist overwrites any union data inside the IncludedResource as the provided Artist +func (t *IncludedResource) FromArtist(v Artist) error { + v.Type = "artist" + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeArtist performs a merge with any union data inside the IncludedResource, using the provided Artist +func (t *IncludedResource) MergeArtist(v Artist) error { + v.Type = "artist" + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JsonMerge(t.union, b) + t.union = merged + return err +} + +func (t IncludedResource) Discriminator() (string, error) { + var discriminator struct { + Discriminator string `json:"type"` + } + err := json.Unmarshal(t.union, &discriminator) + return discriminator.Discriminator, err +} + +func (t IncludedResource) ValueByDiscriminator() (interface{}, error) { + discriminator, err := t.Discriminator() + if err != nil { + return nil, err + } + switch discriminator { + case "album": + return t.AsAlbum() + case "artist": + return t.AsArtist() + case "track": + return t.AsTrack() + default: + return nil, errors.New("unknown discriminator value: " + discriminator) + } +} + +func (t IncludedResource) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *IncludedResource) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} From 4a8f176f0d679f7e7a984f6817604b1d4d099411 Mon Sep 17 00:00:00 2001 From: Deluan Date: Tue, 23 May 2023 19:22:36 -0400 Subject: [PATCH 20/22] Add includes for /albums --- api/parameters/query/includeForAlbum.yml | 13 +++ api/parameters/query/includeForAlbums.yml | 1 - api/resources/album.yml | 6 +- api/resources/albums.yml | 4 + model/album.go | 9 ++ server/api/api.go | 26 ++++- server/api/helpers.go | 8 -- server/api/includes.go | 24 ++++- server/api/openapi_api.gen.go | 112 +++++++++++----------- server/api/openapi_types.gen.go | 5 +- 10 files changed, 135 insertions(+), 73 deletions(-) create mode 100644 api/parameters/query/includeForAlbum.yml diff --git a/api/parameters/query/includeForAlbum.yml b/api/parameters/query/includeForAlbum.yml new file mode 100644 index 000000000..d5c24a46f --- /dev/null +++ b/api/parameters/query/includeForAlbum.yml @@ -0,0 +1,13 @@ +name: include +in: query +description: Related resources to include in the response, separated by commas +required: false +explode: false +schema: + type: array + x-go-type: includeSlice + items: + type: string + enum: + - track + - artist diff --git a/api/parameters/query/includeForAlbums.yml b/api/parameters/query/includeForAlbums.yml index d5c24a46f..411102451 100644 --- a/api/parameters/query/includeForAlbums.yml +++ b/api/parameters/query/includeForAlbums.yml @@ -9,5 +9,4 @@ schema: items: type: string enum: - - track - artist diff --git a/api/resources/album.yml b/api/resources/album.yml index dfb3a85e3..f588f870b 100644 --- a/api/resources/album.yml +++ b/api/resources/album.yml @@ -2,7 +2,7 @@ get: summary: Retrieve an individual album operationId: getAlbum parameters: - - $ref: '../parameters/query/includeForAlbums.yml' + - $ref: '../parameters/query/includeForAlbum.yml' - name: albumId in: path description: The unique identifier of the album @@ -20,6 +20,10 @@ get: properties: data: $ref: '../schemas/Album.yml' + included: + type: array + items: + $ref: '../schemas/IncludedResource.yml' '403': $ref: '../responses/NotAuthorized.yml' '404': diff --git a/api/resources/albums.yml b/api/resources/albums.yml index 7e9e7d5f6..1b455495a 100644 --- a/api/resources/albums.yml +++ b/api/resources/albums.yml @@ -31,6 +31,10 @@ get: $ref: '../schemas/PaginationLinks.yml' meta: $ref: '../schemas/PaginationMeta.yml' + included: + type: array + items: + $ref: '../schemas/IncludedResource.yml' '400': $ref: '../responses/BadRequest.yml' '403': diff --git a/model/album.go b/model/album.go index cae0f427a..578486aa3 100644 --- a/model/album.go +++ b/model/album.go @@ -1,6 +1,7 @@ package model import ( + "strings" "time" "github.com/navidrome/navidrome/utils/slice" @@ -91,6 +92,14 @@ func (als Albums) ToAlbumArtist() Artist { return a } +func (als Albums) ArtistIDs() []string { + var ids []string + for _, al := range als { + ids = append(ids, strings.Split(al.AllArtistIDs, " ")...) + } + return ids +} + type AlbumRepository interface { CountAll(...QueryOptions) (int64, error) Exists(id string) (bool, error) diff --git a/server/api/api.go b/server/api/api.go index 5dc0ab4da..42bac0779 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -122,10 +122,16 @@ func (a *Router) GetAlbums(ctx context.Context, request GetAlbumsRequestObject) } baseUrl := baseResourceUrl(ctx, "albums") links, meta := buildPaginationLinksAndMeta(int32(cnt), request.Params, baseUrl) + resources := includedResources{ctx: ctx, ds: a.ds, includes: request.Params.Include} + err = resources.AddArtists(albums.ArtistIDs()...) + if err != nil { + return nil, err + } return GetAlbums200JSONResponse{ - Data: toAPIAlbums(albums), - Links: links, - Meta: &meta, + Data: toAPIAlbums(albums), + Links: links, + Meta: &meta, + Included: resources.Build(), }, nil } @@ -134,7 +140,19 @@ func (a *Router) GetAlbum(ctx context.Context, request GetAlbumRequestObject) (G if err != nil { return nil, err } - return GetAlbum200JSONResponse{Data: toAPIAlbum(*album)}, nil + resources := includedResources{ctx: ctx, ds: a.ds, includes: request.Params.Include} + err = resources.AddArtists(album.ArtistID, album.AlbumArtistID) + if err != nil { + return nil, err + } + err = resources.AddTracks(album.ID) + if err != nil { + return nil, err + } + return GetAlbum200JSONResponse{ + Data: toAPIAlbum(*album), + Included: resources.Build(), + }, nil } func (a *Router) GetArtists(ctx context.Context, request GetArtistsRequestObject) (GetArtistsResponseObject, error) { diff --git a/server/api/helpers.go b/server/api/helpers.go index bf7fe6f5a..5eed3ec18 100644 --- a/server/api/helpers.go +++ b/server/api/helpers.go @@ -133,14 +133,6 @@ func toAPIArtist(ma model.Artist) Artist { } } -func toAPIArtists(mas model.Artists) []Artist { - artists := make([]Artist, len(mas)) - for i := range mas { - artists[i] = toAPIArtist(mas[i]) - } - return artists -} - type GetParams interface { GetParams() GetTracksParams } diff --git a/server/api/includes.go b/server/api/includes.go index d93b2a81c..919e59908 100644 --- a/server/api/includes.go +++ b/server/api/includes.go @@ -16,8 +16,26 @@ type includedResources struct { resources []IncludedResource } +func (i *includedResources) AddTracks(albumIds ...string) error { + if i.includes == nil || !slices.Contains(*i.includes, string(ResourceTypeTrack)) { + return nil + } + sort.Strings(albumIds) + slices.Compact(albumIds) + tracks, err := i.ds.MediaFile(i.ctx).GetAll(model.QueryOptions{Filters: squirrel.Eq{"album_id": albumIds}}) + if err != nil { + return err + } + for _, tr := range tracks { + inc := &IncludedResource{} + _ = inc.FromTrack(toAPITrack(tr)) + i.resources = append(i.resources, *inc) + } + return nil +} + func (i *includedResources) AddAlbums(albumIds ...string) error { - if i.includes == nil || !slices.Contains(*i.includes, "album") { + if i.includes == nil || !slices.Contains(*i.includes, string(ResourceTypeAlbum)) { return nil } sort.Strings(albumIds) @@ -35,7 +53,7 @@ func (i *includedResources) AddAlbums(albumIds ...string) error { } func (i *includedResources) AddArtists(artistIds ...string) error { - if i.includes == nil || !slices.Contains(*i.includes, "artist") { + if i.includes == nil || !slices.Contains(*i.includes, string(ResourceTypeArtist)) { return nil } sort.Strings(artistIds) @@ -53,7 +71,7 @@ func (i *includedResources) AddArtists(artistIds ...string) error { } func (i *includedResources) Build() *[]IncludedResource { - if len(i.resources) == 0 { + if i.includes == nil { return nil } return &i.resources diff --git a/server/api/openapi_api.gen.go b/server/api/openapi_api.gen.go index 248266235..6d5a90003 100644 --- a/server/api/openapi_api.gen.go +++ b/server/api/openapi_api.gen.go @@ -697,9 +697,10 @@ type GetAlbumsResponseObject interface { } type GetAlbums200JSONResponse struct { - Data []Album `json:"data"` - Links PaginationLinks `json:"links"` - Meta *PaginationMeta `json:"meta,omitempty"` + Data []Album `json:"data"` + Included *[]IncludedResource `json:"included,omitempty"` + Links PaginationLinks `json:"links"` + Meta *PaginationMeta `json:"meta,omitempty"` } func (response GetAlbums200JSONResponse) VisitGetAlbumsResponse(w http.ResponseWriter) error { @@ -748,7 +749,8 @@ type GetAlbumResponseObject interface { } type GetAlbum200JSONResponse struct { - Data Album `json:"data"` + Data Album `json:"data"` + Included *[]IncludedResource `json:"included,omitempty"` } func (response GetAlbum200JSONResponse) VisitGetAlbumResponse(w http.ResponseWriter) error { @@ -1265,57 +1267,57 @@ func (sh *strictHandler) GetTrack(w http.ResponseWriter, r *http.Request, trackI // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+w73W7bONavQuj7gJnBqHa2nd0LA3uRzrZFBm0aJJntRScLUNKxxalEqiTlxFMY2NfY", - "19snWfCQlGSLsmW3aadBb9pE5Pnl4fnjyYcoFWUlOHCtotmHqKKSlqBB4m9zVmiQPwuuKeP4JQOVSlZp", - "Jng0i57jOklWhPIVqaSoQOoVSS0A4wui4U5PyK+KLmBGLLq3blnd/N1DzJa0qCGKI2awvq9BrqI44rSE", - "aBZtQ0VxpNIcSmr4YRpKx7jWIA34v3777fbHmfkniiO9qgwOpSXji2jdfKBS0lW0XscO/TOeqTdM56Nl", - "1DnVBHimyC3TeVBOcDgPk7OBuh8539e0GH+SKBvlBO5oqklJdZr3pESMB8poYe5FwhcSqAb52oq6T1Je", - "lyBZ2kq8sODmfDkRkiCrRAtCCQq2Lf5ig9xhatiCHauO7Ah1XOeUf5wudstv8B8lPALei+QvQaljraAA", - "pUabQNESOkwFXcB7U8FRJ9/Kv0Pmw8+8gboXaa80lfoIL64QbtiPqwbvYdJ24D6xp2M8LeoM+mJeQkE1", - "ZESCErVMQRmzdbsJ40TnYNYqwRXERIEJ92Z/YoJ2WVI1IJEn2BVji82WredCnhZJXar74Q/uqkIY4ee0", - "UDCe30btwOsymr2NtKTpuyiOqNRM6ehmn+bj6O7RQjxy3xyJq4KlsCX9tUH8Z5eemiP6dNJXdAEvWcl0", - "X+zrHIyHSUASMSfICKlAEgMyYG9m6W1h0G1enQzmtC50NPvLSRzNhSypRmb0k8dRHJWMs9JId9LwzriG", - "BciGxdfzuYIBHgWukblA1hinuLaDQQswwOERDCohA6xdCam9adSFVsYcBAcTmUohwTszBipoMxNyIWHO", - "7hDFZmL33aPvUFpDD3hm8nUhM5CTAaGRv10uYB1H3n7R1p7S7BLe16BQLJPBA8cfaVUVLEUFT5c8m9CK", - "/fi7EhioWvT/L2EezaL/m7Y1ytSuqukzKYV8aSwXyW6q7CnNiCe8jqMzbtwsLa5ALkEi5OflxzNALAfE", - "srCOo3OhT2udC8n+gOzz8nQuNOnQtsw8FzX/AnxYsngFLIjBiDEEGSiK1/No9nY3kUvnVF8nv0Nqjv1D", - "1N4MRKO1ZEmt7W+7UCHl03Y7mnWBGlA5q2zs3sSNTlRtONr9FBDosoO5H+xjG6UOxIwBaDdilOl9zaSx", - "u7eNAA29Nh4Ip9B178vNOo6GJOmpKKN6r6H0zjAqYT+Upf4KNH3dstoVDpHEloO+XF6GDfMIHW84atg1", - "E9mMg/VRtRdLM6ZSLXSoFtmMj2aj8tnAFromWMTRAriEMC5c2suQhAKogn9QPYDGbSAZ1fuxaaaLATy4", - "tB+BMbtRCrIGSsRuDW2ZgOUvkO1sWUH/5nwSQ97iZtgSG0P7/E4PSR/m9ZosP6yhcQ7L0jWoDvJYgzps", - "HeZHc3W4Hx3gaj3Wmw4o46vyptuG1GM+YaJ/yU+Jyk2mmzCxkLTKV43LsHci4DNsWhr0FrSEffBbYiGy", - "YXk6CunJI4X1ffvVeml2blNG8GHKl2LIsxpAIyXlPgoxTqh1kKY88L6xKfm8JmgbtIOF35D1f2Iz/Bxa", - "PNB421S1xx+YpfGJGGJqZd7pNRzqQYaGDC8DTVkRqMTiiGXBz0pTXavgUhPC+/ekx9WZLf8zf7bIDDP2", - "WZq62dZXJa0qg8FHip0paxubdx69T1CHdqHNGg59pXtuS1eUYB1HgsOIsOqxjEi19++yfKN7v2jaCi8Z", - "D8WoOZPW9JrWQS1ZyPUVdNw+Dnej9lUSlvv3hUyhlelV8FqntZTA9QVdDHgxtwHbQT7vTUVRQOr6L702", - "Sj8bxsTxzF/LQA5q1ntNqI8hZuQZScwIdiSxkMK3/GlP4fbm99mqOXtfA2EZcM3mDCT2flxjyd7iwR7g", - "OA9/jZdsy7exzKO92SHMtSMT0OaqakJ5h9GBNqZ3D6GoZpsvZ3wuAqlsrfPLhuttPt7koHOwyqoVSJJT", - "RWia4iuJwM8KkU+wQ0vLynhSLWto2EiEKIByw8ccqK5lyHpOSeFqSYGfaEH85g4RouqqElIr7NMF+7qq", - "0Qh29Es01r0tXhMfkML+pMpzIub6lkrYkDvidMkyKcqgQVnIf4JUTPAwoaVd7NZ8YQ1HJ5O/nUxO9uZ2", - "Tqpt4vHmsXdOJmSqNih8idIMKe+pzPbCX25ADNQfltLX3c3Z1la4bB1RN5ht+JNNqBMoBF+YCx8ya9y9", - "q09USVZSuQr1i4JEJkEqOwj0ax5ymwtSgTRhBrKWSghzwrQcbAW5xQZ7nTFB5qzAeP2OFSJh2r7pKEgF", - "z8j375JK/TAJhs+kGlB+AtRhKRmvNZDvn168+sETRc7DGNOccg6F2tc5snz73T4i70IsytJPRm15ar4i", - "NMuY89J+o6m6uNCgCE1ErYP4N3uCYZ7Nimf8lukc67qyLjR7hEtoNmGes1rah6swYre6oVWjCXtwqpuU", - "zAtBO3WzZeeQ3uOw5CUrQQ+G/Fdnr55txP3W4CbhVmYqZMb44lGZDGU/r2rF0qeSMv5HOAVyKPbzrtgf", - "A3yblfAdSVYaVPi8xnZOB2+u9nEpgABPd9OOGq8zwI4BOV6Pw2pbAZW7G81mx6Zd+h7G5OAGb9wmhh3H", - "HDdv/o0Bdi5Mx5G0/tCd92CguRzbIv2krzfxwS9OQ5F97MNQsLOiIK0l06srQ8T19oBKkKe1HcKxvz33", - "HuWXN9f+/RhzYlxtzzbXurJPhMwl6Tj0aYsc9wZ93skta1k4KDWbTpuscyKkda9blsYUURWkxH5PXE7d", - "ICSnF2cxuc1ZmhNaFOJWYa6PGX4ixa0CQnlGSspNoapzYJKU5jaQgiXShPYlo4SSX65en89OL84ISaiC", - "jBiLlXOawoQYazcrlRRLloEiwLNKMBM45t7WVexeOGIXxVVMqoKuTGWgkAObzseEmlTBjS0hN2QjK5yQ", - "nwuGQSmlnEjQksHSeCPr4E0QaIPURkHs5IndABQSVUJqP4EQ4xefWRCK5awiqjaaUySVQLVxpWZXBgXg", - "L40ME/LGssyUVXkGSyjMpbGcJjUrMqdZWlVWZsz+GY7M5FSjTheYkqD47RlaMJvmx07PyApRQEsca4O7", - "CiQDngIqvTljV88htcAht4X75DfjKwqWAlfQMc0X57+SF8BB0oJc1EnBUvLSbiLLJ1ilbBrsgum8Tiap", - "KFvb7fyUFCKZllRpkNOXZz8/O7961okX0YbdRnG09BVVdDJ5PDnBZlcFnFYsmkVPJrZMqqjO8ZpOW8e0", - "sPMw5gDQKs4yIwvoU19IdmfCB4qedsu0HQQa6oxtbXYjOSN2b4wwj97fjK+PhmjGJg+C8LOmo4G6o7mH", - "Ah1MrDMcOV7Tfih+BATOBo3Y15sRXN9sDQ09Pjk5agLlY177XCe3F2UL36bdBb3d1R1Z6G41ToMviZ6D", - "QPTtBbi2eeTu9jqOfrK6DLHR6HzamdJCkCf7QTbnltZx9NcxhEJzWJhG1KUpjXFC0kUp2hNmHXunNf2A", - "/59l673u62Dv1TfPeFwvdWu+AcfnjLdtp+cc01H3mG2LcHig7n7vxogrMfJ5u2+M3LU22nT5OMP66eSn", - "UVBuhOweLJETxjO2ZFlNC3e+1hjbDHzQBpsW7LcY+i2GfuoY+ucIne5588HETndlH0jw9NJ0HNb0g/1h", - "X/z0LZOjAujBcdNTCwVOx+7XEjndjfiY0OneCB5Y7GwVM23fGYfsr/Na+8WOssPD0cd53fmjnv/++z+K", - "GEjyDlakpJVtPDTv2u7EScaw0UDlikioJChzXfhi4xn0S3ubF6AdL98psihEQgvsLdnzbYcwh87X/ZHU", - "t9ToW2p0L+0FZ19/hhypGUrbTpEct9loTL25uweTdzmH8TDSLi9M6wmnH/D/PTnXtXudOrZn4W3+sNzL", - "v4kFUi/H9FeSeTX37B7u1dHZnH/KfFi5nLWZdfcREE21+/z39sbYgs0RrCHb948prdh0+Tha36z/FwAA", - "///+0r9yf0YAAA==", + "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", + "tLpe/S8AAP//4eMuGR1IAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/server/api/openapi_types.gen.go b/server/api/openapi_types.gen.go index 7d5a9b58c..7ae8e491f 100644 --- a/server/api/openapi_types.gen.go +++ b/server/api/openapi_types.gen.go @@ -306,6 +306,9 @@ type FilterStartsWith = []string // Include defines model for include. type Include = string +// IncludeForAlbum defines model for includeForAlbum. +type IncludeForAlbum = includeSlice + // IncludeForAlbums defines model for includeForAlbums. type IncludeForAlbums = includeSlice @@ -363,7 +366,7 @@ type GetAlbumsParams struct { // GetAlbumParams defines parameters for GetAlbum. type GetAlbumParams struct { // Include Related resources to include in the response, separated by commas - Include *IncludeForAlbums `form:"include,omitempty" json:"include,omitempty"` + Include *IncludeForAlbum `form:"include,omitempty" json:"include,omitempty"` } // GetArtistsParams defines parameters for GetArtists. From 40f7170930a2d0b23561e03bdb216ef1acd73074 Mon Sep 17 00:00:00 2001 From: Deluan Date: Tue, 23 May 2023 19:47:00 -0400 Subject: [PATCH 21/22] Add descriptions to included fields --- api/resources/album.yml | 1 + api/resources/albums.yml | 1 + api/resources/track.yml | 1 + api/resources/tracks.yml | 1 + server/api/openapi_api.gen.go | 118 ++++++++++++++++++---------------- 5 files changed, 67 insertions(+), 55 deletions(-) diff --git a/api/resources/album.yml b/api/resources/album.yml index f588f870b..12de5467c 100644 --- a/api/resources/album.yml +++ b/api/resources/album.yml @@ -21,6 +21,7 @@ get: data: $ref: '../schemas/Album.yml' included: + description: Included resources, as requested by the `include` query parameter type: array items: $ref: '../schemas/IncludedResource.yml' diff --git a/api/resources/albums.yml b/api/resources/albums.yml index 1b455495a..e682e4baa 100644 --- a/api/resources/albums.yml +++ b/api/resources/albums.yml @@ -32,6 +32,7 @@ get: meta: $ref: '../schemas/PaginationMeta.yml' included: + description: Included resources, as requested by the `include` query parameter type: array items: $ref: '../schemas/IncludedResource.yml' diff --git a/api/resources/track.yml b/api/resources/track.yml index 86b701058..a3fa69034 100644 --- a/api/resources/track.yml +++ b/api/resources/track.yml @@ -21,6 +21,7 @@ get: data: $ref: '../schemas/Track.yml' included: + description: Included resources, as requested by the `include` query parameter type: array items: $ref: '../schemas/IncludedResource.yml' diff --git a/api/resources/tracks.yml b/api/resources/tracks.yml index f3a8a2a71..62f71fa55 100644 --- a/api/resources/tracks.yml +++ b/api/resources/tracks.yml @@ -32,6 +32,7 @@ get: meta: $ref: '../schemas/PaginationMeta.yml' included: + description: Included resources, as requested by the `include` query parameter type: array items: $ref: '../schemas/IncludedResource.yml' diff --git a/server/api/openapi_api.gen.go b/server/api/openapi_api.gen.go index 6d5a90003..755ec5313 100644 --- a/server/api/openapi_api.gen.go +++ b/server/api/openapi_api.gen.go @@ -697,7 +697,9 @@ type GetAlbumsResponseObject interface { } type GetAlbums200JSONResponse struct { - Data []Album `json:"data"` + Data []Album `json:"data"` + + // Included Included resources, as requested by the `include` query parameter Included *[]IncludedResource `json:"included,omitempty"` Links PaginationLinks `json:"links"` Meta *PaginationMeta `json:"meta,omitempty"` @@ -749,7 +751,9 @@ type GetAlbumResponseObject interface { } type GetAlbum200JSONResponse struct { - Data Album `json:"data"` + Data Album `json:"data"` + + // Included Included resources, as requested by the `include` query parameter Included *[]IncludedResource `json:"included,omitempty"` } @@ -935,7 +939,9 @@ type GetTracksResponseObject interface { } type GetTracks200JSONResponse struct { - Data []Track `json:"data"` + Data []Track `json:"data"` + + // Included Included resources, as requested by the `include` query parameter Included *[]IncludedResource `json:"included,omitempty"` Links PaginationLinks `json:"links"` Meta *PaginationMeta `json:"meta,omitempty"` @@ -987,7 +993,9 @@ type GetTrackResponseObject interface { } type GetTrack200JSONResponse struct { - Data Track `json:"data"` + Data Track `json:"data"` + + // Included Included resources, as requested by the `include` query parameter Included *[]IncludedResource `json:"included,omitempty"` } @@ -1267,57 +1275,57 @@ func (sh *strictHandler) GetTrack(w http.ResponseWriter, r *http.Request, trackI // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "tLpe/S8AAP//4eMuGR1IAAA=", + "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", } // GetSwagger returns the content of the embedded swagger specification file From 7cf98af9dab2401022dac484ad97534582b3e5cc Mon Sep 17 00:00:00 2001 From: Deluan Date: Tue, 23 May 2023 21:15:54 -0400 Subject: [PATCH 22/22] Refactor included handling --- model/album.go | 7 +++- server/api/api.go | 87 +++++++++++++++++++----------------------- server/api/includes.go | 85 +++++++++++++++++++++++++++++++---------- 3 files changed, 109 insertions(+), 70 deletions(-) diff --git a/model/album.go b/model/album.go index 578486aa3..3d2c9b7d1 100644 --- a/model/album.go +++ b/model/album.go @@ -1,7 +1,6 @@ package model import ( - "strings" "time" "github.com/navidrome/navidrome/utils/slice" @@ -61,6 +60,10 @@ func (a Album) CoverArtID() ArtworkID { return artworkIDFromAlbum(a) } +func (a Album) ArtistIDs() []string { + return []string{a.ArtistID, a.AlbumArtistID} +} + type DiscID struct { AlbumID string `json:"albumId"` ReleaseDate string `json:"releaseDate"` @@ -95,7 +98,7 @@ func (als Albums) ToAlbumArtist() Artist { func (als Albums) ArtistIDs() []string { var ids []string for _, al := range als { - ids = append(ids, strings.Split(al.AllArtistIDs, " ")...) + ids = append(ids, al.ArtistIDs()...) } return ids } diff --git a/server/api/api.go b/server/api/api.go index 42bac0779..011825747 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -73,21 +73,18 @@ func (a *Router) GetTracks(ctx context.Context, request GetTracksRequestObject) } baseUrl := baseResourceUrl(ctx, "tracks") links, meta := buildPaginationLinksAndMeta(int32(cnt), request.Params, baseUrl) - resources := includedResources{ctx: ctx, ds: a.ds, includes: request.Params.Include} - err = resources.AddArtists(mfs.ArtistIDs()...) - if err != nil { - return nil, err + + resources := newIncludedResources(ctx, a.ds, request.Params.Include) + resources.Artists(mfs.ArtistIDs()...) + resources.Albums(mfs.AlbumIDs()...) + + response := GetTracks200JSONResponse{ + Data: toAPITracks(mfs), + Links: links, + Meta: &meta, } - err = resources.AddAlbums(mfs.AlbumIDs()...) - if err != nil { - return nil, err - } - return GetTracks200JSONResponse{ - Data: toAPITracks(mfs), - Links: links, - Meta: &meta, - Included: resources.Build(), - }, nil + response.Included, err = resources.Build() + return response, err } func (a *Router) GetTrack(ctx context.Context, request GetTrackRequestObject) (GetTrackResponseObject, error) { @@ -95,19 +92,16 @@ func (a *Router) GetTrack(ctx context.Context, request GetTrackRequestObject) (G if err != nil { return nil, err } - resources := includedResources{ctx: ctx, ds: a.ds, includes: request.Params.Include} - err = resources.AddArtists(mf.ArtistID, mf.AlbumArtistID) - if err != nil { - return nil, err + + resources := newIncludedResources(ctx, a.ds, request.Params.Include) + resources.Artists(mf.ArtistID, mf.AlbumArtistID) + resources.Albums(mf.AlbumID) + + response := GetTrack200JSONResponse{ + Data: toAPITrack(*mf), } - err = resources.AddAlbums(mf.AlbumID) - if err != nil { - return nil, err - } - return GetTrack200JSONResponse{ - Data: toAPITrack(*mf), - Included: resources.Build(), - }, nil + response.Included, err = resources.Build() + return response, err } func (a *Router) GetAlbums(ctx context.Context, request GetAlbumsRequestObject) (GetAlbumsResponseObject, error) { @@ -122,17 +116,17 @@ func (a *Router) GetAlbums(ctx context.Context, request GetAlbumsRequestObject) } baseUrl := baseResourceUrl(ctx, "albums") links, meta := buildPaginationLinksAndMeta(int32(cnt), request.Params, baseUrl) - resources := includedResources{ctx: ctx, ds: a.ds, includes: request.Params.Include} - err = resources.AddArtists(albums.ArtistIDs()...) - if err != nil { - return nil, err + + resources := newIncludedResources(ctx, a.ds, request.Params.Include) + resources.Artists(albums.ArtistIDs()...) + + response := GetAlbums200JSONResponse{ + Data: toAPIAlbums(albums), + Links: links, + Meta: &meta, } - return GetAlbums200JSONResponse{ - Data: toAPIAlbums(albums), - Links: links, - Meta: &meta, - Included: resources.Build(), - }, nil + response.Included, err = resources.Build() + return response, err } func (a *Router) GetAlbum(ctx context.Context, request GetAlbumRequestObject) (GetAlbumResponseObject, error) { @@ -140,19 +134,16 @@ func (a *Router) GetAlbum(ctx context.Context, request GetAlbumRequestObject) (G if err != nil { return nil, err } - resources := includedResources{ctx: ctx, ds: a.ds, includes: request.Params.Include} - err = resources.AddArtists(album.ArtistID, album.AlbumArtistID) - if err != nil { - return nil, err + + resources := newIncludedResources(ctx, a.ds, request.Params.Include) + resources.Artists(album.ArtistID, album.AlbumArtistID) + resources.Tracks(album.ID) + + response := GetAlbum200JSONResponse{ + Data: toAPIAlbum(*album), } - err = resources.AddTracks(album.ID) - if err != nil { - return nil, err - } - return GetAlbum200JSONResponse{ - Data: toAPIAlbum(*album), - Included: resources.Build(), - }, nil + response.Included, err = resources.Build() + return response, err } func (a *Router) GetArtists(ctx context.Context, request GetArtistsRequestObject) (GetArtistsResponseObject, error) { diff --git a/server/api/includes.go b/server/api/includes.go index 919e59908..ffd86ba39 100644 --- a/server/api/includes.go +++ b/server/api/includes.go @@ -14,14 +14,46 @@ type includedResources struct { ds model.DataStore includes *includeSlice resources []IncludedResource + ids map[ResourceType][]string } -func (i *includedResources) AddTracks(albumIds ...string) error { - if i.includes == nil || !slices.Contains(*i.includes, string(ResourceTypeTrack)) { - return nil +func newIncludedResources(ctx context.Context, ds model.DataStore, includes *includeSlice) *includedResources { + i := &includedResources{ + ctx: ctx, + ds: ds, + includes: includes, } - sort.Strings(albumIds) - slices.Compact(albumIds) + if includes != nil { + i.ids = make(map[ResourceType][]string) + for _, inc := range *includes { + i.ids[ResourceType(inc)] = []string{} + } + } + return i +} + +func (i *includedResources) Tracks(trackIds ...string) { + if i.ids == nil || i.ids[ResourceTypeTrack] == nil { + return + } + i.ids[ResourceTypeTrack] = append(i.ids[ResourceTypeTrack], trackIds...) +} + +func (i *includedResources) Albums(albumIds ...string) { + if i.ids == nil || i.ids[ResourceTypeAlbum] == nil { + return + } + i.ids[ResourceTypeAlbum] = append(i.ids[ResourceTypeAlbum], albumIds...) +} + +func (i *includedResources) Artists(artistIds ...string) { + if i.ids == nil || i.ids[ResourceTypeArtist] == nil { + return + } + i.ids[ResourceTypeArtist] = append(i.ids[ResourceTypeArtist], artistIds...) +} + +func (i *includedResources) addTracks(albumIds []string) error { tracks, err := i.ds.MediaFile(i.ctx).GetAll(model.QueryOptions{Filters: squirrel.Eq{"album_id": albumIds}}) if err != nil { return err @@ -34,12 +66,7 @@ func (i *includedResources) AddTracks(albumIds ...string) error { return nil } -func (i *includedResources) AddAlbums(albumIds ...string) error { - if i.includes == nil || !slices.Contains(*i.includes, string(ResourceTypeAlbum)) { - return nil - } - sort.Strings(albumIds) - slices.Compact(albumIds) +func (i *includedResources) addAlbums(albumIds []string) error { albums, err := i.ds.Album(i.ctx).GetAll(model.QueryOptions{Filters: squirrel.Eq{"id": albumIds}}) if err != nil { return err @@ -52,12 +79,7 @@ func (i *includedResources) AddAlbums(albumIds ...string) error { return nil } -func (i *includedResources) AddArtists(artistIds ...string) error { - if i.includes == nil || !slices.Contains(*i.includes, string(ResourceTypeArtist)) { - return nil - } - sort.Strings(artistIds) - slices.Compact(artistIds) +func (i *includedResources) addArtists(artistIds []string) error { artists, err := i.ds.Artist(i.ctx).GetAll(model.QueryOptions{Filters: squirrel.Eq{"artist.id": artistIds}}) if err != nil { return err @@ -70,9 +92,32 @@ func (i *includedResources) AddArtists(artistIds ...string) error { return nil } -func (i *includedResources) Build() *[]IncludedResource { +func (i *includedResources) Build() (*[]IncludedResource, error) { if i.includes == nil { - return nil + return nil, nil } - return &i.resources + for _, typ := range *i.includes { + ids := i.ids[ResourceType(typ)] + sort.Strings(ids) + slices.Compact(ids) + if len(ids) == 0 { + continue + } + switch ResourceType(typ) { + case ResourceTypeAlbum: + if err := i.addAlbums(ids); err != nil { + return nil, err + } + case ResourceTypeArtist: + if err := i.addArtists(ids); err != nil { + return nil, err + } + case ResourceTypeTrack: + if err := i.addTracks(ids); err != nil { + return nil, err + } + } + } + + return &i.resources, nil }