From fb37eefa282543fd8ce63c361cd4cf0dfac9943c Mon Sep 17 00:00:00 2001 From: JakobDev Date: Wed, 26 Apr 2023 08:08:28 +0200 Subject: [PATCH] Add API for License templates (#23009) This adds a API for getting License templates. This tries to be as close to the [GitHub API](https://docs.github.com/en/rest/licenses?apiVersion=2022-11-28) as possible, but Gitea does not support all features that GitHub has. I think they should been added, but this out f the scope of this PR. You should merge #23006 before this PR for security reasons. --- modules/structs/miscellaneous.go | 16 +++ routers/api/v1/api.go | 2 + routers/api/v1/misc/licenses.go | 76 +++++++++++++ routers/api/v1/swagger/misc.go | 14 +++ templates/swagger/v1_json.tmpl | 107 ++++++++++++++++++ .../integration/api_license_templates_test.go | 55 +++++++++ 6 files changed, 270 insertions(+) create mode 100644 routers/api/v1/misc/licenses.go create mode 100644 tests/integration/api_license_templates_test.go diff --git a/modules/structs/miscellaneous.go b/modules/structs/miscellaneous.go index 8acea84d6c..53d10a9907 100644 --- a/modules/structs/miscellaneous.go +++ b/modules/structs/miscellaneous.go @@ -72,6 +72,22 @@ type ServerVersion struct { Version string `json:"version"` } +// LicensesListEntry is used for the API +type LicensesTemplateListEntry struct { + Key string `json:"key"` + Name string `json:"name"` + URL string `json:"url"` +} + +// LicensesInfo contains information about a License +type LicenseTemplateInfo struct { + Key string `json:"key"` + Name string `json:"name"` + URL string `json:"url"` + Implementation string `json:"implementation"` + Body string `json:"body"` +} + // APIError is an api error with a message type APIError struct { Message string `json:"message"` diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index ad68a33d6b..0af095b2bb 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -719,6 +719,8 @@ func Routes(ctx gocontext.Context) *web.Route { m.Post("/markup", bind(api.MarkupOption{}), misc.Markup) m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown) m.Post("/markdown/raw", misc.MarkdownRaw) + m.Get("/licenses", misc.ListLicenseTemplates) + m.Get("/licenses/{name}", misc.GetLicenseTemplateInfo) m.Group("/settings", func() { m.Get("/ui", settings.GetGeneralUISettings) m.Get("/api", settings.GetGeneralAPISettings) diff --git a/routers/api/v1/misc/licenses.go b/routers/api/v1/misc/licenses.go new file mode 100644 index 0000000000..65f63468cf --- /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/context" + "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" +) + +// 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/swagger/misc.go b/routers/api/v1/swagger/misc.go index a4052a6a76..322bad0c1c 100644 --- a/routers/api/v1/swagger/misc.go +++ b/routers/api/v1/swagger/misc.go @@ -14,6 +14,20 @@ type swaggerResponseServerVersion struct { Body api.ServerVersion `json:"body"` } +// LicenseTemplateList +// swagger:response LicenseTemplateList +type swaggerResponseLicensesTemplateList struct { + // in:body + Body []api.LicensesTemplateListEntry `json:"body"` +} + +// LicenseTemplateInfo +// swagger:response LicenseTemplateInfo +type swaggerResponseLicenseTemplateInfo struct { + // in:body + Body api.LicenseTemplateInfo `json:"body"` +} + // StringSlice // swagger:response StringSlice type swaggerResponseStringSlice struct { diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index e3f87d703e..bc403be446 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -883,6 +883,52 @@ } } }, + "/licenses": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "miscellaneous" + ], + "summary": "Returns a list of all license templates", + "operationId": "listLicenseTemplates", + "responses": { + "200": { + "$ref": "#/responses/LicenseTemplateList" + } + } + } + }, + "/licenses/{name}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "miscellaneous" + ], + "summary": "Returns information about a license template", + "operationId": "getLicenseTemplateInfo", + "parameters": [ + { + "type": "string", + "description": "name of the license", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/LicenseTemplateInfo" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/markdown": { "post": { "consumes": [ @@ -18704,6 +18750,52 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, + "LicenseTemplateInfo": { + "description": "LicensesInfo contains information about a License", + "type": "object", + "properties": { + "body": { + "type": "string", + "x-go-name": "Body" + }, + "implementation": { + "type": "string", + "x-go-name": "Implementation" + }, + "key": { + "type": "string", + "x-go-name": "Key" + }, + "name": { + "type": "string", + "x-go-name": "Name" + }, + "url": { + "type": "string", + "x-go-name": "URL" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, + "LicensesTemplateListEntry": { + "description": "LicensesListEntry is used for the API", + "type": "object", + "properties": { + "key": { + "type": "string", + "x-go-name": "Key" + }, + "name": { + "type": "string", + "x-go-name": "Name" + }, + "url": { + "type": "string", + "x-go-name": "URL" + } + }, + "x-go-package": "code.gitea.io/gitea/modules/structs" + }, "MarkdownOption": { "description": "MarkdownOption markdown options", "type": "object", @@ -21587,6 +21679,21 @@ } } }, + "LicenseTemplateInfo": { + "description": "LicenseTemplateInfo", + "schema": { + "$ref": "#/definitions/LicenseTemplateInfo" + } + }, + "LicenseTemplateList": { + "description": "LicenseTemplateList", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/LicensesTemplateListEntry" + } + } + }, "MarkdownRender": { "description": "MarkdownRender is a rendered markdown document", "schema": { diff --git a/tests/integration/api_license_templates_test.go b/tests/integration/api_license_templates_test.go new file mode 100644 index 0000000000..e12aab7c2c --- /dev/null +++ b/tests/integration/api_license_templates_test.go @@ -0,0 +1,55 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "fmt" + "net/http" + "net/url" + "testing" + + "code.gitea.io/gitea/modules/options" + repo_module "code.gitea.io/gitea/modules/repository" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestAPIListLicenseTemplates(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + req := NewRequest(t, "GET", "/api/v1/licenses") + resp := MakeRequest(t, req, http.StatusOK) + + // This tests if the API returns a list of strings + var licenseList []api.LicensesTemplateListEntry + DecodeJSON(t, resp, &licenseList) +} + +func TestAPIGetLicenseTemplateInfo(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // If Gitea has for some reason no License templates, we need to skip this test + if len(repo_module.Licenses) == 0 { + return + } + + // Use the first template for the test + licenseName := repo_module.Licenses[0] + + urlStr := fmt.Sprintf("/api/v1/licenses/%s", url.PathEscape(licenseName)) + req := NewRequest(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + var licenseInfo api.LicenseTemplateInfo + DecodeJSON(t, resp, &licenseInfo) + + // We get the text of the template here + text, _ := options.License(licenseName) + + assert.Equal(t, licenseInfo.Key, licenseName) + assert.Equal(t, licenseInfo.Name, licenseName) + assert.Equal(t, licenseInfo.Body, string(text)) +}