summaryrefslogtreecommitdiffstats
path: root/routers/api/v1/misc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--routers/api/v1/misc/gitignore.go56
-rw-r--r--routers/api/v1/misc/label_templates.go60
-rw-r--r--routers/api/v1/misc/licenses.go76
-rw-r--r--routers/api/v1/misc/markup.go110
-rw-r--r--routers/api/v1/misc/markup_test.go184
-rw-r--r--routers/api/v1/misc/nodeinfo.go80
-rw-r--r--routers/api/v1/misc/signing.go63
-rw-r--r--routers/api/v1/misc/version.go25
8 files changed, 654 insertions, 0 deletions
diff --git a/routers/api/v1/misc/gitignore.go b/routers/api/v1/misc/gitignore.go
new file mode 100644
index 0000000..dffd771
--- /dev/null
+++ b/routers/api/v1/misc/gitignore.go
@@ -0,0 +1,56 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package misc
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/options"
+ repo_module "code.gitea.io/gitea/modules/repository"
+ "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
+)
+
+// Shows a list of all Gitignore templates
+func ListGitignoresTemplates(ctx *context.APIContext) {
+ // swagger:operation GET /gitignore/templates miscellaneous listGitignoresTemplates
+ // ---
+ // summary: Returns a list of all gitignore templates
+ // produces:
+ // - application/json
+ // responses:
+ // "200":
+ // "$ref": "#/responses/GitignoreTemplateList"
+ ctx.JSON(http.StatusOK, repo_module.Gitignores)
+}
+
+// SHows information about a gitignore template
+func GetGitignoreTemplateInfo(ctx *context.APIContext) {
+ // swagger:operation GET /gitignore/templates/{name} miscellaneous getGitignoreTemplateInfo
+ // ---
+ // summary: Returns information about a gitignore template
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: name
+ // in: path
+ // description: name of the template
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/GitignoreTemplateInfo"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ name := util.PathJoinRelX(ctx.Params("name"))
+
+ text, err := options.Gitignore(name)
+ if err != nil {
+ ctx.NotFound()
+ return
+ }
+
+ ctx.JSON(http.StatusOK, &structs.GitignoreTemplateInfo{Name: name, Source: string(text)})
+}
diff --git a/routers/api/v1/misc/label_templates.go b/routers/api/v1/misc/label_templates.go
new file mode 100644
index 0000000..cc11f37
--- /dev/null
+++ b/routers/api/v1/misc/label_templates.go
@@ -0,0 +1,60 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package misc
+
+import (
+ "net/http"
+
+ repo_module "code.gitea.io/gitea/modules/repository"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
+ "code.gitea.io/gitea/services/convert"
+)
+
+// Shows a list of all Label templates
+func ListLabelTemplates(ctx *context.APIContext) {
+ // swagger:operation GET /label/templates miscellaneous listLabelTemplates
+ // ---
+ // summary: Returns a list of all label templates
+ // produces:
+ // - application/json
+ // responses:
+ // "200":
+ // "$ref": "#/responses/LabelTemplateList"
+ result := make([]string, len(repo_module.LabelTemplateFiles))
+ for i := range repo_module.LabelTemplateFiles {
+ result[i] = repo_module.LabelTemplateFiles[i].DisplayName
+ }
+
+ ctx.JSON(http.StatusOK, result)
+}
+
+// Shows all labels in a template
+func GetLabelTemplate(ctx *context.APIContext) {
+ // swagger:operation GET /label/templates/{name} miscellaneous getLabelTemplateInfo
+ // ---
+ // summary: Returns all labels in a template
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: name
+ // in: path
+ // description: name of the template
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/LabelTemplateInfo"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ name := util.PathJoinRelX(ctx.Params("name"))
+
+ labels, err := repo_module.LoadTemplateLabelsByDisplayName(name)
+ if err != nil {
+ ctx.NotFound()
+ return
+ }
+
+ ctx.JSON(http.StatusOK, convert.ToLabelTemplateList(labels))
+}
diff --git a/routers/api/v1/misc/licenses.go b/routers/api/v1/misc/licenses.go
new file mode 100644
index 0000000..2a980f5
--- /dev/null
+++ b/routers/api/v1/misc/licenses.go
@@ -0,0 +1,76 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package misc
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+
+ "code.gitea.io/gitea/modules/options"
+ repo_module "code.gitea.io/gitea/modules/repository"
+ "code.gitea.io/gitea/modules/setting"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/context"
+)
+
+// Returns a list of all License templates
+func ListLicenseTemplates(ctx *context.APIContext) {
+ // swagger:operation GET /licenses miscellaneous listLicenseTemplates
+ // ---
+ // summary: Returns a list of all license templates
+ // produces:
+ // - application/json
+ // responses:
+ // "200":
+ // "$ref": "#/responses/LicenseTemplateList"
+ response := make([]api.LicensesTemplateListEntry, len(repo_module.Licenses))
+ for i, license := range repo_module.Licenses {
+ response[i] = api.LicensesTemplateListEntry{
+ Key: license,
+ Name: license,
+ URL: fmt.Sprintf("%sapi/v1/licenses/%s", setting.AppURL, url.PathEscape(license)),
+ }
+ }
+ ctx.JSON(http.StatusOK, response)
+}
+
+// Returns information about a gitignore template
+func GetLicenseTemplateInfo(ctx *context.APIContext) {
+ // swagger:operation GET /licenses/{name} miscellaneous getLicenseTemplateInfo
+ // ---
+ // summary: Returns information about a license template
+ // produces:
+ // - application/json
+ // parameters:
+ // - name: name
+ // in: path
+ // description: name of the license
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // "$ref": "#/responses/LicenseTemplateInfo"
+ // "404":
+ // "$ref": "#/responses/notFound"
+ name := util.PathJoinRelX(ctx.Params("name"))
+
+ text, err := options.License(name)
+ if err != nil {
+ ctx.NotFound()
+ return
+ }
+
+ response := api.LicenseTemplateInfo{
+ Key: name,
+ Name: name,
+ URL: fmt.Sprintf("%sapi/v1/licenses/%s", setting.AppURL, url.PathEscape(name)),
+ Body: string(text),
+ // This is for combatibilty with the GitHub API. This Text is for some reason added to each License response.
+ Implementation: "Create a text file (typically named LICENSE or LICENSE.txt) in the root of your source code and copy the text of the license into the file",
+ }
+
+ ctx.JSON(http.StatusOK, response)
+}
diff --git a/routers/api/v1/misc/markup.go b/routers/api/v1/misc/markup.go
new file mode 100644
index 0000000..9699c79
--- /dev/null
+++ b/routers/api/v1/misc/markup.go
@@ -0,0 +1,110 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package misc
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/markup/markdown"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/routers/common"
+ "code.gitea.io/gitea/services/context"
+)
+
+// Markup render markup document to HTML
+func Markup(ctx *context.APIContext) {
+ // swagger:operation POST /markup miscellaneous renderMarkup
+ // ---
+ // summary: Render a markup document as HTML
+ // parameters:
+ // - name: body
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/MarkupOption"
+ // consumes:
+ // - application/json
+ // produces:
+ // - text/html
+ // responses:
+ // "200":
+ // "$ref": "#/responses/MarkupRender"
+ // "422":
+ // "$ref": "#/responses/validationError"
+
+ form := web.GetForm(ctx).(*api.MarkupOption)
+
+ if ctx.HasAPIError() {
+ ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg())
+ return
+ }
+
+ common.RenderMarkup(ctx.Base, ctx.Repo, form.Mode, form.Text, form.Context, form.FilePath, form.Wiki)
+}
+
+// Markdown render markdown document to HTML
+func Markdown(ctx *context.APIContext) {
+ // swagger:operation POST /markdown miscellaneous renderMarkdown
+ // ---
+ // summary: Render a markdown document as HTML
+ // parameters:
+ // - name: body
+ // in: body
+ // schema:
+ // "$ref": "#/definitions/MarkdownOption"
+ // consumes:
+ // - application/json
+ // produces:
+ // - text/html
+ // responses:
+ // "200":
+ // "$ref": "#/responses/MarkdownRender"
+ // "422":
+ // "$ref": "#/responses/validationError"
+
+ form := web.GetForm(ctx).(*api.MarkdownOption)
+
+ if ctx.HasAPIError() {
+ ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg())
+ return
+ }
+
+ mode := "markdown"
+ if form.Mode == "comment" || form.Mode == "gfm" {
+ mode = form.Mode
+ }
+
+ common.RenderMarkup(ctx.Base, ctx.Repo, mode, form.Text, form.Context, "", form.Wiki)
+}
+
+// MarkdownRaw render raw markdown HTML
+func MarkdownRaw(ctx *context.APIContext) {
+ // swagger:operation POST /markdown/raw miscellaneous renderMarkdownRaw
+ // ---
+ // summary: Render raw markdown as HTML
+ // parameters:
+ // - name: body
+ // in: body
+ // description: Request body to render
+ // required: true
+ // schema:
+ // type: string
+ // consumes:
+ // - text/plain
+ // produces:
+ // - text/html
+ // responses:
+ // "200":
+ // "$ref": "#/responses/MarkdownRender"
+ // "422":
+ // "$ref": "#/responses/validationError"
+ defer ctx.Req.Body.Close()
+ if err := markdown.RenderRaw(&markup.RenderContext{
+ Ctx: ctx,
+ }, ctx.Req.Body, ctx.Resp); err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+}
diff --git a/routers/api/v1/misc/markup_test.go b/routers/api/v1/misc/markup_test.go
new file mode 100644
index 0000000..5236fd0
--- /dev/null
+++ b/routers/api/v1/misc/markup_test.go
@@ -0,0 +1,184 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package misc
+
+import (
+ go_context "context"
+ "io"
+ "net/http"
+ "strings"
+ "testing"
+
+ "code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/setting"
+ api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web"
+ "code.gitea.io/gitea/services/contexttest"
+
+ "github.com/stretchr/testify/assert"
+)
+
+const (
+ AppURL = "http://localhost:3000/"
+ Repo = "gogits/gogs"
+ FullURL = AppURL + Repo + "/"
+)
+
+func testRenderMarkup(t *testing.T, mode, filePath, text, responseBody string, responseCode int) {
+ setting.AppURL = AppURL
+ options := api.MarkupOption{
+ Mode: mode,
+ Text: text,
+ Context: Repo,
+ Wiki: true,
+ FilePath: filePath,
+ }
+ ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markup")
+ web.SetForm(ctx, &options)
+ Markup(ctx)
+ assert.Equal(t, responseBody, resp.Body.String())
+ assert.Equal(t, responseCode, resp.Code)
+ resp.Body.Reset()
+}
+
+func testRenderMarkdown(t *testing.T, mode, text, responseBody string, responseCode int) {
+ setting.AppURL = AppURL
+ options := api.MarkdownOption{
+ Mode: mode,
+ Text: text,
+ Context: Repo,
+ Wiki: true,
+ }
+ ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
+ web.SetForm(ctx, &options)
+ Markdown(ctx)
+ assert.Equal(t, responseBody, resp.Body.String())
+ assert.Equal(t, responseCode, resp.Code)
+ resp.Body.Reset()
+}
+
+func TestAPI_RenderGFM(t *testing.T) {
+ markup.Init(&markup.ProcessorHelper{
+ IsUsernameMentionable: func(ctx go_context.Context, username string) bool {
+ return username == "r-lyeh"
+ },
+ })
+
+ testCasesCommon := []string{
+ // dear imgui wiki markdown extract: special wiki syntax
+ `Wiki! Enjoy :)
+- [[Links, Language bindings, Engine bindings|Links]]
+- [[Tips]]
+- Bezier widget (by @r-lyeh) https://github.com/ocornut/imgui/issues/786`,
+ // rendered
+ `<p>Wiki! Enjoy :)</p>
+<ul>
+<li><a href="` + FullURL + `wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
+<li><a href="` + FullURL + `wiki/Tips" rel="nofollow">Tips</a></li>
+<li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="https://github.com/ocornut/imgui/issues/786" rel="nofollow">https://github.com/ocornut/imgui/issues/786</a></li>
+</ul>
+`,
+ // Guard wiki sidebar: special syntax
+ `[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
+ // rendered
+ `<p><a href="` + FullURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
+`,
+ // special syntax
+ `[[Name|Link]]`,
+ // rendered
+ `<p><a href="` + FullURL + `wiki/Link" rel="nofollow">Name</a></p>
+`,
+ // empty
+ ``,
+ // rendered
+ ``,
+ }
+
+ testCasesDocument := []string{
+ // wine-staging wiki home extract: special wiki syntax, images
+ `## What is Wine Staging?
+**Wine Staging** on website [wine-staging.com](http://wine-staging.com).
+
+## Quick Links
+Here are some links to the most important topics. You can find the full list of pages at the sidebar.
+
+[[Configuration]]
+[[images/icon-bug.png]]
+`,
+ // rendered
+ `<h2 id="user-content-what-is-wine-staging">What is Wine Staging?</h2>
+<p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p>
+<h2 id="user-content-quick-links">Quick Links</h2>
+<p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p>
+<p><a href="` + FullURL + `wiki/Configuration" rel="nofollow">Configuration</a>
+<a href="` + FullURL + `wiki/raw/images/icon-bug.png" rel="nofollow"><img src="` + FullURL + `wiki/raw/images/icon-bug.png" title="icon-bug.png" alt="images/icon-bug.png"/></a></p>
+`,
+ }
+
+ for i := 0; i < len(testCasesCommon); i += 2 {
+ text := testCasesCommon[i]
+ response := testCasesCommon[i+1]
+ testRenderMarkdown(t, "gfm", text, response, http.StatusOK)
+ testRenderMarkup(t, "gfm", "", text, response, http.StatusOK)
+ testRenderMarkdown(t, "comment", text, response, http.StatusOK)
+ testRenderMarkup(t, "comment", "", text, response, http.StatusOK)
+ testRenderMarkup(t, "file", "path/test.md", text, response, http.StatusOK)
+ }
+
+ for i := 0; i < len(testCasesDocument); i += 2 {
+ text := testCasesDocument[i]
+ response := testCasesDocument[i+1]
+ testRenderMarkdown(t, "gfm", text, response, http.StatusOK)
+ testRenderMarkup(t, "gfm", "", text, response, http.StatusOK)
+ testRenderMarkup(t, "file", "path/test.md", text, response, http.StatusOK)
+ }
+
+ testRenderMarkup(t, "file", "path/test.unknown", "## Test", "Unsupported render extension: .unknown\n", http.StatusUnprocessableEntity)
+ testRenderMarkup(t, "unknown", "", "## Test", "Unknown mode: unknown\n", http.StatusUnprocessableEntity)
+}
+
+var simpleCases = []string{
+ // Guard wiki sidebar: special syntax
+ `[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
+ // rendered
+ `<p>[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]</p>
+`,
+ // special syntax
+ `[[Name|Link]]`,
+ // rendered
+ `<p>[[Name|Link]]</p>
+`,
+ // empty
+ ``,
+ // rendered
+ ``,
+}
+
+func TestAPI_RenderSimple(t *testing.T) {
+ setting.AppURL = AppURL
+ options := api.MarkdownOption{
+ Mode: "markdown",
+ Text: "",
+ Context: Repo,
+ }
+ ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
+ for i := 0; i < len(simpleCases); i += 2 {
+ options.Text = simpleCases[i]
+ web.SetForm(ctx, &options)
+ Markdown(ctx)
+ assert.Equal(t, simpleCases[i+1], resp.Body.String())
+ resp.Body.Reset()
+ }
+}
+
+func TestAPI_RenderRaw(t *testing.T) {
+ setting.AppURL = AppURL
+ ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
+ for i := 0; i < len(simpleCases); i += 2 {
+ ctx.Req.Body = io.NopCloser(strings.NewReader(simpleCases[i]))
+ MarkdownRaw(ctx)
+ assert.Equal(t, simpleCases[i+1], resp.Body.String())
+ resp.Body.Reset()
+ }
+}
diff --git a/routers/api/v1/misc/nodeinfo.go b/routers/api/v1/misc/nodeinfo.go
new file mode 100644
index 0000000..9c2a0db
--- /dev/null
+++ b/routers/api/v1/misc/nodeinfo.go
@@ -0,0 +1,80 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package misc
+
+import (
+ "net/http"
+ "time"
+
+ issues_model "code.gitea.io/gitea/models/issues"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
+)
+
+const cacheKeyNodeInfoUsage = "API_NodeInfoUsage"
+
+// NodeInfo returns the NodeInfo for the Gitea instance to allow for federation
+func NodeInfo(ctx *context.APIContext) {
+ // swagger:operation GET /nodeinfo miscellaneous getNodeInfo
+ // ---
+ // summary: Returns the nodeinfo of the Gitea application
+ // produces:
+ // - application/json
+ // responses:
+ // "200":
+ // "$ref": "#/responses/NodeInfo"
+
+ nodeInfoUsage := structs.NodeInfoUsage{}
+ if setting.Federation.ShareUserStatistics {
+ var cached bool
+ nodeInfoUsage, cached = ctx.Cache.Get(cacheKeyNodeInfoUsage).(structs.NodeInfoUsage)
+
+ if !cached {
+ usersTotal := int(user_model.CountUsers(ctx, nil))
+ now := time.Now()
+ timeOneMonthAgo := now.AddDate(0, -1, 0).Unix()
+ timeHaveYearAgo := now.AddDate(0, -6, 0).Unix()
+ usersActiveMonth := int(user_model.CountUsers(ctx, &user_model.CountUserFilter{LastLoginSince: &timeOneMonthAgo}))
+ usersActiveHalfyear := int(user_model.CountUsers(ctx, &user_model.CountUserFilter{LastLoginSince: &timeHaveYearAgo}))
+
+ allIssues, _ := issues_model.CountIssues(ctx, &issues_model.IssuesOptions{})
+ allComments, _ := issues_model.CountComments(ctx, &issues_model.FindCommentsOptions{})
+
+ nodeInfoUsage = structs.NodeInfoUsage{
+ Users: structs.NodeInfoUsageUsers{
+ Total: usersTotal,
+ ActiveMonth: usersActiveMonth,
+ ActiveHalfyear: usersActiveHalfyear,
+ },
+ LocalPosts: int(allIssues),
+ LocalComments: int(allComments),
+ }
+
+ if err := ctx.Cache.Put(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil {
+ ctx.InternalServerError(err)
+ return
+ }
+ }
+ }
+
+ nodeInfo := &structs.NodeInfo{
+ Version: "2.1",
+ Software: structs.NodeInfoSoftware{
+ Name: "forgejo",
+ Version: setting.AppVer,
+ Repository: "https://codeberg.org/forgejo/forgejo.git",
+ Homepage: "https://forgejo.org/",
+ },
+ Protocols: []string{"activitypub"},
+ Services: structs.NodeInfoServices{
+ Inbound: []string{},
+ Outbound: []string{"rss2.0"},
+ },
+ OpenRegistrations: setting.Service.ShowRegistrationButton,
+ Usage: nodeInfoUsage,
+ }
+ ctx.JSON(http.StatusOK, nodeInfo)
+}
diff --git a/routers/api/v1/misc/signing.go b/routers/api/v1/misc/signing.go
new file mode 100644
index 0000000..24a46c1
--- /dev/null
+++ b/routers/api/v1/misc/signing.go
@@ -0,0 +1,63 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package misc
+
+import (
+ "fmt"
+ "net/http"
+
+ asymkey_service "code.gitea.io/gitea/services/asymkey"
+ "code.gitea.io/gitea/services/context"
+)
+
+// SigningKey returns the public key of the default signing key if it exists
+func SigningKey(ctx *context.APIContext) {
+ // swagger:operation GET /signing-key.gpg miscellaneous getSigningKey
+ // ---
+ // summary: Get default signing-key.gpg
+ // produces:
+ // - text/plain
+ // responses:
+ // "200":
+ // description: "GPG armored public key"
+ // schema:
+ // type: string
+
+ // swagger:operation GET /repos/{owner}/{repo}/signing-key.gpg repository repoSigningKey
+ // ---
+ // summary: Get signing-key.gpg for given repository
+ // produces:
+ // - text/plain
+ // parameters:
+ // - name: owner
+ // in: path
+ // description: owner of the repo
+ // type: string
+ // required: true
+ // - name: repo
+ // in: path
+ // description: name of the repo
+ // type: string
+ // required: true
+ // responses:
+ // "200":
+ // description: "GPG armored public key"
+ // schema:
+ // type: string
+
+ path := ""
+ if ctx.Repo != nil && ctx.Repo.Repository != nil {
+ path = ctx.Repo.Repository.RepoPath()
+ }
+
+ content, err := asymkey_service.PublicSigningKey(ctx, path)
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "gpg export", err)
+ return
+ }
+ _, err = ctx.Write([]byte(content))
+ if err != nil {
+ ctx.Error(http.StatusInternalServerError, "gpg export", fmt.Errorf("Error writing key content %w", err))
+ }
+}
diff --git a/routers/api/v1/misc/version.go b/routers/api/v1/misc/version.go
new file mode 100644
index 0000000..e3b43a0
--- /dev/null
+++ b/routers/api/v1/misc/version.go
@@ -0,0 +1,25 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package misc
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/services/context"
+)
+
+// Version shows the version of the Gitea server
+func Version(ctx *context.APIContext) {
+ // swagger:operation GET /version miscellaneous getVersion
+ // ---
+ // summary: Returns the version of the Gitea application
+ // produces:
+ // - application/json
+ // responses:
+ // "200":
+ // "$ref": "#/responses/ServerVersion"
+ ctx.JSON(http.StatusOK, &structs.ServerVersion{Version: setting.AppVer})
+}