diff --git a/.gitignore b/.gitignore index 5f8cf36dab383986982b18ada90d9f74f7f241c3..03fcb4154f5b10e8aef42b0d4c794e44bbce41d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /tank +/tank.yaml /vendor/ /cover.out /coverage.html +/api/docs/swagger.* \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1e66fbb038f9fee3802f9d5db4b6d43212d07f3e..f6b84c3a7ab33547f63e7cdc28c3654127cbacf7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -59,6 +59,7 @@ test-store-postgres: build: stage: build script: + # we should actually use `make build` here, see #30 - go build -ldflags "-extldflags '-static'" -tags netgo -o $CI_PROJECT_DIR/tank ./cmd/tank ## sqlite needs cgo... :( ##- go build -o $CI_PROJECT_DIR/tank ./cmd/tank @@ -68,6 +69,28 @@ build: paths: - tank +build-openapi-2-scheme: + stage: build + script: + - go get -u github.com/swaggo/swag/cmd/swag + - make api-docs + artifacts: + paths: + - api/docs/swagger.yaml + +build-openapi-3-scheme: + stage: build + image: + name: openapitools/openapi-generator-cli:latest-release + needs: + - job: build-openapi-2-scheme + artifacts: true + script: + - /usr/local/bin/docker-entrypoint.sh generate -i api/docs/swagger.yaml -o api/docs -g openapi-yaml --minimal-update + artifacts: + paths: + - api/docs/openapi/openapi.yaml + docker: stage: build image: diff --git a/Makefile b/Makefile index 4e7b45231ceada33af387ca5581a326cbf233b6d..935c74fd05c10e276c57dee1a9062acb931938c7 100644 --- a/Makefile +++ b/Makefile @@ -21,6 +21,12 @@ ifdef GOROOT GOCMD = $(GOROOT)/bin/go endif +SWAG := swag +ifdef GOPATH +SWAG = $(GOPATH)/bin/swag +endif +SWAG_ARGS := -d api/v1/,cmd/tank/ -g api.go + EXECUTEABLE := tank all: build @@ -35,6 +41,15 @@ format: ui: $(GOCMD) generate ./ui +fmt-api-docs: + $(SWAG) fmt $(SWAG_ARGS) + +api-docs: + $(SWAG) init $(SWAG_ARGS) --pd -o api/docs + +# build target actually depends on api-docs +# to allow building binary without generating api docs first, we put api/docs/docs.go under version control +# see #30 build: ui $(GOCMD) build -o $(EXECUTEABLE) ./cmd/tank diff --git a/api/docs/docs.go b/api/docs/docs.go new file mode 100644 index 0000000000000000000000000000000000000000..b7745974d6444e08fc69ac377ce70271e78e9474 --- /dev/null +++ b/api/docs/docs.go @@ -0,0 +1,1969 @@ +// Package docs GENERATED BY THE COMMAND ABOVE; DO NOT EDIT +// This file was generated by swaggo/swag +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": { + "name": "API Support", + "url": "https://gitlab.servus.at/autoradio/tank" + }, + "license": { + "name": "AGPLv3", + "url": "https://www.gnu.org/licenses/agpl-3.0" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/v1/playlists": { + "get": { + "description": "Lists all playlists.", + "produces": [ + "application/json" + ], + "summary": "List playlists", + "parameters": [ + { + "type": "integer", + "description": "Limit number of results", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Start listing from offset", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.PlaylistsListing" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + } + }, + "/api/v1/playlists/{id}": { + "get": { + "description": "Retrieves a playlist.", + "produces": [ + "application/json" + ], + "summary": "Retrieve playlist", + "parameters": [ + { + "type": "integer", + "description": "ID of the playlist", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/store.Playlist" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + } + }, + "/api/v1/shows": { + "get": { + "description": "Lists all existing shows", + "produces": [ + "application/json" + ], + "summary": "List shows", + "parameters": [ + { + "type": "integer", + "description": "Limit number of results", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Start listing from offset", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.ShowsListing" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + } + }, + "/api/v1/shows/{name}": { + "post": { + "description": "Creates a new show", + "produces": [ + "application/json" + ], + "summary": "Create show", + "parameters": [ + { + "type": "string", + "description": "Name of the show to be created", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "If given, all files and playlists will be copied from the show", + "name": "clone-from", + "in": "query" + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/store.Show" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + }, + "delete": { + "description": "Deletes a show", + "produces": [ + "application/json" + ], + "summary": "Delete show", + "parameters": [ + { + "type": "string", + "description": "Name of the show to be deleted", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + } + }, + "/api/v1/shows/{name}/files": { + "get": { + "description": "Lists files of show", + "produces": [ + "application/json" + ], + "summary": "List files", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Limit number of results", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Start listing from offset", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.FilesListing" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + }, + "post": { + "description": "Adds a file and starts import", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Add file", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "description": "URI of the file", + "name": "file", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1.FileCreateRequest" + } + }, + { + "type": "string", + "description": "running|done - If given, return not before import has the given state", + "name": "wait-for", + "in": "query" + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/store.File" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + } + }, + "/api/v1/shows/{name}/files/{id}": { + "get": { + "description": "Retrieves file object.", + "produces": [ + "application/json" + ], + "summary": "Retrieve file", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "ID of the file", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/store.File" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + }, + "delete": { + "description": "Removes a file.", + "summary": "Delete file", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "ID of the file", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + }, + "patch": { + "description": "Updates file metadata.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Update file", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "ID of the file", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "File metadata", + "name": "metadata", + "in": "body", + "schema": { + "$ref": "#/definitions/store.FileMetadata" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/store.File" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + } + }, + "/api/v1/shows/{name}/files/{id}/import": { + "get": { + "description": "Retrieves import status of the file.", + "produces": [ + "application/json" + ], + "summary": "Retrieve import status", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "ID of the file", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "running|done - If given, return not before import has the given state", + "name": "wait-for", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/importer.Job" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "404": { + "description": "No job for this file", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + }, + "delete": { + "description": "Cancels import of file.", + "summary": "Cancel file import", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "ID of the file", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "404": { + "description": "No job for this file", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + } + }, + "/api/v1/shows/{name}/files/{id}/logs": { + "get": { + "description": "Retrieves import logs of the file.", + "produces": [ + "application/json" + ], + "summary": "Retrieve import logs", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "ID of the file", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.FileImportLogs" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + } + }, + "/api/v1/shows/{name}/files/{id}/upload": { + "get": { + "description": "Uploads file content. Only available if file was created using SourceURI set to upload://-type and file import state is running.", + "summary": "Upload file content", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "ID of the file", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "A unique identifier for the uploaded file", + "name": "flowIdentifier", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + }, + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + }, + "put": { + "description": "Uploads file content. Only available if file was created using SourceURI set to upload://-type and file import state is running.", + "consumes": [ + "application/octet-stream" + ], + "produces": [ + "application/json" + ], + "summary": "Upload file content", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "ID of the file", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + }, + "post": { + "description": "Uploads file content via flow.js. Only available if file was created using SourceURI set to upload://-type and file import state is running.", + "consumes": [ + "multipart/form-data" + ], + "summary": "Upload file content", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "ID of the file", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + } + }, + "/api/v1/shows/{name}/files/{id}/usage": { + "get": { + "description": "Lists playlists referring to the file.", + "produces": [ + "application/json" + ], + "summary": "List referring playlists", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "ID of the file", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.FileUsageListing" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + } + }, + "/api/v1/shows/{name}/imports": { + "get": { + "description": "Lists all running and pending imports", + "produces": [ + "application/json" + ], + "summary": "List imports", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Limit number of results", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Start listing from offset", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.JobsListing" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + } + }, + "/api/v1/shows/{name}/playlists": { + "get": { + "description": "Lists playlists of show.", + "produces": [ + "application/json" + ], + "summary": "List playlists", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Limit number of results", + "name": "limit", + "in": "query" + }, + { + "type": "integer", + "description": "Start listing from offset", + "name": "offset", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.PlaylistsListing" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + }, + "post": { + "description": "Creates a new playlist for the show.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create playlist", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "description": "Playlist data", + "name": "playlist", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/store.Playlist" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/store.Playlist" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + } + }, + "/api/v1/shows/{name}/playlists/{id}": { + "get": { + "description": "Retrieves a playlist of a show.", + "produces": [ + "application/json" + ], + "summary": "Retrieve playlist", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "ID of the playlist", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/store.Playlist" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + }, + "put": { + "description": "Updates a playlist of a show.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Update playlist", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "ID of the playlist", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Playlist data", + "name": "playlist", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/store.Playlist" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/store.Playlist" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + }, + "delete": { + "description": "Deletes a playlist of a show.", + "summary": "Delete playlist", + "parameters": [ + { + "type": "string", + "description": "Name of the show", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "ID of the playlist", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/v1.ErrorResponse" + } + } + } + } + }, + "/auth/backends": { + "get": { + "description": "Lists authentication backends.", + "produces": [ + "application/json" + ], + "summary": "List authentication backends", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/auth.AuthBackendInfo" + } + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/auth.HTTPErrorResponse" + } + } + } + } + }, + "/auth/oidc/callback": { + "get": { + "description": "Completes OIDC login.", + "produces": [ + "application/json" + ], + "summary": "Complete OIDC login", + "parameters": [ + { + "type": "string", + "description": "OIDC state", + "name": "state", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "OIDC code", + "name": "code", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/auth.HTTPErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/auth.HTTPErrorResponse" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/auth.HTTPErrorResponse" + } + }, + "410": { + "description": "Gone", + "schema": { + "$ref": "#/definitions/auth.HTTPErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/auth.HTTPErrorResponse" + } + } + } + } + }, + "/auth/oidc/login": { + "get": { + "description": "Creates a session via OIDC. Redirects to identity provider.", + "produces": [ + "application/json" + ], + "summary": "Create OIDC session", + "parameters": [ + { + "type": "string", + "description": "OIDC session ID", + "name": "session-id", + "in": "query", + "required": true + } + ], + "responses": { + "302": { + "description": "" + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/auth.HTTPErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/auth.HTTPErrorResponse" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/auth.HTTPErrorResponse" + } + } + } + } + }, + "/auth/session": { + "get": { + "description": "Retrieves session info.", + "produces": [ + "application/json" + ], + "summary": "Retrieve session info", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/auth.Session" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/auth.HTTPErrorResponse" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/auth.HTTPErrorResponse" + } + }, + "408": { + "description": "Request Timeout", + "schema": { + "$ref": "#/definitions/auth.HTTPErrorResponse" + } + } + } + }, + "post": { + "description": "Creates a new session.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create session", + "parameters": [ + { + "description": "Request data according to backend", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/auth.NewSessionRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/auth.NewSessionResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/auth.HTTPErrorResponse" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/auth.HTTPErrorResponse" + } + } + } + }, + "delete": { + "description": "Deletes the session.", + "produces": [ + "application/json" + ], + "summary": "Delete session", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/auth.HTTPErrorResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/auth.HTTPErrorResponse" + } + } + } + } + }, + "/healthz": { + "get": { + "description": "Checks daemon health.", + "produces": [ + "application/json" + ], + "summary": "Check health", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/main.Health" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/main.Health" + } + } + } + } + } + }, + "definitions": { + "auth.AuthBackendInfo": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "state": { + "type": "string", + "enum": [ + "new", + "initializing", + "ready", + "failed", + "destroyed", + "unknown" + ] + } + } + }, + "auth.HTTPErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, + "auth.NewSessionRequest": { + "type": "object", + "properties": { + "arguments": { + "type": "array", + "items": { + "type": "integer" + } + }, + "backend": { + "type": "string" + } + } + }, + "auth.NewSessionResponse": { + "type": "object", + "properties": { + "session": { + "$ref": "#/definitions/auth.Session" + }, + "token": { + "type": "string" + } + } + }, + "auth.Session": { + "type": "object", + "properties": { + "all-shows": { + "type": "boolean" + }, + "privileged": { + "type": "boolean" + }, + "public-shows": { + "type": "array", + "items": { + "type": "string" + } + }, + "readonly": { + "type": "boolean" + }, + "shows": { + "type": "array", + "items": { + "type": "string" + } + }, + "username": { + "type": "string" + } + } + }, + "importer.Job": { + "type": "object", + "properties": { + "created": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "progress": { + "type": "integer" + }, + "ref-id": { + "type": "string" + }, + "show": { + "type": "string" + }, + "source": { + "$ref": "#/definitions/importer.SourceURL" + }, + "started": { + "type": "integer" + }, + "state": { + "type": "integer" + }, + "user": { + "type": "string" + } + } + }, + "importer.SourceURL": { + "type": "object", + "properties": { + "forceQuery": { + "description": "append a query ('?') even if RawQuery is empty", + "type": "boolean" + }, + "fragment": { + "description": "fragment for references, without '#'", + "type": "string" + }, + "host": { + "description": "host or host:port", + "type": "string" + }, + "opaque": { + "description": "encoded opaque data", + "type": "string" + }, + "path": { + "description": "path (relative paths may omit leading slash)", + "type": "string" + }, + "rawFragment": { + "description": "encoded fragment hint (see EscapedFragment method)", + "type": "string" + }, + "rawPath": { + "description": "encoded path hint (see EscapedPath method)", + "type": "string" + }, + "rawQuery": { + "description": "encoded query values, without '?'", + "type": "string" + }, + "scheme": { + "type": "string" + }, + "user": { + "description": "username and password information", + "$ref": "#/definitions/url.Userinfo" + } + } + }, + "main.Health": { + "type": "object", + "properties": { + "auth": { + "type": "string" + }, + "importer": { + "type": "string" + }, + "store": { + "type": "string" + } + } + }, + "store.File": { + "type": "object", + "properties": { + "created": { + "type": "string" + }, + "duration": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "metadata": { + "$ref": "#/definitions/store.FileMetadata" + }, + "show": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "source": { + "$ref": "#/definitions/store.FileSource" + }, + "updated": { + "type": "string" + } + } + }, + "store.FileMetadata": { + "type": "object", + "properties": { + "album": { + "type": "string" + }, + "artist": { + "description": "actually a full-text index would be nice here...", + "type": "string" + }, + "isrc": { + "type": "string" + }, + "organization": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, + "store.FileSource": { + "type": "object", + "properties": { + "hash": { + "type": "string" + }, + "import": { + "$ref": "#/definitions/store.Import" + }, + "uri": { + "type": "string" + } + } + }, + "store.Import": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "state": { + "type": "integer" + } + } + }, + "store.ImportLogs": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/store.Log" + } + }, + "store.Log": { + "type": "object" + }, + "store.Playlist": { + "type": "object", + "properties": { + "created": { + "type": "string" + }, + "description": { + "type": "string" + }, + "entries": { + "type": "array", + "items": { + "$ref": "#/definitions/store.PlaylistEntry" + } + }, + "id": { + "type": "integer" + }, + "playout-mode": { + "type": "string" + }, + "show": { + "type": "string" + }, + "updated": { + "type": "string" + } + } + }, + "store.PlaylistEntry": { + "type": "object", + "properties": { + "duration": { + "type": "integer" + }, + "file": { + "$ref": "#/definitions/store.File" + }, + "uri": { + "type": "string" + } + } + }, + "store.Show": { + "type": "object", + "properties": { + "created": { + "type": "string" + }, + "name": { + "type": "string" + }, + "updated": { + "type": "string" + } + } + }, + "url.Userinfo": { + "type": "object" + }, + "v1.ErrorResponse": { + "type": "object", + "properties": { + "detail": {}, + "error": { + "type": "string" + } + } + }, + "v1.FileCreateRequest": { + "type": "object", + "properties": { + "source-uri": { + "type": "string" + } + } + }, + "v1.FileImportLogs": { + "type": "object", + "properties": { + "results": { + "$ref": "#/definitions/store.ImportLogs" + } + } + }, + "v1.FileUsageListing": { + "type": "object", + "properties": { + "results": { + "type": "object", + "properties": { + "playlists": { + "type": "array", + "items": { + "$ref": "#/definitions/store.Playlist" + } + } + } + } + } + }, + "v1.FilesListing": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/definitions/store.File" + } + } + } + }, + "v1.JobsListing": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/definitions/importer.Job" + } + } + } + }, + "v1.PlaylistsListing": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/definitions/store.Playlist" + } + } + } + }, + "v1.ShowsListing": { + "type": "object", + "properties": { + "results": { + "type": "array", + "items": { + "$ref": "#/definitions/store.Show" + } + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "AURA Tank API", + Description: "Import & Playlist Daemon", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/api/v1/api.go b/api/v1/api.go index ea49aa842f23f887df5d20c1ad142ab6732ceb35..c546f0980e5eb3e3b77182276e82bb34050923a5 100644 --- a/api/v1/api.go +++ b/api/v1/api.go @@ -1,6 +1,6 @@ // // tank, Import and Playlist Daemon for Aura project -// Copyright (C) 2017-2020 Christian Pointner +// Copyright (C) 2017-2020 Christian Pointner // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as @@ -28,6 +28,20 @@ import ( "gitlab.servus.at/autoradio/tank/store" ) +// @title AURA Tank API +// @version 1.0 +// @description Import & Playlist Daemon + +// @contact.name API Support +// @contact.url https://gitlab.servus.at/autoradio/tank + +// @license.name AGPLv3 +// @license.url https://www.gnu.org/licenses/agpl-3.0 + +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization + type API struct { store *store.Store importer *importer.Importer diff --git a/api/v1/files.go b/api/v1/files.go index 86bc38600afa776d88897572ef9f261628f1c135..116a99433e5f214ce7cecb8d6d82c63188f2541e 100644 --- a/api/v1/files.go +++ b/api/v1/files.go @@ -27,6 +27,18 @@ import ( "gitlab.servus.at/autoradio/tank/store" ) +// ListFilesOfShow returns a list of all files of the show. +// @Summary List files +// @Description Lists files of show +// @Produce json +// @Param name path string true "Name of the show" +// @Param limit query int false "Limit number of results" +// @Param offset query int false "Start listing from offset" +// @Success 200 {object} FilesListing +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/files [get] func (api *API) ListFilesOfShow(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { @@ -45,6 +57,19 @@ func (api *API) ListFilesOfShow(c *gin.Context) { c.JSON(http.StatusOK, FilesListing{files}) } +// CreateFileForShow adds a file and starts import. +// @Summary Add file +// @Description Adds a file and starts import +// @Accept json +// @Produce json +// @Param name path string true "Name of the show" +// @Param file body FileCreateRequest true "URI of the file" +// @Param wait-for query string false "running|done - If given, return not before import has the given state" +// @Success 201 {object} store.File +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/files [post] func (api *API) CreateFileForShow(c *gin.Context) { showID := c.Param("show-id") authorized, sess := authorizeRequestForShow(c, showID) @@ -131,6 +156,18 @@ create_file_response: c.JSON(status, errResp) } +// ReadFileOfShow retrieves file object. +// @Summary Retrieve file +// @Description Retrieves file object. +// @Produce json +// @Param name path string true "Name of the show" +// @Param id path int true "ID of the file" +// @Success 200 {object} store.File +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/files/{id} [get] func (api *API) ReadFileOfShow(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { @@ -150,6 +187,20 @@ func (api *API) ReadFileOfShow(c *gin.Context) { c.JSON(http.StatusOK, file) } +// PatchFileOfShow updates file metadata. +// @Summary Update file +// @Description Updates file metadata. +// @Accept json +// @Produce json +// @Param name path string true "Name of the show" +// @Param id path int true "ID of the file" +// @Param metadata body store.FileMetadata false "File metadata" +// @Success 200 {object} store.File +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/files/{id} [patch] func (api *API) PatchFileOfShow(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { @@ -174,6 +225,18 @@ func (api *API) PatchFileOfShow(c *gin.Context) { c.JSON(http.StatusOK, file) } +// DeleteFileOfShow removes a file. +// @Summary Delete file +// @Description Removes a file. +// @Param name path string true "Name of the show" +// @Param id path int true "ID of the file" +// @Success 204 {object} nil +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 409 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/files/{id} [delete] func (api *API) DeleteFileOfShow(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { @@ -195,6 +258,18 @@ func (api *API) DeleteFileOfShow(c *gin.Context) { c.JSON(http.StatusNoContent, nil) } +// ReadUsageOfFile lists playlists referring to the file. +// @Summary List referring playlists +// @Description Lists playlists referring to the file. +// @Produce json +// @Param name path string true "Name of the show" +// @Param id path int true "ID of the file" +// @Success 200 {object} FileUsageListing +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/files/{id}/usage [get] func (api *API) ReadUsageOfFile(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { @@ -214,6 +289,18 @@ func (api *API) ReadUsageOfFile(c *gin.Context) { c.JSON(http.StatusOK, result) } +// ReadLogsOfFile retrieves import logs of the file. +// @Summary Retrieve import logs +// @Description Retrieves import logs of the file. +// @Produce json +// @Param name path string true "Name of the show" +// @Param id path int true "ID of the file" +// @Success 200 {object} FileImportLogs +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/files/{id}/logs [get] func (api *API) ReadLogsOfFile(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { diff --git a/api/v1/files_import.go b/api/v1/files_import.go index b350703965bc39f2cdb4e2fd7451f35fbb2138b8..94436394e4827d7d107cae665d9438123609e270 100644 --- a/api/v1/files_import.go +++ b/api/v1/files_import.go @@ -24,6 +24,18 @@ import ( "github.com/gin-gonic/gin" ) +// ListImportsOfShow returns a list of all running and pending imports for files belonging to this show. +// @Summary List imports +// @Description Lists all running and pending imports +// @Produce json +// @Param name path string true "Name of the show" +// @Param limit query int false "Limit number of results" +// @Param offset query int false "Start listing from offset" +// @Success 200 {object} JobsListing +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/imports [get] func (api *API) ListImportsOfShow(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { @@ -42,6 +54,19 @@ func (api *API) ListImportsOfShow(c *gin.Context) { c.JSON(http.StatusOK, JobsListing{jobs}) } +// ReadImportOfFile retrieves import status of the file. +// @Summary Retrieve import status +// @Description Retrieves import status of the file. +// @Produce json +// @Param name path string true "Name of the show" +// @Param id path int true "ID of the file" +// @Param wait-for query string false "running|done - If given, return not before import has the given state" +// @Success 200 {object} importer.Job +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse "No job for this file" +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/files/{id}/import [get] func (api *API) ReadImportOfFile(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { @@ -80,6 +105,17 @@ func (api *API) ReadImportOfFile(c *gin.Context) { c.JSON(http.StatusOK, job) } +// CancelImportOfFile cancels import of file. +// @Summary Cancel file import +// @Description Cancels import of file. +// @Param name path string true "Name of the show" +// @Param id path int true "ID of the file" +// @Success 204 {object} nil +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse "No job for this file" +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/files/{id}/import [delete] func (api *API) CancelImportOfFile(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { diff --git a/api/v1/files_upload.go b/api/v1/files_upload.go index ab374a4e525d8f25a9547981329f192914eeca6f..ca66c0639b98e65c56ccc718c1d00831012d3f29 100644 --- a/api/v1/files_upload.go +++ b/api/v1/files_upload.go @@ -37,6 +37,19 @@ import ( //******* simple binary http upload +// UploadFileSimple uploads file content. +// @Summary Upload file content +// @Description Uploads file content. Only available if file was created using SourceURI set to upload://-type and file import state is running. +// @Accept octet-stream +// @Produce json +// @Param name path string true "Name of the show" +// @Param id path int true "ID of the file" +// @Success 200 +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/files/{id}/upload [put] func (api *API) UploadFileSimple(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { @@ -364,6 +377,18 @@ func getFlowJSParameterFromQuery(r *http.Request) (id string, chunk, totalChunks return } +// UploadFileFlowJS uploads file content via flow.js. +// @Summary Upload file content +// @Description Uploads file content via flow.js. Only available if file was created using SourceURI set to upload://-type and file import state is running. +// @Accept mpfd +// @Param name path string true "Name of the show" +// @Param id path int true "ID of the file" +// @Success 200 {object} nil +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 409 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/files/{id}/upload [post] func (api *API) UploadFileFlowJS(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { @@ -429,6 +454,20 @@ func (api *API) UploadFileFlowJS(c *gin.Context) { c.JSON(http.StatusOK, nil) } +// TestFileFlowJS uploads file content. +// @Summary Upload file content +// @Description Uploads file content. Only available if file was created using SourceURI set to upload://-type and file import state is running. +// @Param name path string true "Name of the show" +// @Param id path int true "ID of the file" +// @Param flowIdentifier query string true "A unique identifier for the uploaded file" +// TODO: document all flow.js params +// @Success 200 {object} nil +// @Success 204 {object} nil +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 409 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/files/{id}/upload [get] func (api *API) TestFileFlowJS(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { diff --git a/api/v1/playlists.go b/api/v1/playlists.go index 8b243bfb3383ba254d2367c14a2dc5c3fa525552..31577cbf88b5d11b5bc4b9a7a5c0e00f5fac025b 100644 --- a/api/v1/playlists.go +++ b/api/v1/playlists.go @@ -26,6 +26,18 @@ import ( "gitlab.servus.at/autoradio/tank/store" ) +// ListPlaylistsOfShow lists playlists. +// @Summary List playlists +// @Description Lists playlists of show. +// @Produce json +// @Param name path string true "Name of the show" +// @Param limit query int false "Limit number of results" +// @Param offset query int false "Start listing from offset" +// @Success 200 {object} PlaylistsListing +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/playlists [get] func (api *API) ListPlaylistsOfShow(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { @@ -44,6 +56,18 @@ func (api *API) ListPlaylistsOfShow(c *gin.Context) { c.JSON(http.StatusOK, PlaylistsListing{playlists}) } +// CreatePlaylistForShow creates a new playlist. +// @Summary Create playlist +// @Description Creates a new playlist for the show. +// @Accept json +// @Produce json +// @Param name path string true "Name of the show" +// @Param playlist body store.Playlist true "Playlist data" +// @Success 201 {object} store.Playlist +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/playlists [post] func (api *API) CreatePlaylistForShow(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { @@ -63,6 +87,18 @@ func (api *API) CreatePlaylistForShow(c *gin.Context) { c.JSON(http.StatusCreated, playlist) } +// ReadPlaylistOfShow retrieves a playlist of a show. +// @Summary Retrieve playlist +// @Description Retrieves a playlist of a show. +// @Produce json +// @Param name path string true "Name of the show" +// @Param id path int true "ID of the playlist" +// @Success 200 {object} store.Playlist +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/playlists/{id} [get] func (api *API) ReadPlaylistOfShow(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { @@ -82,6 +118,20 @@ func (api *API) ReadPlaylistOfShow(c *gin.Context) { c.JSON(http.StatusOK, playlist) } +// UpdatePlaylistOfShow updates a playlist of a show. +// @Summary Update playlist +// @Description Updates a playlist of a show. +// @Accept json +// @Produce json +// @Param name path string true "Name of the show" +// @Param id path int true "ID of the playlist" +// @Param playlist body store.Playlist true "Playlist data" +// @Success 200 {object} store.Playlist +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/playlists/{id} [put] func (api *API) UpdatePlaylistOfShow(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { @@ -105,6 +155,17 @@ func (api *API) UpdatePlaylistOfShow(c *gin.Context) { c.JSON(http.StatusOK, playlist) } +// DeletePlaylistOfShow deletes a playlist of a show. +// @Summary Delete playlist +// @Description Deletes a playlist of a show. +// @Param name path string true "Name of the show" +// @Param id path int true "ID of the playlist" +// @Success 204 {object} nil +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name}/playlists/{id} [delete] func (api *API) DeletePlaylistOfShow(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { @@ -125,6 +186,17 @@ func (api *API) DeletePlaylistOfShow(c *gin.Context) { // global +// ListPlaylists lists all playlists. +// @Summary List playlists +// @Description Lists all playlists. +// @Produce json +// @Param limit query int false "Limit number of results" +// @Param offset query int false "Start listing from offset" +// @Success 200 {object} PlaylistsListing +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/playlists [get] func (api *API) ListPlaylists(c *gin.Context) { if authorized, _ := authorizeRequestAllShows(c); !authorized { return @@ -142,6 +214,17 @@ func (api *API) ListPlaylists(c *gin.Context) { c.JSON(http.StatusOK, PlaylistsListing{playlists}) } +// ReadPlaylist retrieves a playlist. +// @Summary Retrieve playlist +// @Description Retrieves a playlist. +// @Produce json +// @Param id path int true "ID of the playlist" +// @Success 200 {object} store.Playlist +// @Failure 400 {object} ErrorResponse +// @Failure 403 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/playlists/{id} [get] func (api *API) ReadPlaylist(c *gin.Context) { if authorized, _ := authorizeRequestAllShows(c); !authorized { return diff --git a/api/v1/shows.go b/api/v1/shows.go index b4d11e691262db83b18a1a35414ec294639d6891..def4d6db8dfa9a1c7c787a5e21ba9ddfcc08f198 100644 --- a/api/v1/shows.go +++ b/api/v1/shows.go @@ -26,6 +26,17 @@ import ( "gitlab.servus.at/autoradio/tank/store" ) +// ListShows returns a list of all shows that are accessible to the current session. +// If authentication is disabled a list of all existing shows is returned. +// @Summary List shows +// @Description Lists all existing shows +// @Produce json +// @Param limit query int false "Limit number of results" +// @Param offset query int false "Start listing from offset" +// @Success 200 {object} ShowsListing +// @Failure 400 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows [get] func (api *API) ListShows(c *gin.Context) { offset, limit, ok := getPaginationParameter(c) if !ok { @@ -72,6 +83,18 @@ func (api *API) ListShows(c *gin.Context) { c.JSON(http.StatusOK, ShowsListing{Shows: shows}) } +// CreateShow creates a new show. +// @Summary Create show +// @Description Creates a new show +// @Produce json +// @Param name path string true "Name of the show to be created" +// @Param clone-from query string false "If given, all files and playlists will be copied from the show" +// @Success 201 {object} store.Show +// @Failure 403 {object} ErrorResponse +// @Failure 404 {object} ErrorResponse +// @Failure 409 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name} [post] func (api *API) CreateShow(c *gin.Context) { showID := c.Param("show-id") if authorized, _ := authorizeRequestForShow(c, showID); !authorized { @@ -97,6 +120,15 @@ func (api *API) CreateShow(c *gin.Context) { c.JSON(http.StatusCreated, show) } +// DeleteShow deletes a show. +// @Summary Delete show +// @Description Deletes a show +// @Produce json +// @Param name path string true "Name of the show to be deleted" +// @Success 204 {object} nil +// @Failure 403 {object} ErrorResponse +// @Failure 500 {object} ErrorResponse +// @Router /api/v1/shows/{name} [delete] func (api *API) DeleteShow(c *gin.Context) { showID := c.Param("show-id") if authorized, s := authorizeRequestForShow(c, showID); !authorized { diff --git a/auth/auth.go b/auth/auth.go index b6efccee4111b5fda008efe38894c24d6c76e029..7c1fc14e51f28be968c9513d3e1ed7171e571052 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -87,6 +87,16 @@ func Init(c *Config, infoLog, errLog, dbgLog *log.Logger) (err error) { return } +// newSession creates a new session. +// @Summary Create session +// @Description Creates a new session. +// @Accept json +// @Produce json +// @Param request body NewSessionRequest true "Request data according to backend" +// @Success 200 {object} NewSessionResponse +// @Failure 400 {object} HTTPErrorResponse +// @Failure 409 {object} HTTPErrorResponse +// @Router /auth/session [post] func newSession(c *gin.Context) { request := &NewSessionRequest{} err := json.NewDecoder(c.Request.Body).Decode(request) @@ -128,6 +138,15 @@ func newSession(c *gin.Context) { c.JSON(http.StatusOK, response) } +// getSession retrieves session info. +// @Summary Retrieve session info +// @Description Retrieves session info. +// @Produce json +// @Success 200 {object} Session +// @Failure 400 {object} HTTPErrorResponse +// @Failure 404 {object} HTTPErrorResponse +// @Failure 408 {object} HTTPErrorResponse +// @Router /auth/session [get] func getSession(c *gin.Context) { s := getSessionFromBearerToken(c.Request) if s == nil { @@ -161,6 +180,14 @@ func getSession(c *gin.Context) { } } +// deleteSession deletes the session. +// @Summary Delete session +// @Description Deletes the session. +// @Produce json +// @Success 200 {object} string +// @Failure 400 {object} HTTPErrorResponse +// @Failure 401 {object} HTTPErrorResponse +// @Router /auth/session [delete] func deleteSession(c *gin.Context) { s := getSessionFromBearerToken(c.Request) if s == nil || s.State() > SessionStateLoggedIn { @@ -175,6 +202,13 @@ func deleteSession(c *gin.Context) { c.JSON(http.StatusOK, "you are now logged out") } +// listBackends lists authentication backends. +// @Summary List authentication backends +// @Description Lists authentication backends. +// @Produce json +// @Success 200 {object} []AuthBackendInfo +// @Failure 404 {object} HTTPErrorResponse +// @Router /auth/backends [get] func listBackends(c *gin.Context) { backends := []AuthBackendInfo{} if auth.backends.oidc != nil { diff --git a/auth/backend_oidc.go b/auth/backend_oidc.go index b99b75700b93a9266b27e46de5490a7631e53f6d..ec8cc2a1d8cd7300091da69dfc4731d74e57061b 100644 --- a/auth/backend_oidc.go +++ b/auth/backend_oidc.go @@ -225,6 +225,16 @@ func (b *OIDCBackend) NewSession(ctx context.Context, arguments json.RawMessage) return } +// Login creates a session via OIDC. +// @Summary Create OIDC session +// @Description Creates a session via OIDC. Redirects to identity provider. +// @Produce json +// @Param session-id query string true "OIDC session ID" +// @Success 302 +// @Failure 400 {object} HTTPErrorResponse +// @Failure 401 {object} HTTPErrorResponse +// @Failure 409 {object} HTTPErrorResponse +// @Router /auth/oidc/login [get] func (b *OIDCBackend) Login(c *gin.Context) { s := auth.sessions.get(c.Query("session-id")) if s == nil { @@ -244,6 +254,19 @@ func (b *OIDCBackend) Login(c *gin.Context) { c.Redirect(http.StatusFound, b.oauth2Config.AuthCodeURL(s.ID(), oidc.Nonce(s.oidc.nonce))) } +// Callback completes OIDC login. +// @Summary Complete OIDC login +// @Description Completes OIDC login. +// @Produce json +// @Param state query string true "OIDC state" +// @Param code query string true "OIDC code" +// @Success 200 {object} string +// @Failure 400 {object} HTTPErrorResponse +// @Failure 401 {object} HTTPErrorResponse +// @Failure 409 {object} HTTPErrorResponse +// @Failure 410 {object} HTTPErrorResponse +// @Failure 500 {object} HTTPErrorResponse +// @Router /auth/oidc/callback [get] func (b *OIDCBackend) Callback(c *gin.Context) { s := auth.sessions.get(c.Query("state")) if s == nil { diff --git a/auth/utils.go b/auth/utils.go index 36f4d28e6fd78c2d47deb2c4018fb13314d3261a..531942641f19cd6f282af0599e77dc399d947307 100644 --- a/auth/utils.go +++ b/auth/utils.go @@ -84,7 +84,7 @@ func (s *BackendState) MarshalText() (data []byte, err error) { type AuthBackendInfo struct { Name string Description string - State *BackendState + State *BackendState `swaggertype:"string" enums:"new,initializing,ready,failed,destroyed,unknown"` } type NewSessionRequest struct { diff --git a/cmd/tank/web.go b/cmd/tank/web.go index 163dcf890b4659d52c07ad31e27d6bce53e6a88f..fa6a7ad6f815f8ab24ea9451081087a23a265c40 100644 --- a/cmd/tank/web.go +++ b/cmd/tank/web.go @@ -30,6 +30,9 @@ import ( "github.com/gin-gonic/gin" cors "github.com/rs/cors/wrapper/gin" + "github.com/swaggo/gin-swagger" + "github.com/swaggo/gin-swagger/swaggerFiles" + _ "gitlab.servus.at/autoradio/tank/api/docs" apiV1 "gitlab.servus.at/autoradio/tank/api/v1" "gitlab.servus.at/autoradio/tank/auth" "gitlab.servus.at/autoradio/tank/importer" @@ -38,10 +41,11 @@ import ( ) const ( - WebUIPathPrefix = "/ui/" - WebAuthPrefix = "/auth/" - WebAPIv1Prefix = "/api/v1/" - HealthzEndpoint = "/healthz" + WebUIPathPrefix = "/ui/" + WebAuthPrefix = "/auth/" + WebAPIDocsPrefix = "/api/docs/" + WebAPIv1Prefix = "/api/v1/" + HealthzEndpoint = "/healthz" ) func apache2CombinedLogger(param gin.LogFormatterParams) string { @@ -105,11 +109,18 @@ func (s HealthStatus) MarshalText() (data []byte, err error) { } type Health struct { - Auth HealthStatus `json:"auth"` - Store HealthStatus `json:"store"` - Importer HealthStatus `json:"importer"` + Auth HealthStatus `json:"auth" swaggertype:"string"` + Store HealthStatus `json:"store" swaggertype:"string"` + Importer HealthStatus `json:"importer" swaggertype:"string"` } +// healthzHandler checks daemon health. +// @Summary Check health +// @Description Checks daemon health. +// @Produce json +// @Success 200 {object} Health +// @Failure 503 {object} Health +// @Router /healthz [get] func healthzHandler(c *gin.Context, st *store.Store, im *importer.Importer) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // TODO: hardcoded value defer cancel() @@ -161,6 +172,7 @@ func runWeb(ln net.Listener, st *store.Store, im *importer.Importer, conf WebCon apiV1.InstallHTTPHandler(r.Group(WebAPIv1Prefix), st, im, infoLog, errLog, dbgLog) auth.InstallHTTPHandler(r.Group(WebAuthPrefix)) r.GET(HealthzEndpoint, func(c *gin.Context) { healthzHandler(c, st, im) }) + r.GET(WebAPIDocsPrefix+"*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) srv := &http.Server{ Handler: r, diff --git a/go.mod b/go.mod index 7aade99ebc822b21ed7bbe874d9287b4dee00638..f9c7dd1d5b429fa221448b8796d5c63c38eac8e7 100644 --- a/go.mod +++ b/go.mod @@ -6,15 +6,24 @@ require ( github.com/codegangsta/cli v1.20.0 github.com/coreos/go-oidc v2.0.0+incompatible github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 - github.com/gin-gonic/gin v1.4.0 + github.com/gin-gonic/gin v1.7.7 + github.com/go-openapi/swag v0.21.1 // indirect github.com/jinzhu/gorm v1.9.8 - github.com/kr/pretty v0.1.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect github.com/rs/cors v1.6.0 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect - github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd // indirect + github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect + github.com/swaggo/gin-swagger v1.4.1 + github.com/swaggo/swag v1.8.0 + github.com/urfave/cli/v2 v2.4.0 // indirect + golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914 + golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect + golang.org/x/tools v0.1.10 // indirect gopkg.in/gormigrate.v1 v1.5.0 gopkg.in/square/go-jose.v2 v2.3.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22 + gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 ) diff --git a/go.sum b/go.sum index 2dbbbb0f9c279aafa125d5c8b5e98a45ffaf9cf5..3a74fdbc2964f8356f9b04cfd2b678c64507a3f9 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,16 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs= +github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -17,6 +25,11 @@ github.com/coreos/go-oidc v2.0.0+incompatible h1:+RStIopZ8wooMx+Vs5Bt8zMXxV1ABl5 github.com/coreos/go-oidc v2.0.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -30,12 +43,38 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g= -github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= -github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ= -github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/gzip v0.0.3 h1:etUaeesHhEORpZMp18zoOhepboiWnFtXrBZxszWUn4k= +github.com/gin-contrib/gzip v0.0.3/go.mod h1:YxxswVZIqOvcHEQpsSn+QF5guQtO1dCfy0shBPy4jFc= +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.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= +github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +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/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +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.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -47,15 +86,17 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +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/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -72,9 +113,12 @@ github.com/jinzhu/now v1.0.0 h1:6WV8LvwPpDhKjo5U9O6b4+xdG/jTXNPwlDme/MTo8Ns= github.com/jinzhu/now v1.0.0/go.mod h1:oHTiXerJ20+SfYcrdlBO7rzZRJWGwSTQ0iUY2jI6Gfc= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +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/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +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/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= @@ -84,24 +128,46 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 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/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0 h1:/5u4a+KGJptBRqGzPvYQL9p0d/tPR4S31+Tnzj9lEO4= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +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 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +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/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +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 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/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/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= +github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.3 h1:7JgpsBaN0uMkyju4tbYHu0mnM55hNKVYLsXmwr15NQI= +github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -118,18 +184,44 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0= -github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU= +github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= +github.com/swaggo/gin-swagger v1.4.1 h1:F2vJndw+Q+ZBOlsC6CaodqXJV3ZOf6hpg/4Y6MEx5BM= +github.com/swaggo/gin-swagger v1.4.1/go.mod h1:hmJ1vPn+XjUvnbzjCdUAxVqgraxELxk8x5zAsjCE5mg= +github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= +github.com/swaggo/swag v1.8.0 h1:80NNhvpJcuItNpBDqgJwDuKlMmaZ/OATOzhG3bhcM3w= +github.com/swaggo/swag v1.8.0/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/urfave/cli/v2 v2.4.0 h1:m2pxjjDFgDxSPtO8WSdbndj17Wu2y8vOT86wE/tjr+I= +github.com/urfave/cli/v2 v2.4.0/go.mod h1:NX9W0zmTvedE5oDoOMs2RTC8RvdK98NTYZE5LbaEYPg= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -137,10 +229,19 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90Pveol golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= 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= @@ -150,8 +251,17 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/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= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 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-20190319182350-c85d3e98c914 h1:jIOcLT9BZzyJ9ce+IwwZ+aF9yeCqzrR+NrD68a/SHKw= @@ -160,21 +270,52 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ 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= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-20181122145206-62eef0e2fa9b/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-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs= +golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -190,11 +331,9 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= -gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/gormigrate.v1 v1.5.0 h1:M667uzFRcnBf5cNAcSyYyNdJTJ1KQnUTmc+mjCLfPfw= gopkg.in/gormigrate.v1 v1.5.0/go.mod h1:Lf00lQrHqfSYWiTtPcyQabsDdM6ejZaMgV0OU6JMSlw= gopkg.in/square/go-jose.v2 v2.3.0 h1:nLzhkFyl5bkblqYBoiWJUt5JkWOzmiaBtCxdJAqJd3U= @@ -204,8 +343,13 @@ gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22 h1:0efs3hwEZhFKsCoP8l6dDB1AZWMgnEl3yWXWRZTOaEA= -gopkg.in/yaml.v3 v3.0.0-20190709130402-674ba3eaed22/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/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 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=