mirror of
https://github.com/go-shiori/shiori.git
synced 2025-09-27 07:18:38 +08:00
Swagger improvements (#666)
* refactor: swagger docs into a folder * added scripts for the swaggger tasks * check version and fmt * CI * formatted swag comments * using custom delims * revert custom delims * swag 1.16.1 * update swagger docs * avoid make swagger output * swagger check * test * swag-fmt * swagger run * gofmt * avoid swag-fmt check for now due to inconsistencies with gofmt * re-enabled by using go fmt afterwards * use newer swag in CI * add gopath to path * using go binary instead of unset env * alternative * correct swag version * formatted * formatted * correct go fmt command * make swagger * swagger-check -> swag-check * run swag-check on lint
This commit is contained in:
parent
2e1016e5f1
commit
8b015a3850
12 changed files with 160 additions and 59 deletions
21
.github/workflows/_swagger-check.yml
vendored
Normal file
21
.github/workflows/_swagger-check.yml
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
name: "swagger-check"
|
||||
|
||||
on: workflow_call
|
||||
|
||||
jobs:
|
||||
swagger-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3.4.0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # v3.5.0
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- name: Install dependencies
|
||||
run: go install $(cat go.mod | grep swaggo/swag | cut -d " " -f 1)/cmd/swag@$(cat go.mod | grep swaggo/swag | cut -d " " -f 2)
|
||||
|
||||
- name: check
|
||||
run: make swag-check
|
||||
|
4
.github/workflows/pull_request.yml
vendored
4
.github/workflows/pull_request.yml
vendored
|
@ -14,8 +14,10 @@ jobs:
|
|||
uses: ./.github/workflows/_golangci-lint.yml
|
||||
call-test:
|
||||
uses: ./.github/workflows/_test.yml
|
||||
call-swagger-check:
|
||||
uses: ./.github/workflows/_swagger-check.yml
|
||||
call-gorelease:
|
||||
needs: [call-lint, call-test]
|
||||
needs: [call-lint, call-test, call-swagger-check]
|
||||
uses: ./.github/workflows/_gorelease.yml
|
||||
call-buildx:
|
||||
needs: call-gorelease
|
||||
|
|
23
Makefile
23
Makefile
|
@ -19,6 +19,10 @@ LDFLAGS += -s -w -X main.version=$(BUILD_HASH) -X main.date=$(BUILD_TIME)
|
|||
GIN_MODE ?= debug
|
||||
SHIORI_DEVELOPMENT ?= true
|
||||
|
||||
# Swagger
|
||||
SWAG_VERSION := v1.8.12
|
||||
SWAGGER_DOCS_PATH ?= ./docs/swagger
|
||||
|
||||
# Help documentatin à la https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
|
||||
.PHONY: help
|
||||
help:
|
||||
|
@ -42,11 +46,24 @@ run-server:
|
|||
## Generate swagger docs
|
||||
.PHONY: swagger
|
||||
swagger:
|
||||
swag init
|
||||
SWAGGER_DOCS_PATH=$(SWAGGER_DOCS_PATH) $(BASH) ./scripts/swagger.sh
|
||||
|
||||
## Run linter
|
||||
.PHONY: swag-check
|
||||
swag-check:
|
||||
REQUIRED_SWAG_VERSION=$(SWAG_VERSION) SWAGGER_DOCS_PATH=$(SWAGGER_DOCS_PATH) $(BASH) ./scripts/swagger_check.sh
|
||||
|
||||
.PHONY: swag-fmt
|
||||
swag-fmt:
|
||||
swag fmt --dir internal/http
|
||||
go fmt ./internal/http/...
|
||||
|
||||
## Run linters
|
||||
.PHONY: lint
|
||||
lint:
|
||||
lint: golangci-lint swag-check
|
||||
|
||||
## Run golangci-lint
|
||||
.PHONY: golangci-lint
|
||||
golangci-lint:
|
||||
golangci-lint run
|
||||
|
||||
## Run unit tests
|
||||
|
|
|
@ -35,7 +35,7 @@ make swagger
|
|||
|
||||
## Lint the code
|
||||
|
||||
In order to lint the code, you need to have installed [golangci-lint](https://golangci-lint.run).
|
||||
In order to lint the code, you need to have installed [golangci-lint](https://golangci-lint.run) and [swag](https://github.com/swaggo/swag).
|
||||
|
||||
After that, run the following command:
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Code generated by swaggo/swag. DO NOT EDIT.
|
||||
|
||||
package docs
|
||||
package swagger
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
|
||||
|
@ -34,7 +34,7 @@ const docTemplate = `{
|
|||
"name": "payload",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.loginRequestPayload"
|
||||
"$ref": "#/definitions/api_v1.loginRequestPayload"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -42,7 +42,7 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "Login successful",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.loginResponseMessage"
|
||||
"$ref": "#/definitions/api_v1.loginResponseMessage"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
@ -86,7 +86,7 @@ const docTemplate = `{
|
|||
"200": {
|
||||
"description": "Refresh successful",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.loginResponseMessage"
|
||||
"$ref": "#/definitions/api_v1.loginResponseMessage"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
|
@ -142,7 +142,7 @@ const docTemplate = `{
|
|||
}
|
||||
},
|
||||
"definitions": {
|
||||
"api.loginRequestPayload": {
|
||||
"api_v1.loginRequestPayload": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"password",
|
||||
|
@ -160,9 +160,17 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"api.loginResponseMessage": {
|
||||
"api_v1.loginResponseMessage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"expires": {
|
||||
"description": "Deprecated, used only for legacy APIs",
|
||||
"type": "integer"
|
||||
},
|
||||
"session": {
|
||||
"description": "Deprecated, used only for legacy APIs",
|
||||
"type": "string"
|
||||
},
|
||||
"token": {
|
||||
"type": "string"
|
||||
}
|
|
@ -22,7 +22,7 @@
|
|||
"name": "payload",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.loginRequestPayload"
|
||||
"$ref": "#/definitions/api_v1.loginRequestPayload"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -30,7 +30,7 @@
|
|||
"200": {
|
||||
"description": "Login successful",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.loginResponseMessage"
|
||||
"$ref": "#/definitions/api_v1.loginResponseMessage"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
|
@ -74,7 +74,7 @@
|
|||
"200": {
|
||||
"description": "Refresh successful",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/api.loginResponseMessage"
|
||||
"$ref": "#/definitions/api_v1.loginResponseMessage"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
|
@ -130,7 +130,7 @@
|
|||
}
|
||||
},
|
||||
"definitions": {
|
||||
"api.loginRequestPayload": {
|
||||
"api_v1.loginRequestPayload": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"password",
|
||||
|
@ -148,9 +148,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"api.loginResponseMessage": {
|
||||
"api_v1.loginResponseMessage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"expires": {
|
||||
"description": "Deprecated, used only for legacy APIs",
|
||||
"type": "integer"
|
||||
},
|
||||
"session": {
|
||||
"description": "Deprecated, used only for legacy APIs",
|
||||
"type": "string"
|
||||
},
|
||||
"token": {
|
||||
"type": "string"
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
definitions:
|
||||
api.loginRequestPayload:
|
||||
api_v1.loginRequestPayload:
|
||||
properties:
|
||||
password:
|
||||
type: string
|
||||
|
@ -11,8 +11,14 @@ definitions:
|
|||
- password
|
||||
- username
|
||||
type: object
|
||||
api.loginResponseMessage:
|
||||
api_v1.loginResponseMessage:
|
||||
properties:
|
||||
expires:
|
||||
description: Deprecated, used only for legacy APIs
|
||||
type: integer
|
||||
session:
|
||||
description: Deprecated, used only for legacy APIs
|
||||
type: string
|
||||
token:
|
||||
type: string
|
||||
type: object
|
||||
|
@ -48,14 +54,14 @@ paths:
|
|||
in: body
|
||||
name: payload
|
||||
schema:
|
||||
$ref: '#/definitions/api.loginRequestPayload'
|
||||
$ref: '#/definitions/api_v1.loginRequestPayload'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Login successful
|
||||
schema:
|
||||
$ref: '#/definitions/api.loginResponseMessage'
|
||||
$ref: '#/definitions/api_v1.loginResponseMessage'
|
||||
"400":
|
||||
description: Invalid login data
|
||||
summary: Login to an account using username and password
|
||||
|
@ -83,7 +89,7 @@ paths:
|
|||
"200":
|
||||
description: Refresh successful
|
||||
schema:
|
||||
$ref: '#/definitions/api.loginResponseMessage'
|
||||
$ref: '#/definitions/api_v1.loginResponseMessage'
|
||||
"403":
|
||||
description: Token not provided/invalid
|
||||
summary: Refresh a token for an account
|
|
@ -49,14 +49,15 @@ type loginResponseMessage struct {
|
|||
}
|
||||
|
||||
// loginHandler godoc
|
||||
// @Summary Login to an account using username and password
|
||||
// @Tags Auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param payload body loginRequestPayload false "Login data"
|
||||
// @Success 200 {object} loginResponseMessage "Login successful"
|
||||
// @Failure 400 {object} nil "Invalid login data"
|
||||
// @Router /api/v1/auth/login [post]
|
||||
//
|
||||
// @Summary Login to an account using username and password
|
||||
// @Tags Auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param payload body loginRequestPayload false "Login data"
|
||||
// @Success 200 {object} loginResponseMessage "Login successful"
|
||||
// @Failure 400 {object} nil "Invalid login data"
|
||||
// @Router /api/v1/auth/login [post]
|
||||
func (r *AuthAPIRoutes) loginHandler(c *gin.Context) {
|
||||
var payload loginRequestPayload
|
||||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||||
|
@ -103,13 +104,14 @@ func (r *AuthAPIRoutes) loginHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
// refreshHandler godoc
|
||||
// @Summary Refresh a token for an account
|
||||
// @Tags Auth
|
||||
// @securityDefinitions.apikey ApiKeyAuth
|
||||
// @Produce json
|
||||
// @Success 200 {object} loginResponseMessage "Refresh successful"
|
||||
// @Failure 403 {object} nil "Token not provided/invalid"
|
||||
// @Router /api/v1/auth/refresh [post]
|
||||
//
|
||||
// @Summary Refresh a token for an account
|
||||
// @Tags Auth
|
||||
// @securityDefinitions.apikey ApiKeyAuth
|
||||
// @Produce json
|
||||
// @Success 200 {object} loginResponseMessage "Refresh successful"
|
||||
// @Failure 403 {object} nil "Token not provided/invalid"
|
||||
// @Router /api/v1/auth/refresh [post]
|
||||
func (r *AuthAPIRoutes) refreshHandler(c *gin.Context) {
|
||||
ctx := context.NewContextFromGin(c)
|
||||
if !ctx.UserIsLogged() {
|
||||
|
@ -133,13 +135,14 @@ func (r *AuthAPIRoutes) refreshHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
// meHandler godoc
|
||||
// @Summary Get information for the current logged in user
|
||||
// @Tags Auth
|
||||
// @securityDefinitions.apikey ApiKeyAuth
|
||||
// @Produce json
|
||||
// @Success 200 {object} model.Account
|
||||
// @Failure 403 {object} nil "Token not provided/invalid"
|
||||
// @Router /api/v1/auth/me [get]
|
||||
//
|
||||
// @Summary Get information for the current logged in user
|
||||
// @Tags Auth
|
||||
// @securityDefinitions.apikey ApiKeyAuth
|
||||
// @Produce json
|
||||
// @Success 200 {object} model.Account
|
||||
// @Failure 403 {object} nil "Token not provided/invalid"
|
||||
// @Router /api/v1/auth/me [get]
|
||||
func (r *AuthAPIRoutes) meHandler(c *gin.Context) {
|
||||
ctx := context.NewContextFromGin(c)
|
||||
if !ctx.UserIsLogged() {
|
||||
|
|
|
@ -21,13 +21,13 @@ func (r *TagsAPIRoutes) Setup(g *gin.RouterGroup) model.Routes {
|
|||
return r
|
||||
}
|
||||
|
||||
// @Summary List tags
|
||||
// @Tags Tags
|
||||
// @securityDefinitions.apikey ApiKeyAuth
|
||||
// @Produce json
|
||||
// @Success 200 {object} model.Tag "List of tags"
|
||||
// @Failure 403 {object} nil "Token not provided/invalid"
|
||||
// @Router /api/v1/tags [get]
|
||||
// @Summary List tags
|
||||
// @Tags Tags
|
||||
// @securityDefinitions.apikey ApiKeyAuth
|
||||
// @Produce json
|
||||
// @Success 200 {object} model.Tag "List of tags"
|
||||
// @Failure 403 {object} nil "Token not provided/invalid"
|
||||
// @Router /api/v1/tags [get]
|
||||
func (r *TagsAPIRoutes) listHandler(c *gin.Context) {
|
||||
tags, err := r.deps.Database.GetTags(c)
|
||||
if err != nil {
|
||||
|
@ -38,14 +38,14 @@ func (r *TagsAPIRoutes) listHandler(c *gin.Context) {
|
|||
response.Send(c, http.StatusOK, tags)
|
||||
}
|
||||
|
||||
// @Summary Create tag
|
||||
// @Tags Tags
|
||||
// @securityDefinitions.apikey ApiKeyAuth
|
||||
// @Produce json
|
||||
// @Success 200 {object} model.Tag "Created tag"
|
||||
// @Failure 400 {object} nil "Token not provided/invalid"
|
||||
// @Failure 403 {object} nil "Token not provided/invalid"
|
||||
// @Router /api/v1/tags [post]
|
||||
// @Summary Create tag
|
||||
// @Tags Tags
|
||||
// @securityDefinitions.apikey ApiKeyAuth
|
||||
// @Produce json
|
||||
// @Success 200 {object} model.Tag "Created tag"
|
||||
// @Failure 400 {object} nil "Token not provided/invalid"
|
||||
// @Failure 403 {object} nil "Token not provided/invalid"
|
||||
// @Router /api/v1/tags [post]
|
||||
func (r *TagsAPIRoutes) createHandler(c *gin.Context) {
|
||||
var tag model.Tag
|
||||
if err := c.BindJSON(&tag); err != nil {
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
swaggerfiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
|
||||
_ "github.com/go-shiori/shiori/docs" // docs is generated by Swag CLI, you have to import it.
|
||||
_ "github.com/go-shiori/shiori/docs/swagger"
|
||||
)
|
||||
|
||||
type SwaggerAPIRoutes struct {
|
||||
|
|
4
scripts/swagger.sh
Normal file
4
scripts/swagger.sh
Normal file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This script is used to generate the swagger files for the API.
|
||||
swag init --output=$SWAGGER_DOCS_PATH
|
32
scripts/swagger_check.sh
Normal file
32
scripts/swagger_check.sh
Normal file
|
@ -0,0 +1,32 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This script is used to check if the swagger files are up to date.
|
||||
|
||||
# Check if swag version is correct
|
||||
CURRENT_SWAG_VERSION=$(swag --version | cut -d " " -f 3)
|
||||
if [ "$CURRENT_SWAG_VERSION" != "$REQUIRED_SWAG_VERSION" ]; then
|
||||
echo "swag version is incorrect. Required version: $REQUIRED_SWAG_VERSION, current version: $CURRENT_SWAG_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if the git tree for CWD is clean
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "❌ git tree is not clean. Please commit all changes before running this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check swag comments
|
||||
make swag-fmt
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "❌ swag comments are not formatted. Please run 'make swag-fmt' and commit the changes."
|
||||
git reset --hard
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check swagger documentation
|
||||
make swagger
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "❌ swagger documentation not updated, please run 'make swagger' and commit the changes."
|
||||
git reset --hard
|
||||
exit 1
|
||||
fi
|
Loading…
Add table
Reference in a new issue