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 /models | |
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 'models')
-rw-r--r-- | models/fixtures/repo_topic.yml | 8 | ||||
-rw-r--r-- | models/fixtures/topic.yml | 8 | ||||
-rw-r--r-- | models/topic.go | 154 | ||||
-rw-r--r-- | models/topic_test.go | 19 |
4 files changed, 153 insertions, 36 deletions
diff --git a/models/fixtures/repo_topic.yml b/models/fixtures/repo_topic.yml index 7041ccfd09..f166faccc1 100644 --- a/models/fixtures/repo_topic.yml +++ b/models/fixtures/repo_topic.yml @@ -17,3 +17,11 @@ - repo_id: 33 topic_id: 4 + +- + repo_id: 2 + topic_id: 5 + +- + repo_id: 2 + topic_id: 6 diff --git a/models/fixtures/topic.yml b/models/fixtures/topic.yml index c868b207cb..6cd0b37fa1 100644 --- a/models/fixtures/topic.yml +++ b/models/fixtures/topic.yml @@ -15,3 +15,11 @@ - id: 4 name: graphql repo_count: 1 + +- id: 5 + name: topicname1 + repo_count: 1 + +- id: 6 + name: topicname2 + repo_count: 2 diff --git a/models/topic.go b/models/topic.go index 8a587acc3a..e4fda03fc4 100644 --- a/models/topic.go +++ b/models/topic.go @@ -54,11 +54,38 @@ func (err ErrTopicNotExist) Error() string { return fmt.Sprintf("topic is not exist [name: %s]", err.Name) } -// ValidateTopic checks topics by length and match pattern rules +// ValidateTopic checks a topic by length and match pattern rules func ValidateTopic(topic string) bool { return len(topic) <= 35 && topicPattern.MatchString(topic) } +// SanitizeAndValidateTopics sanitizes and checks an array or topics +func SanitizeAndValidateTopics(topics []string) (validTopics []string, invalidTopics []string) { + validTopics = make([]string, 0) + mValidTopics := make(map[string]struct{}) + invalidTopics = make([]string, 0) + + for _, topic := range topics { + topic = strings.TrimSpace(strings.ToLower(topic)) + // ignore empty string + if len(topic) == 0 { + continue + } + // ignore same topic twice + if _, ok := mValidTopics[topic]; ok { + continue + } + if ValidateTopic(topic) { + validTopics = append(validTopics, topic) + mValidTopics[topic] = struct{}{} + } else { + invalidTopics = append(invalidTopics, topic) + } + } + + return validTopics, invalidTopics +} + // GetTopicByName retrieves topic by name func GetTopicByName(name string) (*Topic, error) { var topic Topic @@ -70,6 +97,54 @@ func GetTopicByName(name string) (*Topic, error) { return &topic, nil } +// addTopicByNameToRepo adds a topic name to a repo and increments the topic count. +// Returns topic after the addition +func addTopicByNameToRepo(e Engine, repoID int64, topicName string) (*Topic, error) { + var topic Topic + has, err := e.Where("name = ?", topicName).Get(&topic) + if err != nil { + return nil, err + } + if !has { + topic.Name = topicName + topic.RepoCount = 1 + if _, err := e.Insert(&topic); err != nil { + return nil, err + } + } else { + topic.RepoCount++ + if _, err := e.ID(topic.ID).Cols("repo_count").Update(&topic); err != nil { + return nil, err + } + } + + if _, err := e.Insert(&RepoTopic{ + RepoID: repoID, + TopicID: topic.ID, + }); err != nil { + return nil, err + } + + return &topic, nil +} + +// removeTopicFromRepo remove a topic from a repo and decrements the topic repo count +func removeTopicFromRepo(repoID int64, topic *Topic, e Engine) error { + topic.RepoCount-- + if _, err := e.ID(topic.ID).Cols("repo_count").Update(topic); err != nil { + return err + } + + if _, err := e.Delete(&RepoTopic{ + RepoID: repoID, + TopicID: topic.ID, + }); err != nil { + return err + } + + return nil +} + // FindTopicOptions represents the options when fdin topics type FindTopicOptions struct { RepoID int64 @@ -103,6 +178,50 @@ func FindTopics(opts *FindTopicOptions) (topics []*Topic, err error) { return topics, sess.Desc("topic.repo_count").Find(&topics) } +// GetRepoTopicByName retrives topic from name for a repo if it exist +func GetRepoTopicByName(repoID int64, topicName string) (*Topic, error) { + var cond = builder.NewCond() + var topic Topic + cond = cond.And(builder.Eq{"repo_topic.repo_id": repoID}).And(builder.Eq{"topic.name": topicName}) + sess := x.Table("topic").Where(cond) + sess.Join("INNER", "repo_topic", "repo_topic.topic_id = topic.id") + has, err := sess.Get(&topic) + if has { + return &topic, err + } + return nil, err +} + +// AddTopic adds a topic name to a repository (if it does not already have it) +func AddTopic(repoID int64, topicName string) (*Topic, error) { + topic, err := GetRepoTopicByName(repoID, topicName) + if err != nil { + return nil, err + } + if topic != nil { + // Repo already have topic + return topic, nil + } + + return addTopicByNameToRepo(x, repoID, topicName) +} + +// DeleteTopic removes a topic name from a repository (if it has it) +func DeleteTopic(repoID int64, topicName string) (*Topic, error) { + topic, err := GetRepoTopicByName(repoID, topicName) + if err != nil { + return nil, err + } + if topic == nil { + // Repo doesn't have topic, can't be removed + return nil, nil + } + + err = removeTopicFromRepo(repoID, topic, x) + + return topic, err +} + // SaveTopics save topics to a repository func SaveTopics(repoID int64, topicNames ...string) error { topics, err := FindTopics(&FindTopicOptions{ @@ -152,40 +271,15 @@ func SaveTopics(repoID int64, topicNames ...string) error { } for _, topicName := range addedTopicNames { - var topic Topic - if has, err := sess.Where("name = ?", topicName).Get(&topic); err != nil { - return err - } else if !has { - topic.Name = topicName - topic.RepoCount = 1 - if _, err := sess.Insert(&topic); err != nil { - return err - } - } else { - topic.RepoCount++ - if _, err := sess.ID(topic.ID).Cols("repo_count").Update(&topic); err != nil { - return err - } - } - - if _, err := sess.Insert(&RepoTopic{ - RepoID: repoID, - TopicID: topic.ID, - }); err != nil { + _, err := addTopicByNameToRepo(sess, repoID, topicName) + if err != nil { return err } } for _, topic := range removeTopics { - topic.RepoCount-- - if _, err := sess.ID(topic.ID).Cols("repo_count").Update(topic); err != nil { - return err - } - - if _, err := sess.Delete(&RepoTopic{ - RepoID: repoID, - TopicID: topic.ID, - }); err != nil { + err := removeTopicFromRepo(repoID, topic, sess) + if err != nil { return err } } diff --git a/models/topic_test.go b/models/topic_test.go index 65e52afb12..c173c7bf2a 100644 --- a/models/topic_test.go +++ b/models/topic_test.go @@ -11,11 +11,15 @@ import ( ) func TestAddTopic(t *testing.T) { + totalNrOfTopics := 6 + repo1NrOfTopics := 3 + repo2NrOfTopics := 2 + assert.NoError(t, PrepareTestDatabase()) topics, err := FindTopics(&FindTopicOptions{}) assert.NoError(t, err) - assert.EqualValues(t, 4, len(topics)) + assert.EqualValues(t, totalNrOfTopics, len(topics)) topics, err = FindTopics(&FindTopicOptions{ Limit: 2, @@ -27,33 +31,36 @@ func TestAddTopic(t *testing.T) { RepoID: 1, }) assert.NoError(t, err) - assert.EqualValues(t, 3, len(topics)) + assert.EqualValues(t, repo1NrOfTopics, len(topics)) assert.NoError(t, SaveTopics(2, "golang")) + repo2NrOfTopics = 1 topics, err = FindTopics(&FindTopicOptions{}) assert.NoError(t, err) - assert.EqualValues(t, 4, len(topics)) + assert.EqualValues(t, totalNrOfTopics, len(topics)) topics, err = FindTopics(&FindTopicOptions{ RepoID: 2, }) assert.NoError(t, err) - assert.EqualValues(t, 1, len(topics)) + assert.EqualValues(t, repo2NrOfTopics, len(topics)) assert.NoError(t, SaveTopics(2, "golang", "gitea")) + repo2NrOfTopics = 2 + totalNrOfTopics++ topic, err := GetTopicByName("gitea") assert.NoError(t, err) assert.EqualValues(t, 1, topic.RepoCount) topics, err = FindTopics(&FindTopicOptions{}) assert.NoError(t, err) - assert.EqualValues(t, 5, len(topics)) + assert.EqualValues(t, totalNrOfTopics, len(topics)) topics, err = FindTopics(&FindTopicOptions{ RepoID: 2, }) assert.NoError(t, err) - assert.EqualValues(t, 2, len(topics)) + assert.EqualValues(t, repo2NrOfTopics, len(topics)) } func TestTopicValidator(t *testing.T) { |