From b4e8b8ac81eb9eb2223ff1d9a7a18d1a621b0be2 Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Tue, 7 Feb 2023 11:23:49 +0100 Subject: [PATCH] [API] Forgejo API /api/forgejo/v1 --- Makefile | 31 +++- go.mod | 7 +- go.sum | 14 +- public/forgejo/api.v1.yml | 40 +++++ routers/api/forgejo/v1/api.go | 18 ++ routers/api/forgejo/v1/forgejo.go | 24 +++ routers/api/forgejo/v1/generated.go | 167 ++++++++++++++++++ routers/api/forgejo/v1/root.go | 14 ++ routers/init.go | 2 + routers/web/misc/swagger-forgejo.go | 19 ++ routers/web/web.go | 1 + templates/swagger/forgejo-ui.tmpl | 13 ++ tests/integration/api_forgejo_root_test.go | 21 +++ tests/integration/api_forgejo_version_test.go | 25 +++ web_src/js/standalone/forgejo-swagger.js | 22 +++ webpack.config.js | 4 + 16 files changed, 411 insertions(+), 11 deletions(-) create mode 100644 public/forgejo/api.v1.yml create mode 100644 routers/api/forgejo/v1/api.go create mode 100644 routers/api/forgejo/v1/forgejo.go create mode 100644 routers/api/forgejo/v1/generated.go create mode 100644 routers/api/forgejo/v1/root.go create mode 100644 routers/web/misc/swagger-forgejo.go create mode 100644 templates/swagger/forgejo-ui.tmpl create mode 100644 tests/integration/api_forgejo_root_test.go create mode 100644 tests/integration/api_forgejo_version_test.go create mode 100644 web_src/js/standalone/forgejo-swagger.js diff --git a/Makefile b/Makefile index d9f1194fc5..074c52a32f 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,8 @@ COMMA := , XGO_VERSION := go-1.20.x AIR_PACKAGE ?= github.com/cosmtrek/air@v1.40.4 +OAPI_CODEGEN_PACKAGE ?= github.com/deepmap/oapi-codegen/cmd/oapi-codegen@v1.12.4 +KIN_OPENAPI_CODEGEN_PACKAGE ?= github.com/getkin/kin-openapi/cmd/validate@v0.114.0 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.6.0 ERRCHECK_PACKAGE ?= github.com/kisielk/errcheck@v1.6.2 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.4.0 @@ -96,7 +98,9 @@ else endif endif -LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" +FORGEJO_VERSION := v2.0.0 # SemVer + +LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" -X "api/forgejo/v1.ForgejoVersion=$(FORGEJO_VERSION)" LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64 @@ -148,6 +152,8 @@ ifdef DEPS_PLAYWRIGHT PLAYWRIGHT_FLAGS += --with-deps endif +FORGEJO_API_SPEC := public/forgejo/api.v1.yml + SWAGGER_SPEC := templates/swagger/v1_json.tmpl SWAGGER_SPEC_S_TMPL := s|"basePath": *"/api/v1"|"basePath": "{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|g SWAGGER_SPEC_S_JSON := s|"basePath": *"{{AppSubUrl \| JSEscape \| Safe}}/api/v1"|"basePath": "/api/v1"|g @@ -208,6 +214,8 @@ help: @echo " - generate-license update license files" @echo " - generate-gitignore update gitignore files" @echo " - generate-manpage generate manpage" + @echo " - generate-forgejo-api generate the forgejo API from spec" + @echo " - forgejo-api-validate check if the forgejo API matches the specs" @echo " - generate-swagger generate the swagger spec from code comments" @echo " - swagger-validate check if the swagger spec is valid" @echo " - golangci-lint run golangci-lint linter" @@ -297,6 +305,25 @@ ifneq "$(TAGS)" "$(shell cat $(TAGS_EVIDENCE) 2>/dev/null)" TAGS_PREREQ := $(TAGS_EVIDENCE) endif +FORGEJO_API_SERVER = routers/api/forgejo/v1/generated.go + +.PHONY: generate-forgejo-api +generate-forgejo-api: $(FORGEJO_API_SPEC) + $(GO) run $(OAPI_CODEGEN_PACKAGE) -package v1 -generate chi-server,types $< > $(FORGEJO_API_SERVER) + +.PHONY: forgejo-api-check +forgejo-api-check: generate-forgejo-api + @diff=$$(git diff $(FORGEJO_API_SERVER) ; \ + if [ -n "$$diff" ]; then \ + echo "Please run 'make generate-forgejo-api' and commit the result:"; \ + echo "$${diff}"; \ + exit 1; \ + fi + +.PHONY: forgejo-api-validate +forgejo-api-validate: + $(GO) run $(KIN_OPENAPI_CODEGEN_PACKAGE) $(FORGEJO_API_SPEC) + .PHONY: generate-swagger generate-swagger: $(SWAGGER_SPEC) @@ -332,7 +359,7 @@ checks: checks-frontend checks-backend checks-frontend: lockfile-check svg-check .PHONY: checks-backend -checks-backend: tidy-check swagger-check fmt-check misspell-check swagger-validate security-check +checks-backend: tidy-check swagger-check fmt-check misspell-check forgejo-api-validate swagger-validate security-check .PHONY: lint lint: lint-frontend lint-backend diff --git a/go.mod b/go.mod index 3dc050b99a..75defac96b 100644 --- a/go.mod +++ b/go.mod @@ -107,7 +107,7 @@ require ( golang.org/x/oauth2 v0.3.0 golang.org/x/sys v0.3.0 golang.org/x/text v0.5.0 - golang.org/x/tools v0.1.12 + golang.org/x/tools v0.3.0 google.golang.org/grpc v1.47.0 google.golang.org/protobuf v1.28.1 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df @@ -184,6 +184,7 @@ require ( github.com/go-openapi/strfmt v0.21.3 // indirect github.com/go-openapi/swag v0.22.3 // indirect github.com/go-openapi/validate v0.22.0 // indirect + github.com/go-test/deep v1.0.8 // indirect github.com/go-webauthn/revoke v0.1.6 // indirect github.com/goccy/go-json v0.10.0 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect @@ -232,7 +233,7 @@ require ( github.com/oklog/ulid v1.3.1 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.1 // indirect + github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/pierrec/lz4/v4 v4.1.17 // indirect github.com/pjbgf/sha1cd v0.2.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -271,7 +272,7 @@ require ( go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/mod v0.7.0 // indirect - golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect + golang.org/x/sync v0.1.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90 // indirect diff --git a/go.sum b/go.sum index 03c2cc18c6..9a84552bfb 100644 --- a/go.sum +++ b/go.sum @@ -476,8 +476,9 @@ github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP github.com/go-swagger/go-swagger v0.30.3 h1:HuzvdMRed/9Q8vmzVcfNBQByZVtT79DNZxZ18OprdoI= github.com/go-swagger/go-swagger v0.30.3/go.mod h1:neDPes8r8PCz2JPvHRDj8BTULLh4VJUt7n6MpQqxhHM= github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0= -github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-testfixtures/testfixtures/v3 v3.8.1 h1:uonwvepqRvSgddcrReZQhojTlWlmOlHkYAb9ZaOMWgU= github.com/go-testfixtures/testfixtures/v3 v3.8.1/go.mod h1:Kdu7YeMC0KRXVHdaQ91Vmx3pcjoTF63h4f1qTJDdXLA= github.com/go-webauthn/revoke v0.1.6 h1:3tv+itza9WpX5tryRQx4GwxCCBrCIiJ8GIkOhxiAmmU= @@ -1030,8 +1031,8 @@ github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAv github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= -github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= @@ -1501,8 +1502,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc= -golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 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= @@ -1704,8 +1705,9 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0 h1:SrNbZl6ECOS1qFzgTdQfWXZM9XBkiA6tkFrH9YSTPHM= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/public/forgejo/api.v1.yml b/public/forgejo/api.v1.yml new file mode 100644 index 0000000000..903dd659d0 --- /dev/null +++ b/public/forgejo/api.v1.yml @@ -0,0 +1,40 @@ +openapi: 3.0.0 +info: + title: Forgejo API + description: |- + Forgejo REST API + + contact: + email: contact@forgejo.org + license: + name: MIT + url: https://codeberg.org/forgejo/forgejo/src/branch/forgejo/LICENSE + version: 1.0.0 +externalDocs: + description: Find out more about Forgejo + url: http://forgejo.org +servers: + - url: /api/forgejo/v1 +paths: + /version: + get: + summary: API version + description: Semantic version of the Forgejo API + operationId: getVersion + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Version' +components: + schemas: + Version: + type: object + properties: + number: + type: string + diff --git a/routers/api/forgejo/v1/api.go b/routers/api/forgejo/v1/api.go new file mode 100644 index 0000000000..2a933450ea --- /dev/null +++ b/routers/api/forgejo/v1/api.go @@ -0,0 +1,18 @@ +// Copyright 2023 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1 + +import ( + gocontext "context" + + "code.gitea.io/gitea/modules/web" +) + +func Routes(ctx gocontext.Context) *web.Route { + m := web.NewRoute() + forgejo := NewForgejo() + m.Get("", Root) + m.Get("/version", forgejo.GetVersion) + return m +} diff --git a/routers/api/forgejo/v1/forgejo.go b/routers/api/forgejo/v1/forgejo.go new file mode 100644 index 0000000000..54ab19d7bd --- /dev/null +++ b/routers/api/forgejo/v1/forgejo.go @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +package v1 + +import ( + "net/http" + + "code.gitea.io/gitea/modules/json" +) + +type Forgejo struct{} + +var _ ServerInterface = &Forgejo{} + +func NewForgejo() *Forgejo { + return &Forgejo{} +} + +var ForgejoVersion = "development" + +func (f *Forgejo) GetVersion(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(Version{&ForgejoVersion}) +} diff --git a/routers/api/forgejo/v1/generated.go b/routers/api/forgejo/v1/generated.go new file mode 100644 index 0000000000..afec612e85 --- /dev/null +++ b/routers/api/forgejo/v1/generated.go @@ -0,0 +1,167 @@ +// Package v1 provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version v1.12.4 DO NOT EDIT. +package v1 + +import ( + "fmt" + "net/http" + + "github.com/go-chi/chi/v5" +) + +// Version defines model for Version. +type Version struct { + Number *string `json:"number,omitempty"` +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // API version + // (GET /version) + GetVersion(w http.ResponseWriter, r *http.Request) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetVersion operation middleware +func (siw *ServerInterfaceWrapper) GetVersion(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetVersion(w, r) + }) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshallingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshallingParamError) Error() string { + return fmt.Sprintf("Error unmarshalling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshallingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/version", wrapper.GetVersion) + }) + + return r +} diff --git a/routers/api/forgejo/v1/root.go b/routers/api/forgejo/v1/root.go new file mode 100644 index 0000000000..b976c51292 --- /dev/null +++ b/routers/api/forgejo/v1/root.go @@ -0,0 +1,14 @@ +// Copyright The Forgejo Authors. +// SPDX-License-Identifier: MIT + +package v1 + +import ( + "net/http" +) + +func Root(w http.ResponseWriter, r *http.Request) { + // https://www.rfc-editor.org/rfc/rfc8631 + w.Header().Set("Link", "; rel=\"service-desc\"") + w.WriteHeader(http.StatusNoContent) +} diff --git a/routers/init.go b/routers/init.go index ab2e2a0bb7..7c5452bab8 100644 --- a/routers/init.go +++ b/routers/init.go @@ -31,6 +31,7 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" actions_router "code.gitea.io/gitea/routers/api/actions" + forgejo "code.gitea.io/gitea/routers/api/forgejo/v1" packages_router "code.gitea.io/gitea/routers/api/packages" apiv1 "code.gitea.io/gitea/routers/api/v1" "code.gitea.io/gitea/routers/common" @@ -190,6 +191,7 @@ func NormalRoutes(ctx context.Context) *web.Route { r.Mount("/", web_routers.Routes(ctx)) r.Mount("/api/v1", apiv1.Routes(ctx)) + r.Mount("/api/forgejo/v1", forgejo.Routes(ctx)) r.Mount("/api/internal", private.Routes()) if setting.Packages.Enabled { diff --git a/routers/web/misc/swagger-forgejo.go b/routers/web/misc/swagger-forgejo.go new file mode 100644 index 0000000000..2f539e955c --- /dev/null +++ b/routers/web/misc/swagger-forgejo.go @@ -0,0 +1,19 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package misc + +import ( + "net/http" + + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" +) + +// tplSwagger swagger page template +const tplForgejoSwagger base.TplName = "swagger/forgejo-ui" + +func SwaggerForgejo(ctx *context.Context) { + ctx.Data["APIVersion"] = "v1" + ctx.HTML(http.StatusOK, tplForgejoSwagger) +} diff --git a/routers/web/web.go b/routers/web/web.go index 88e27ad678..66c200c9f6 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -211,6 +211,7 @@ func Routes(ctx gocontext.Context) *web.Route { if setting.API.EnableSwagger { // Note: The route moved from apiroutes because it's in fact want to render a web page routes.Get("/api/swagger", append(common, misc.Swagger)...) // Render V1 by default + routes.Get("/api/forgejo/swagger", append(common, misc.SwaggerForgejo)...) } // TODO: These really seem like things that could be folded into Contexter or as helper functions diff --git a/templates/swagger/forgejo-ui.tmpl b/templates/swagger/forgejo-ui.tmpl new file mode 100644 index 0000000000..d0ee889753 --- /dev/null +++ b/templates/swagger/forgejo-ui.tmpl @@ -0,0 +1,13 @@ + + + + + Forgejo API + + + + {{svg "octicon-reply"}}{{.locale.Tr "return_to_gitea"}} +
+ + + diff --git a/tests/integration/api_forgejo_root_test.go b/tests/integration/api_forgejo_root_test.go new file mode 100644 index 0000000000..d21c9449b3 --- /dev/null +++ b/tests/integration/api_forgejo_root_test.go @@ -0,0 +1,21 @@ +// Copyright The Forgejo Authors. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIForgejoRoot(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", "/api/forgejo/v1") + resp := MakeRequest(t, req, http.StatusNoContent) + assert.Contains(t, resp.Header().Get("Link"), "/assets/forgejo/api.v1.yml") +} diff --git a/tests/integration/api_forgejo_version_test.go b/tests/integration/api_forgejo_version_test.go new file mode 100644 index 0000000000..cd904338a7 --- /dev/null +++ b/tests/integration/api_forgejo_version_test.go @@ -0,0 +1,25 @@ +// Copyright The Forgejo Authors. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/routers/api/forgejo/v1" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIForgejoVersion(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", "/api/forgejo/v1/version") + resp := MakeRequest(t, req, http.StatusOK) + + var version v1.Version + DecodeJSON(t, resp, &version) + assert.Equal(t, "development", *version.Number) +} diff --git a/web_src/js/standalone/forgejo-swagger.js b/web_src/js/standalone/forgejo-swagger.js new file mode 100644 index 0000000000..b565827b30 --- /dev/null +++ b/web_src/js/standalone/forgejo-swagger.js @@ -0,0 +1,22 @@ +import SwaggerUI from 'swagger-ui-dist/swagger-ui-es-bundle.js'; +import 'swagger-ui-dist/swagger-ui.css'; + +window.addEventListener('load', async () => { + const url = document.getElementById('swagger-ui').getAttribute('data-source'); + + const ui = SwaggerUI({ + url: url, + dom_id: '#swagger-ui', + deepLinking: true, + docExpansion: 'none', + defaultModelRendering: 'model', // don't show examples by default, because they may be incomplete + presets: [ + SwaggerUI.presets.apis + ], + plugins: [ + SwaggerUI.plugins.DownloadUrl + ] + }); + + window.ui = ui; +}); diff --git a/webpack.config.js b/webpack.config.js index e5e592052e..bcfaaabde1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -59,6 +59,10 @@ export default { fileURLToPath(new URL('web_src/fomantic/build/semantic.css', import.meta.url)), fileURLToPath(new URL('web_src/less/index.less', import.meta.url)), ], + forgejoswagger: [ // Forgejo swagger is OpenAPI 3.0.0 and has specific parameters + fileURLToPath(new URL('web_src/js/standalone/forgejo-swagger.js', import.meta.url)), + fileURLToPath(new URL('web_src/less/standalone/swagger.less', import.meta.url)), + ], swagger: [ fileURLToPath(new URL('web_src/js/standalone/swagger.js', import.meta.url)), fileURLToPath(new URL('web_src/less/standalone/swagger.less', import.meta.url)),