diff options
author | David Svantesson <davidsvantesson@gmail.com> | 2019-09-03 17:46:24 +0200 |
---|---|---|
committer | Lunny Xiao <xiaolunwen@gmail.com> | 2019-09-03 23:46:24 +0800 |
commit | 1f951cdebae314a45b5e4ec1d464f1b4a53c8002 (patch) | |
tree | 5c22a1872beaa218599374678491a2fd66323075 /routers/api/v1/repo/topic.go | |
parent | 99d6863238d8717c2f14bc72ab023a0f683c50cc (diff) | |
download | gitea-1f951cdebae314a45b5e4ec1d464f1b4a53c8002.tar.gz gitea-1f951cdebae314a45b5e4ec1d464f1b4a53c8002.zip |
Add API endpoint for accessing repo topics (#7963)
* Create API endpoints for repo topics.
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Generate swagger
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Add documentation to functions
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Grammar fix
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Fix function comment
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Can't use FindTopics when looking for a single repo topic, as it doesnt use exact match
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Add PUT /repos/{owner}/{repo}/topics and remove GET /repos/{owner}/{repo}/topics
* Ignore if topic is sent twice in same request, refactoring.
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Fix topic dropdown with api changes.
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Style fix
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Update API documentation
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Better way to handle duplicate topics in slice
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Make response element TopicName an array of strings, instead of using an array of TopicName
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Add test cases for API Repo Topics.
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Fix format of tests
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Fix comments
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Fix unit tests after adding some more topics to the test fixture.
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Update models/topic.go
Limit multiple if else if ...
Co-Authored-By: Antoine GIRARD <sapk@users.noreply.github.com>
* Engine as first parameter in function
Co-Authored-By: Antoine GIRARD <sapk@users.noreply.github.com>
* Replace magic numbers with http status code constants.
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Fix variable scope
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Test one read with login and one with token
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Add some more tests
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Apply suggestions from code review
Use empty struct for efficiency
Co-Authored-By: Lauris BH <lauris@nix.lv>
* Add test case to check access for user with write access
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Fix access, repo admin required to change topics
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Correct first test to be without token
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
* Any repo reader should be able to access topics.
* No need for string pointer
Signed-off-by: David Svantesson <davidsvantesson@gmail.com>
Diffstat (limited to 'routers/api/v1/repo/topic.go')
-rw-r--r-- | routers/api/v1/repo/topic.go | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/routers/api/v1/repo/topic.go b/routers/api/v1/repo/topic.go new file mode 100644 index 0000000000..6c3ac0020a --- /dev/null +++ b/routers/api/v1/repo/topic.go @@ -0,0 +1,274 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package repo + +import ( + "net/http" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/routers/api/v1/convert" +) + +// ListTopics returns list of current topics for repo +func ListTopics(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/topics repository repoListTopics + // --- + // summary: Get list of topics that a repository has + // produces: + // - application/json + // 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": + // "$ref": "#/responses/TopicNames" + + topics, err := models.FindTopics(&models.FindTopicOptions{ + RepoID: ctx.Repo.Repository.ID, + }) + if err != nil { + log.Error("ListTopics failed: %v", err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "message": "ListTopics failed.", + }) + return + } + + topicNames := make([]string, len(topics)) + for i, topic := range topics { + topicNames[i] = topic.Name + } + ctx.JSON(http.StatusOK, map[string]interface{}{ + "topics": topicNames, + }) +} + +// UpdateTopics updates repo with a new set of topics +func UpdateTopics(ctx *context.APIContext, form api.RepoTopicOptions) { + // swagger:operation PUT /repos/{owner}/{repo}/topics repository repoUpdateTopics + // --- + // summary: Replace list of topics for a repository + // produces: + // - application/json + // 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 + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/RepoTopicOptions" + // responses: + // "204": + // "$ref": "#/responses/empty" + + topicNames := form.Topics + validTopics, invalidTopics := models.SanitizeAndValidateTopics(topicNames) + + if len(validTopics) > 25 { + ctx.JSON(http.StatusUnprocessableEntity, map[string]interface{}{ + "invalidTopics": nil, + "message": "Exceeding maximum number of topics per repo", + }) + return + } + + if len(invalidTopics) > 0 { + ctx.JSON(http.StatusUnprocessableEntity, map[string]interface{}{ + "invalidTopics": invalidTopics, + "message": "Topic names are invalid", + }) + return + } + + err := models.SaveTopics(ctx.Repo.Repository.ID, validTopics...) + if err != nil { + log.Error("SaveTopics failed: %v", err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "message": "Save topics failed.", + }) + return + } + + ctx.Status(http.StatusNoContent) +} + +// AddTopic adds a topic name to a repo +func AddTopic(ctx *context.APIContext) { + // swagger:operation PUT /repos/{owner}/{repo}/topics/{topic} repository repoAddTopíc + // --- + // summary: Add a topic to a repository + // produces: + // - application/json + // 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 + // - name: topic + // in: path + // description: name of the topic to add + // type: string + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + + topicName := strings.TrimSpace(strings.ToLower(ctx.Params(":topic"))) + + if !models.ValidateTopic(topicName) { + ctx.Error(http.StatusUnprocessableEntity, "", "Topic name is invalid") + return + } + + // Prevent adding more topics than allowed to repo + topics, err := models.FindTopics(&models.FindTopicOptions{ + RepoID: ctx.Repo.Repository.ID, + }) + if err != nil { + log.Error("AddTopic failed: %v", err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "message": "ListTopics failed.", + }) + return + } + if len(topics) >= 25 { + ctx.JSON(http.StatusUnprocessableEntity, map[string]interface{}{ + "message": "Exceeding maximum allowed topics per repo.", + }) + return + } + + _, err = models.AddTopic(ctx.Repo.Repository.ID, topicName) + if err != nil { + log.Error("AddTopic failed: %v", err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "message": "AddTopic failed.", + }) + return + } + + ctx.Status(http.StatusNoContent) +} + +// DeleteTopic removes topic name from repo +func DeleteTopic(ctx *context.APIContext) { + // swagger:operation DELETE /repos/{owner}/{repo}/topics/{topic} repository repoDeleteTopic + // --- + // summary: Delete a topic from a repository + // produces: + // - application/json + // 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 + // - name: topic + // in: path + // description: name of the topic to delete + // type: string + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + topicName := strings.TrimSpace(strings.ToLower(ctx.Params(":topic"))) + + if !models.ValidateTopic(topicName) { + ctx.Error(http.StatusUnprocessableEntity, "", "Topic name is invalid") + return + } + + topic, err := models.DeleteTopic(ctx.Repo.Repository.ID, topicName) + if err != nil { + log.Error("DeleteTopic failed: %v", err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "message": "DeleteTopic failed.", + }) + return + } + + if topic == nil { + ctx.NotFound() + } + + ctx.Status(http.StatusNoContent) +} + +// TopicSearch search for creating topic +func TopicSearch(ctx *context.Context) { + // swagger:operation GET /topics/search repository topicSearch + // --- + // summary: search topics via keyword + // produces: + // - application/json + // parameters: + // - name: q + // in: query + // description: keywords to search + // required: true + // type: string + // responses: + // "200": + // "$ref": "#/responses/TopicListResponse" + if ctx.User == nil { + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "message": "Only owners could change the topics.", + }) + return + } + + kw := ctx.Query("q") + + topics, err := models.FindTopics(&models.FindTopicOptions{ + Keyword: kw, + Limit: 10, + }) + if err != nil { + log.Error("SearchTopics failed: %v", err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "message": "Search topics failed.", + }) + return + } + + topicResponses := make([]*api.TopicResponse, len(topics)) + for i, topic := range topics { + topicResponses[i] = convert.ToTopicResponse(topic) + } + ctx.JSON(http.StatusOK, map[string]interface{}{ + "topics": topicResponses, + }) +} |