Commit fe967ec1 authored by Ernesto Rico Schmidt's avatar Ernesto Rico Schmidt
Browse files

Merge branch 'feature/7-api-docs' into 'master'

Add api docs generated by swaggo/swag

See merge request !5
parents f7109bfc 752135a8
Pipeline #1781 passed with stages
in 20 minutes and 41 seconds
/tank
/tank.yaml
/vendor/
/cover.out
/coverage.html
/api/docs/swagger.*
\ No newline at end of file
......@@ -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:
......
......@@ -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
......
This diff is collapsed.
//
// tank, Import and Playlist Daemon for Aura project
// Copyright (C) 2017-2020 Christian Pointner <equinox@helsinki.at>
// Copyright (C) 2017-2020 Christian Pointner <equinox@helsinki.at>
//
// 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
......
......@@ -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 {
......
......@@ -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 {
......
......@@ -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 {
......
......@@ -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
......
......@@ -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 {
......
......@@ -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 {
......
......@@ -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"))