mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
API: Add missing Swagger annotations and update swagger.json
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -20,6 +20,13 @@ import (
|
||||
var swaggerJSON []byte
|
||||
|
||||
// GetDocs registers the Swagger API documentation endpoints.
|
||||
//
|
||||
// @Summary serves embedded Swagger documentation (debug builds only)
|
||||
// @Id GetDocs
|
||||
// @Tags System
|
||||
// @Produce json
|
||||
// @Success 200 {object} gin.H "Swagger JSON"
|
||||
// @Router /swagger.json [get]
|
||||
func GetDocs(router *gin.RouterGroup) {
|
||||
// Get global configuration.
|
||||
conf := get.Config()
|
||||
|
||||
@@ -187,7 +187,13 @@ func StartImport(router *gin.RouterGroup) {
|
||||
|
||||
// CancelImport stops the current import operation.
|
||||
//
|
||||
// DELETE /api/v1/import
|
||||
// @Summary cancels the active import job
|
||||
// @Id CancelImport
|
||||
// @Tags Library
|
||||
// @Produce json
|
||||
// @Success 200 {object} i18n.Response
|
||||
// @Failure 401,403,404,429 {object} i18n.Response
|
||||
// @Router /api/v1/import [delete]
|
||||
func CancelImport(router *gin.RouterGroup) {
|
||||
router.DELETE("/import", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourceFiles, acl.ActionManage)
|
||||
|
||||
@@ -179,7 +179,13 @@ func StartIndexing(router *gin.RouterGroup) {
|
||||
|
||||
// CancelIndexing stops indexing media files in the "originals" folder.
|
||||
//
|
||||
// DELETE /api/v1/index
|
||||
// @Summary cancels the active indexing job
|
||||
// @Id CancelIndexing
|
||||
// @Tags Library
|
||||
// @Produce json
|
||||
// @Success 200 {object} i18n.Response
|
||||
// @Failure 401,403,404,429 {object} i18n.Response
|
||||
// @Router /api/v1/index [delete]
|
||||
func CancelIndexing(router *gin.RouterGroup) {
|
||||
router.DELETE("/index", func(c *gin.Context) {
|
||||
s := Auth(c, acl.ResourcePhotos, acl.ActionUpdate)
|
||||
|
||||
@@ -15,7 +15,12 @@ import (
|
||||
// OAuthUserinfo should return information about the authenticated user,
|
||||
// see https://github.com/photoprism/photoprism/issues/4369.
|
||||
//
|
||||
// GET /api/v1/oauth/userinfo
|
||||
// @Summary OAuth2 userinfo endpoint (not implemented)
|
||||
// @Id OAuthUserinfo
|
||||
// @Tags Authentication
|
||||
// @Produce json
|
||||
// @Failure 405 {object} i18n.Response
|
||||
// @Router /api/v1/oauth/userinfo [get]
|
||||
func OAuthUserinfo(router *gin.RouterGroup) {
|
||||
router.GET("/oauth/userinfo", func(c *gin.Context) {
|
||||
// Prevent CDNs from caching this endpoint.
|
||||
|
||||
@@ -12,10 +12,17 @@ import (
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
)
|
||||
|
||||
// Shares handles link share
|
||||
// ShareToken renders the generic share landing page.
|
||||
//
|
||||
// GET /s/:token/...
|
||||
func Shares(router *gin.RouterGroup) {
|
||||
// @Summary renders the generic share landing page
|
||||
// @Id ShareToken
|
||||
// @Tags Shares
|
||||
// @Produce text/html
|
||||
// @Param token path string true "Share token"
|
||||
// @Success 200 {string} string "Rendered HTML page"
|
||||
// @Failure 302 {string} string "Redirect to the base site when the token is invalid"
|
||||
// @Router /s/{token} [get]
|
||||
func ShareToken(router *gin.RouterGroup) {
|
||||
router.GET("/:token", func(c *gin.Context) {
|
||||
conf := get.Config()
|
||||
|
||||
@@ -34,7 +41,20 @@ func Shares(router *gin.RouterGroup) {
|
||||
uri := conf.LibraryUri("/albums")
|
||||
c.HTML(http.StatusOK, "share.gohtml", gin.H{"shared": gin.H{"token": token, "uri": uri}, "config": clientConfig})
|
||||
})
|
||||
}
|
||||
|
||||
// ShareTokenShared renders the album/page view for an individual share.
|
||||
//
|
||||
// @Summary renders the album/page view for an individual share
|
||||
// @Id ShareTokenShared
|
||||
// @Tags Shares
|
||||
// @Produce text/html
|
||||
// @Param token path string true "Share token"
|
||||
// @Param shared path string true "Shared resource UID"
|
||||
// @Success 200 {string} string "Rendered HTML page"
|
||||
// @Failure 302 {string} string "Redirect to the base site when the token is invalid"
|
||||
// @Router /s/{token}/{shared} [get]
|
||||
func ShareTokenShared(router *gin.RouterGroup) {
|
||||
router.GET("/:token/:shared", func(c *gin.Context) {
|
||||
conf := get.Config()
|
||||
|
||||
|
||||
@@ -25,8 +25,15 @@ import (
|
||||
|
||||
// SharePreview returns a preview image for the given share uid if the token is valid.
|
||||
//
|
||||
// GET /s/:token/:shared/preview
|
||||
// TODO: Proof of concept, needs refactoring.
|
||||
// @Summary returns a share preview image when the token is valid
|
||||
// @Id SharePreview
|
||||
// @Tags Shares
|
||||
// @Produce image/jpeg
|
||||
// @Param token path string true "Share token"
|
||||
// @Param shared path string true "Shared resource UID"
|
||||
// @Success 200 {file} file "Preview image"
|
||||
// @Failure 302 {string} string "Redirect to the default preview page"
|
||||
// @Router /s/{token}/{shared}/preview [get]
|
||||
func SharePreview(router *gin.RouterGroup) {
|
||||
router.GET("/:token/:shared/preview", func(c *gin.Context) {
|
||||
conf := get.Config()
|
||||
|
||||
@@ -7,31 +7,34 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetShares(t *testing.T) {
|
||||
func TestShareToken(t *testing.T) {
|
||||
t.Run("InvalidToken", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
Shares(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/1jxf3jfn2k/ss6sg6bxpogaaba7")
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, r.Code)
|
||||
})
|
||||
//TODO Why does it panic?
|
||||
/*t.Run("ValidTokenAndShare", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
Shares(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/4jxf3jfn2k/as6sg6bxpogaaba7")
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, r.Code)
|
||||
})*/
|
||||
t.Run("InvalidToken", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
Shares(router)
|
||||
ShareToken(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/xxx")
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, r.Code)
|
||||
})
|
||||
//TODO Why does it panic?
|
||||
/*t.Run("ValidToken", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
Shares(router)
|
||||
ShareToken(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/4jxf3jfn2k")
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, r.Code)
|
||||
})*/
|
||||
}
|
||||
|
||||
func TestShareTokenShared(t *testing.T) {
|
||||
t.Run("InvalidToken", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
ShareTokenShared(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/1jxf3jfn2k/ss6sg6bxpogaaba7")
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, r.Code)
|
||||
})
|
||||
//TODO Why does it panic?
|
||||
/*t.Run("ValidTokenAndShare", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
ShareTokenShared(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/4jxf3jfn2k/as6sg6bxpogaaba7")
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, r.Code)
|
||||
})*/
|
||||
}
|
||||
|
||||
@@ -48,7 +48,14 @@ var uncachedIconSvg []byte
|
||||
|
||||
// GetSvg returns SVG placeholder symbols.
|
||||
//
|
||||
// GET /api/v1/svg/*
|
||||
// @Summary returns SVG placeholder symbols for UI fallbacks
|
||||
// @Id GetSvg
|
||||
// @Tags Assets
|
||||
// @Produce image/svg+xml
|
||||
// @Param icon path string true "SVG icon name" Enums(user,face,camera,photo,raw,file,video,label,portrait,folder,album,broken,uncached)
|
||||
// @Success 200 {string} string "SVG icon"
|
||||
// @Failure 404 {object} gin.H "Icon not found"
|
||||
// @Router /api/v1/svg/{icon} [get]
|
||||
func GetSvg(router *gin.RouterGroup) {
|
||||
router.GET("/svg/user", func(c *gin.Context) {
|
||||
c.Data(http.StatusOK, "image/svg+xml", userIconSvg)
|
||||
|
||||
@@ -7871,6 +7871,50 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/import": {
|
||||
"delete": {
|
||||
"operationId": "CancelImport",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/i18n.Response"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/i18n.Response"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/i18n.Response"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/i18n.Response"
|
||||
}
|
||||
},
|
||||
"429": {
|
||||
"description": "Too Many Requests",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/i18n.Response"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "cancels the active import job",
|
||||
"tags": [
|
||||
"Library"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/import/": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
@@ -7924,6 +7968,48 @@
|
||||
}
|
||||
},
|
||||
"/api/v1/index": {
|
||||
"delete": {
|
||||
"operationId": "CancelIndexing",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/i18n.Response"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/i18n.Response"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/i18n.Response"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/i18n.Response"
|
||||
}
|
||||
},
|
||||
"429": {
|
||||
"description": "Too Many Requests",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/i18n.Response"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "cancels the active indexing job",
|
||||
"tags": [
|
||||
"Library"
|
||||
]
|
||||
},
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
@@ -8657,6 +8743,26 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/oauth/userinfo": {
|
||||
"get": {
|
||||
"operationId": "OAuthUserinfo",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"responses": {
|
||||
"405": {
|
||||
"description": "Method Not Allowed",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/i18n.Response"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "OAuth2 userinfo endpoint (not implemented)",
|
||||
"tags": [
|
||||
"Authentication"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/oidc/login": {
|
||||
"get": {
|
||||
"operationId": "OIDCLogin",
|
||||
@@ -11236,6 +11342,56 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/svg/{icon}": {
|
||||
"get": {
|
||||
"operationId": "GetSvg",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "SVG icon name",
|
||||
"enum": [
|
||||
"user",
|
||||
"face",
|
||||
"camera",
|
||||
"photo",
|
||||
"raw",
|
||||
"file",
|
||||
"video",
|
||||
"label",
|
||||
"portrait",
|
||||
"folder",
|
||||
"album",
|
||||
"broken",
|
||||
"uncached"
|
||||
],
|
||||
"in": "path",
|
||||
"name": "icon",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"image/svg+xml"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "SVG icon",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Icon not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gin.H"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "returns SVG placeholder symbols for UI fallbacks",
|
||||
"tags": [
|
||||
"Assets"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/t/{thumb}/{token}/{size}": {
|
||||
"get": {
|
||||
"description": "Fore more information see:\n- https://docs.photoprism.app/developer-guide/api/thumbnails/#image-endpoint-uri",
|
||||
@@ -12382,6 +12538,168 @@
|
||||
"CORS"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/s/{token}": {
|
||||
"get": {
|
||||
"operationId": "ShareToken",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Share token",
|
||||
"in": "path",
|
||||
"name": "token",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"text/html"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Rendered HTML page",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"302": {
|
||||
"description": "Redirect to the base site when the token is invalid",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "renders the generic share landing page",
|
||||
"tags": [
|
||||
"Shares"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/s/{token}/{shared}": {
|
||||
"get": {
|
||||
"operationId": "ShareTokenShared",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Share token",
|
||||
"in": "path",
|
||||
"name": "token",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Shared resource UID",
|
||||
"in": "path",
|
||||
"name": "shared",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"text/html"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Rendered HTML page",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"302": {
|
||||
"description": "Redirect to the base site when the token is invalid",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "renders the album/page view for an individual share",
|
||||
"tags": [
|
||||
"Shares"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/s/{token}/{shared}/preview": {
|
||||
"get": {
|
||||
"operationId": "SharePreview",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Share token",
|
||||
"in": "path",
|
||||
"name": "token",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"description": "Shared resource UID",
|
||||
"in": "path",
|
||||
"name": "shared",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"image/jpeg"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Preview image",
|
||||
"schema": {
|
||||
"type": "file"
|
||||
}
|
||||
},
|
||||
"302": {
|
||||
"description": "Redirect to the default preview page",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "returns a share preview image when the token is valid",
|
||||
"tags": [
|
||||
"Shares"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/swagger.json": {
|
||||
"get": {
|
||||
"operationId": "GetDocs",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Swagger JSON",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/gin.H"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "serves embedded Swagger documentation (debug builds only)",
|
||||
"tags": [
|
||||
"System"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/ws": {
|
||||
"get": {
|
||||
"operationId": "WebSocket",
|
||||
"responses": {
|
||||
"101": {
|
||||
"description": "Switching Protocols",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "opens a WebSocket connection for real-time events",
|
||||
"tags": [
|
||||
"WebSocket"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
|
||||
@@ -11,6 +11,13 @@ import (
|
||||
)
|
||||
|
||||
// WebSocket registers the /ws endpoint for establishing websocket connections.
|
||||
//
|
||||
// @Summary opens a WebSocket connection for real-time events
|
||||
// @Id WebSocket
|
||||
// @Tags WebSocket
|
||||
// @Success 101 {string} string "Switching Protocols"
|
||||
// @Failure 403 {string} string "Forbidden"
|
||||
// @Router /ws [get]
|
||||
func WebSocket(router *gin.RouterGroup) {
|
||||
if router == nil {
|
||||
return
|
||||
|
||||
@@ -16,7 +16,8 @@ func registerSharingRoutes(router *gin.Engine, conf *config.Config) {
|
||||
|
||||
s := router.Group(conf.BaseUri("/s"))
|
||||
{
|
||||
api.Shares(s)
|
||||
api.ShareToken(s)
|
||||
api.ShareTokenShared(s)
|
||||
api.SharePreview(s)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user