aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEarl Warren2023-02-07 11:23:49 +0100
committerLoïc Dachary2023-03-12 15:17:10 +0100
commitec5217b9d1b94bb04e34ce8c27eecbdc6f3a247a (patch)
tree10954da04e7c960906408b9cc5aebf27afc83f9a
parent7f625d25a3497dffbfbf983d6d250dbcaac57c34 (diff)
[API] Forgejo API /api/forgejo/v1
(cherry picked from commit 20b56692693e054bb3c04b4ef12b29b0715b4530) (cherry picked from commit 1574643a6a9634e5b92c033a4bfb69062a86bd05) Update semantic version according to specification (cherry picked from commit 22510f41306f9c133a7e99b61f9c38dabfd4b810) Mise à jour de 'Makefile' (cherry picked from commit c3d85d8409f1bb18a45659a167cf1ffee057f3b6) (cherry picked from commit 5ea23098513c068444226af41faf9be9c4c998e6)
-rw-r--r--Makefile32
-rw-r--r--public/forgejo/api.v1.yml40
-rw-r--r--routers/api/forgejo/v1/api.go18
-rw-r--r--routers/api/forgejo/v1/forgejo.go24
-rw-r--r--routers/api/forgejo/v1/generated.go167
-rw-r--r--routers/api/forgejo/v1/root.go14
-rw-r--r--routers/init.go2
-rw-r--r--routers/web/misc/swagger-forgejo.go19
-rw-r--r--routers/web/web.go1
-rw-r--r--templates/swagger/forgejo-ui.tmpl13
-rw-r--r--tests/integration/api_forgejo_root_test.go21
-rw-r--r--tests/integration/api_forgejo_version_test.go25
-rw-r--r--web_src/js/standalone/forgejo-swagger.js22
-rw-r--r--webpack.config.js6
14 files changed, 401 insertions, 3 deletions
diff --git a/Makefile b/Makefile
index 52efa6493..49851a30a 100644
--- a/Makefile
+++ b/Makefile
@@ -96,7 +96,10 @@ else
endif
endif
-LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)"
+# SemVer
+FORGEJO_VERSION := 3.0.0+0-gitea-1.19.0
+
+LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" -X "code.gitea.io/gitea/routers/api/forgejo/v1.ForgejoVersion=$(FORGEJO_VERSION)"
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
@@ -148,6 +151,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
@@ -209,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"
@@ -298,6 +305,27 @@ ifneq "$(TAGS)" "$(shell cat $(TAGS_EVIDENCE) 2>/dev/null)"
TAGS_PREREQ := $(TAGS_EVIDENCE)
endif
+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
+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)
@@ -333,7 +361,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/public/forgejo/api.v1.yml b/public/forgejo/api.v1.yml
new file mode 100644
index 000000000..903dd659d
--- /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 000000000..2a933450e
--- /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 000000000..54ab19d7b
--- /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 000000000..afec612e8
--- /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 000000000..b976c5129
--- /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", "</assets/forgejo/api.v1.yml>; rel=\"service-desc\"")
+ w.WriteHeader(http.StatusNoContent)
+}
diff --git a/routers/init.go b/routers/init.go
index 8cf53fc10..2044297f7 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 000000000..2f539e955
--- /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 292268dc8..c73b9f470 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 000000000..d0ee88975
--- /dev/null
+++ b/templates/swagger/forgejo-ui.tmpl
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <title>Forgejo API</title>
+ <link href="{{AssetUrlPrefix}}/css/swagger.css?v={{AssetVersion}}" rel="stylesheet">
+ </head>
+ <body>
+ <a class="swagger-back-link" href="{{AppUrl}}">{{svg "octicon-reply"}}{{.locale.Tr "return_to_gitea"}}</a>
+ <div id="swagger-ui" data-source="{{AssetUrlPrefix}}/forgejo/api.{{.APIVersion}}.yml"></div>
+ <script src="{{AssetUrlPrefix}}/js/forgejoswagger.js?v={{AssetVersion}}"></script>
+ </body>
+</html>
diff --git a/tests/integration/api_forgejo_root_test.go b/tests/integration/api_forgejo_root_test.go
new file mode 100644
index 000000000..d21c9449b
--- /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 000000000..cd904338a
--- /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 000000000..b565827b3
--- /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 245791e7e..cef78e5c6 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -60,7 +60,11 @@ export default {
fileURLToPath(new URL('web_src/less/index.less', import.meta.url)),
],
webcomponents: [
- fileURLToPath(new URL('web_src/js/webcomponents/GiteaOriginUrl.js', import.meta.url)),
+ fileURLToPath(new URL('web_src/js/webcomponents/GiteaOriginUrl.js', 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)),