Never use /api/v1 from Gitea UI Pages (#19318)
Reusing `/api/v1` from Gitea UI Pages have pros and cons. Pros: 1) Less code copy Cons: 1) API/v1 have to support shared session with page requests. 2) You need to consider for each other when you want to change something about api/v1 or page. This PR moves all dependencies to API/v1 from UI Pages. Partially replace #16052
This commit is contained in:
parent
bb7e0619c3
commit
783a021889
32 changed files with 1082 additions and 74 deletions
|
@ -7,6 +7,7 @@ package integrations
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -20,6 +21,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/indexer/issues"
|
"code.gitea.io/gitea/modules/indexer/issues"
|
||||||
"code.gitea.io/gitea/modules/references"
|
"code.gitea.io/gitea/modules/references"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
@ -347,3 +349,209 @@ func TestIssueRedirect(t *testing.T) {
|
||||||
resp = session.MakeRequest(t, req, http.StatusSeeOther)
|
resp = session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
assert.Equal(t, "/"+path.Join("org26", "repo_external_tracker_alpha", "pulls", "1"), test.RedirectURL(resp))
|
assert.Equal(t, "/"+path.Join("org26", "repo_external_tracker_alpha", "pulls", "1"), test.RedirectURL(resp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSearchIssues(t *testing.T) {
|
||||||
|
defer prepareTestEnv(t)()
|
||||||
|
|
||||||
|
session := loginUser(t, "user2")
|
||||||
|
|
||||||
|
link, _ := url.Parse("/issues/search")
|
||||||
|
req := NewRequest(t, "GET", link.String())
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
var apiIssues []*api.Issue
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 10)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", link.String())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 10)
|
||||||
|
|
||||||
|
since := "2000-01-01T00%3A50%3A01%2B00%3A00" // 946687801
|
||||||
|
before := time.Unix(999307200, 0).Format(time.RFC3339)
|
||||||
|
query := url.Values{}
|
||||||
|
query.Add("since", since)
|
||||||
|
query.Add("before", before)
|
||||||
|
link.RawQuery = query.Encode()
|
||||||
|
req = NewRequest(t, "GET", link.String())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 8)
|
||||||
|
query.Del("since")
|
||||||
|
query.Del("before")
|
||||||
|
|
||||||
|
query.Add("state", "closed")
|
||||||
|
link.RawQuery = query.Encode()
|
||||||
|
req = NewRequest(t, "GET", link.String())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 2)
|
||||||
|
|
||||||
|
query.Set("state", "all")
|
||||||
|
link.RawQuery = query.Encode()
|
||||||
|
req = NewRequest(t, "GET", link.String())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.EqualValues(t, "15", resp.Header().Get("X-Total-Count"))
|
||||||
|
assert.Len(t, apiIssues, 10) // there are more but 10 is page item limit
|
||||||
|
|
||||||
|
query.Add("limit", "20")
|
||||||
|
link.RawQuery = query.Encode()
|
||||||
|
req = NewRequest(t, "GET", link.String())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 15)
|
||||||
|
|
||||||
|
query = url.Values{"assigned": {"true"}, "state": {"all"}}
|
||||||
|
link.RawQuery = query.Encode()
|
||||||
|
req = NewRequest(t, "GET", link.String())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 1)
|
||||||
|
|
||||||
|
query = url.Values{"milestones": {"milestone1"}, "state": {"all"}}
|
||||||
|
link.RawQuery = query.Encode()
|
||||||
|
req = NewRequest(t, "GET", link.String())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 1)
|
||||||
|
|
||||||
|
query = url.Values{"milestones": {"milestone1,milestone3"}, "state": {"all"}}
|
||||||
|
link.RawQuery = query.Encode()
|
||||||
|
req = NewRequest(t, "GET", link.String())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 2)
|
||||||
|
|
||||||
|
query = url.Values{"owner": {"user2"}} // user
|
||||||
|
link.RawQuery = query.Encode()
|
||||||
|
req = NewRequest(t, "GET", link.String())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 6)
|
||||||
|
|
||||||
|
query = url.Values{"owner": {"user3"}} // organization
|
||||||
|
link.RawQuery = query.Encode()
|
||||||
|
req = NewRequest(t, "GET", link.String())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 3)
|
||||||
|
|
||||||
|
query = url.Values{"owner": {"user3"}, "team": {"team1"}} // organization + team
|
||||||
|
link.RawQuery = query.Encode()
|
||||||
|
req = NewRequest(t, "GET", link.String())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchIssuesWithLabels(t *testing.T) {
|
||||||
|
defer prepareTestEnv(t)()
|
||||||
|
|
||||||
|
session := loginUser(t, "user1")
|
||||||
|
|
||||||
|
link, _ := url.Parse("/api/v1/repos/issues/search")
|
||||||
|
req := NewRequest(t, "GET", link.String())
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
var apiIssues []*api.Issue
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
|
||||||
|
assert.Len(t, apiIssues, 10)
|
||||||
|
|
||||||
|
query := url.Values{}
|
||||||
|
link.RawQuery = query.Encode()
|
||||||
|
req = NewRequest(t, "GET", link.String())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 10)
|
||||||
|
|
||||||
|
query.Add("labels", "label1")
|
||||||
|
link.RawQuery = query.Encode()
|
||||||
|
req = NewRequest(t, "GET", link.String())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 2)
|
||||||
|
|
||||||
|
// multiple labels
|
||||||
|
query.Set("labels", "label1,label2")
|
||||||
|
link.RawQuery = query.Encode()
|
||||||
|
req = NewRequest(t, "GET", link.String())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 2)
|
||||||
|
|
||||||
|
// an org label
|
||||||
|
query.Set("labels", "orglabel4")
|
||||||
|
link.RawQuery = query.Encode()
|
||||||
|
req = NewRequest(t, "GET", link.String())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 1)
|
||||||
|
|
||||||
|
// org and repo label
|
||||||
|
query.Set("labels", "label2,orglabel4")
|
||||||
|
query.Add("state", "all")
|
||||||
|
link.RawQuery = query.Encode()
|
||||||
|
req = NewRequest(t, "GET", link.String())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 2)
|
||||||
|
|
||||||
|
// org and repo label which share the same issue
|
||||||
|
query.Set("labels", "label1,orglabel4")
|
||||||
|
link.RawQuery = query.Encode()
|
||||||
|
req = NewRequest(t, "GET", link.String())
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiIssues)
|
||||||
|
assert.Len(t, apiIssues, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetIssueInfo(t *testing.T) {
|
||||||
|
defer prepareTestEnv(t)()
|
||||||
|
|
||||||
|
issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 10}).(*models.Issue)
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}).(*repo_model.Repository)
|
||||||
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
|
||||||
|
assert.NoError(t, issue.LoadAttributes())
|
||||||
|
assert.Equal(t, int64(1019307200), int64(issue.DeadlineUnix))
|
||||||
|
assert.Equal(t, api.StateOpen, issue.State())
|
||||||
|
|
||||||
|
session := loginUser(t, owner.Name)
|
||||||
|
|
||||||
|
urlStr := fmt.Sprintf("/%s/%s/issues/%d/info", owner.Name, repo.Name, issue.Index)
|
||||||
|
req := NewRequest(t, "GET", urlStr)
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
var apiIssue api.Issue
|
||||||
|
DecodeJSON(t, resp, &apiIssue)
|
||||||
|
|
||||||
|
assert.EqualValues(t, issue.ID, apiIssue.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateIssueDeadline(t *testing.T) {
|
||||||
|
defer prepareTestEnv(t)()
|
||||||
|
|
||||||
|
issueBefore := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 10}).(*models.Issue)
|
||||||
|
repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID}).(*repo_model.Repository)
|
||||||
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID}).(*user_model.User)
|
||||||
|
assert.NoError(t, issueBefore.LoadAttributes())
|
||||||
|
assert.Equal(t, int64(1019307200), int64(issueBefore.DeadlineUnix))
|
||||||
|
assert.Equal(t, api.StateOpen, issueBefore.State())
|
||||||
|
|
||||||
|
session := loginUser(t, owner.Name)
|
||||||
|
|
||||||
|
issueURL := fmt.Sprintf("%s/%s/issues/%d", owner.Name, repoBefore.Name, issueBefore.Index)
|
||||||
|
req := NewRequest(t, "GET", issueURL)
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||||
|
|
||||||
|
urlStr := issueURL + "/deadline?_csrf=" + htmlDoc.GetCSRF()
|
||||||
|
req = NewRequestWithJSON(t, "POST", urlStr, map[string]string{
|
||||||
|
"due_date": "2022-04-06T00:00:00.000Z",
|
||||||
|
})
|
||||||
|
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusCreated)
|
||||||
|
var apiIssue api.IssueDeadline
|
||||||
|
DecodeJSON(t, resp, &apiIssue)
|
||||||
|
|
||||||
|
assert.EqualValues(t, "2022-04-06", apiIssue.Deadline.Format("2006-01-02"))
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -173,3 +175,30 @@ func TestOrgRestrictedUser(t *testing.T) {
|
||||||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s", orgName, repoName))
|
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s", orgName, repoName))
|
||||||
restrictedSession.MakeRequest(t, req, http.StatusOK)
|
restrictedSession.MakeRequest(t, req, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTeamSearch(t *testing.T) {
|
||||||
|
defer prepareTestEnv(t)()
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
|
||||||
|
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User)
|
||||||
|
|
||||||
|
var results TeamSearchResults
|
||||||
|
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
csrf := GetCSRF(t, session, "/"+org.Name)
|
||||||
|
req := NewRequestf(t, "GET", "/org/%s/teams/-/search?q=%s", org.Name, "_team")
|
||||||
|
req.Header.Add("X-Csrf-Token", csrf)
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &results)
|
||||||
|
assert.NotEmpty(t, results.Data)
|
||||||
|
assert.Len(t, results.Data, 1)
|
||||||
|
assert.Equal(t, "test_team", results.Data[0].Name)
|
||||||
|
|
||||||
|
// no access if not organization member
|
||||||
|
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
|
||||||
|
session = loginUser(t, user5.Name)
|
||||||
|
csrf = GetCSRF(t, session, "/"+org.Name)
|
||||||
|
req = NewRequestf(t, "GET", "/org/%s/teams/-/search?q=%s", org.Name, "team")
|
||||||
|
req.Header.Add("X-Csrf-Token", csrf)
|
||||||
|
session.MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
46
integrations/repo_topic_test.go
Normal file
46
integrations/repo_topic_test.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright 2022 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 integrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTopicSearch(t *testing.T) {
|
||||||
|
defer prepareTestEnv(t)()
|
||||||
|
searchURL, _ := url.Parse("/explore/topics/search")
|
||||||
|
var topics struct {
|
||||||
|
TopicNames []*api.TopicResponse `json:"topics"`
|
||||||
|
}
|
||||||
|
|
||||||
|
query := url.Values{"page": []string{"1"}, "limit": []string{"4"}}
|
||||||
|
|
||||||
|
searchURL.RawQuery = query.Encode()
|
||||||
|
res := MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK)
|
||||||
|
DecodeJSON(t, res, &topics)
|
||||||
|
assert.Len(t, topics.TopicNames, 4)
|
||||||
|
assert.EqualValues(t, "6", res.Header().Get("x-total-count"))
|
||||||
|
|
||||||
|
query.Add("q", "topic")
|
||||||
|
searchURL.RawQuery = query.Encode()
|
||||||
|
res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK)
|
||||||
|
DecodeJSON(t, res, &topics)
|
||||||
|
assert.Len(t, topics.TopicNames, 2)
|
||||||
|
|
||||||
|
query.Set("q", "database")
|
||||||
|
searchURL.RawQuery = query.Encode()
|
||||||
|
res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK)
|
||||||
|
DecodeJSON(t, res, &topics)
|
||||||
|
if assert.Len(t, topics.TopicNames, 1) {
|
||||||
|
assert.EqualValues(t, 2, topics.TopicNames[0].ID)
|
||||||
|
assert.EqualValues(t, "database", topics.TopicNames[0].Name)
|
||||||
|
assert.EqualValues(t, 1, topics.TopicNames[0].RepoCount)
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,8 +8,11 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
"code.gitea.io/gitea/modules/translation/i18n"
|
"code.gitea.io/gitea/modules/translation/i18n"
|
||||||
|
|
||||||
|
@ -222,3 +225,26 @@ func testExportUserGPGKeys(t *testing.T, user, expected string) {
|
||||||
// t.Log(resp.Body.String())
|
// t.Log(resp.Body.String())
|
||||||
assert.Equal(t, expected, resp.Body.String())
|
assert.Equal(t, expected, resp.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestListStopWatches(t *testing.T) {
|
||||||
|
defer prepareTestEnv(t)()
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
|
||||||
|
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
|
||||||
|
|
||||||
|
session := loginUser(t, owner.Name)
|
||||||
|
req := NewRequestf(t, "GET", "/user/stopwatches")
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
var apiWatches []*api.StopWatch
|
||||||
|
DecodeJSON(t, resp, &apiWatches)
|
||||||
|
stopwatch := unittest.AssertExistsAndLoadBean(t, &models.Stopwatch{UserID: owner.ID}).(*models.Stopwatch)
|
||||||
|
issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: stopwatch.IssueID}).(*models.Issue)
|
||||||
|
if assert.Len(t, apiWatches, 1) {
|
||||||
|
assert.EqualValues(t, stopwatch.CreatedUnix.AsTime().Unix(), apiWatches[0].Created.Unix())
|
||||||
|
assert.EqualValues(t, issue.Index, apiWatches[0].IssueIndex)
|
||||||
|
assert.EqualValues(t, issue.Title, apiWatches[0].IssueTitle)
|
||||||
|
assert.EqualValues(t, repo.Name, apiWatches[0].RepoName)
|
||||||
|
assert.EqualValues(t, repo.OwnerName, apiWatches[0].RepoOwnerName)
|
||||||
|
assert.Greater(t, int64(apiWatches[0].Seconds), int64(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -191,22 +191,6 @@ func (ctx *APIContext) SetLinkHeader(total, pageSize int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTotalCountHeader set "X-Total-Count" header
|
|
||||||
func (ctx *APIContext) SetTotalCountHeader(total int64) {
|
|
||||||
ctx.RespHeader().Set("X-Total-Count", fmt.Sprint(total))
|
|
||||||
ctx.AppendAccessControlExposeHeaders("X-Total-Count")
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header
|
|
||||||
func (ctx *APIContext) AppendAccessControlExposeHeaders(names ...string) {
|
|
||||||
val := ctx.RespHeader().Get("Access-Control-Expose-Headers")
|
|
||||||
if len(val) != 0 {
|
|
||||||
ctx.RespHeader().Set("Access-Control-Expose-Headers", fmt.Sprintf("%s, %s", val, strings.Join(names, ", ")))
|
|
||||||
} else {
|
|
||||||
ctx.RespHeader().Set("Access-Control-Expose-Headers", strings.Join(names, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequireCSRF requires a validated a CSRF token
|
// RequireCSRF requires a validated a CSRF token
|
||||||
func (ctx *APIContext) RequireCSRF() {
|
func (ctx *APIContext) RequireCSRF() {
|
||||||
headerToken := ctx.Req.Header.Get(ctx.csrf.GetHeaderName())
|
headerToken := ctx.Req.Header.Get(ctx.csrf.GetHeaderName())
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
|
@ -21,6 +22,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
|
@ -577,6 +579,22 @@ func (ctx *Context) Value(key interface{}) interface{} {
|
||||||
return ctx.Req.Context().Value(key)
|
return ctx.Req.Context().Value(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetTotalCountHeader set "X-Total-Count" header
|
||||||
|
func (ctx *Context) SetTotalCountHeader(total int64) {
|
||||||
|
ctx.RespHeader().Set("X-Total-Count", fmt.Sprint(total))
|
||||||
|
ctx.AppendAccessControlExposeHeaders("X-Total-Count")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header
|
||||||
|
func (ctx *Context) AppendAccessControlExposeHeaders(names ...string) {
|
||||||
|
val := ctx.RespHeader().Get("Access-Control-Expose-Headers")
|
||||||
|
if len(val) != 0 {
|
||||||
|
ctx.RespHeader().Set("Access-Control-Expose-Headers", fmt.Sprintf("%s, %s", val, strings.Join(names, ", ")))
|
||||||
|
} else {
|
||||||
|
ctx.RespHeader().Set("Access-Control-Expose-Headers", strings.Join(names, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handler represents a custom handler
|
// Handler represents a custom handler
|
||||||
type Handler func(*Context)
|
type Handler func(*Context)
|
||||||
|
|
||||||
|
@ -780,3 +798,21 @@ func Contexter() func(next http.Handler) http.Handler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SearchOrderByMap represents all possible search order
|
||||||
|
var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{
|
||||||
|
"asc": {
|
||||||
|
"alpha": db.SearchOrderByAlphabetically,
|
||||||
|
"created": db.SearchOrderByOldest,
|
||||||
|
"updated": db.SearchOrderByLeastUpdated,
|
||||||
|
"size": db.SearchOrderBySize,
|
||||||
|
"id": db.SearchOrderByID,
|
||||||
|
},
|
||||||
|
"desc": {
|
||||||
|
"alpha": db.SearchOrderByAlphabeticallyReverse,
|
||||||
|
"created": db.SearchOrderByNewest,
|
||||||
|
"updated": db.SearchOrderByRecentUpdated,
|
||||||
|
"size": db.SearchOrderBySizeReverse,
|
||||||
|
"id": db.SearchOrderByIDReverse,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -2,20 +2,16 @@
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package utils
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
"code.gitea.io/gitea/modules/context"
|
|
||||||
"code.gitea.io/gitea/modules/convert"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetQueryBeforeSince return parsed time (unix format) from URL query's before and since
|
// GetQueryBeforeSince return parsed time (unix format) from URL query's before and since
|
||||||
func GetQueryBeforeSince(ctx *context.APIContext) (before, since int64, err error) {
|
func GetQueryBeforeSince(ctx *Context) (before, since int64, err error) {
|
||||||
qCreatedBefore, err := prepareQueryArg(ctx, "before")
|
qCreatedBefore, err := prepareQueryArg(ctx, "before")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
|
@ -53,16 +49,8 @@ func parseTime(value string) (int64, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepareQueryArg unescape and trim a query arg
|
// prepareQueryArg unescape and trim a query arg
|
||||||
func prepareQueryArg(ctx *context.APIContext, name string) (value string, err error) {
|
func prepareQueryArg(ctx *Context, name string) (value string, err error) {
|
||||||
value, err = url.PathUnescape(ctx.FormString(name))
|
value, err = url.PathUnescape(ctx.FormString(name))
|
||||||
value = strings.TrimSpace(value)
|
value = strings.TrimSpace(value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetListOptions returns list options using the page and limit parameters
|
|
||||||
func GetListOptions(ctx *context.APIContext) db.ListOptions {
|
|
||||||
return db.ListOptions{
|
|
||||||
Page: ctx.FormInt("page"),
|
|
||||||
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -26,7 +26,7 @@ func NewAvailable(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFindNotificationOptions(ctx *context.APIContext) *models.FindNotificationOptions {
|
func getFindNotificationOptions(ctx *context.APIContext) *models.FindNotificationOptions {
|
||||||
before, since, err := utils.GetQueryBeforeSince(ctx)
|
before, since, err := context.GetQueryBeforeSince(ctx.Context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -111,7 +111,7 @@ func SearchIssues(ctx *context.APIContext) {
|
||||||
// "200":
|
// "200":
|
||||||
// "$ref": "#/responses/IssueList"
|
// "$ref": "#/responses/IssueList"
|
||||||
|
|
||||||
before, since, err := utils.GetQueryBeforeSince(ctx)
|
before, since, err := context.GetQueryBeforeSince(ctx.Context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||||
return
|
return
|
||||||
|
@ -359,7 +359,7 @@ func ListIssues(ctx *context.APIContext) {
|
||||||
// responses:
|
// responses:
|
||||||
// "200":
|
// "200":
|
||||||
// "$ref": "#/responses/IssueList"
|
// "$ref": "#/responses/IssueList"
|
||||||
before, since, err := utils.GetQueryBeforeSince(ctx)
|
before, since, err := context.GetQueryBeforeSince(ctx.Context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -58,7 +58,7 @@ func ListIssueComments(ctx *context.APIContext) {
|
||||||
// "200":
|
// "200":
|
||||||
// "$ref": "#/responses/CommentList"
|
// "$ref": "#/responses/CommentList"
|
||||||
|
|
||||||
before, since, err := utils.GetQueryBeforeSince(ctx)
|
before, since, err := context.GetQueryBeforeSince(ctx.Context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||||
return
|
return
|
||||||
|
@ -150,7 +150,7 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) {
|
||||||
// "200":
|
// "200":
|
||||||
// "$ref": "#/responses/TimelineList"
|
// "$ref": "#/responses/TimelineList"
|
||||||
|
|
||||||
before, since, err := utils.GetQueryBeforeSince(ctx)
|
before, since, err := context.GetQueryBeforeSince(ctx.Context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||||
return
|
return
|
||||||
|
@ -253,7 +253,7 @@ func ListRepoIssueComments(ctx *context.APIContext) {
|
||||||
// "200":
|
// "200":
|
||||||
// "$ref": "#/responses/CommentList"
|
// "$ref": "#/responses/CommentList"
|
||||||
|
|
||||||
before, since, err := utils.GetQueryBeforeSince(ctx)
|
before, since, err := context.GetQueryBeforeSince(ctx.Context)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -103,7 +103,7 @@ func ListTrackedTimes(ctx *context.APIContext) {
|
||||||
opts.UserID = user.ID
|
opts.UserID = user.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = utils.GetQueryBeforeSince(ctx); err != nil {
|
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Context); err != nil {
|
||||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -522,7 +522,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = utils.GetQueryBeforeSince(ctx); err != nil {
|
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Context); err != nil {
|
||||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -597,7 +597,7 @@ func ListMyTrackedTimes(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = utils.GetQueryBeforeSince(ctx); err != nil {
|
if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Context); err != nil {
|
||||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,23 +31,6 @@ import (
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
var searchOrderByMap = map[string]map[string]db.SearchOrderBy{
|
|
||||||
"asc": {
|
|
||||||
"alpha": db.SearchOrderByAlphabetically,
|
|
||||||
"created": db.SearchOrderByOldest,
|
|
||||||
"updated": db.SearchOrderByLeastUpdated,
|
|
||||||
"size": db.SearchOrderBySize,
|
|
||||||
"id": db.SearchOrderByID,
|
|
||||||
},
|
|
||||||
"desc": {
|
|
||||||
"alpha": db.SearchOrderByAlphabeticallyReverse,
|
|
||||||
"created": db.SearchOrderByNewest,
|
|
||||||
"updated": db.SearchOrderByRecentUpdated,
|
|
||||||
"size": db.SearchOrderBySizeReverse,
|
|
||||||
"id": db.SearchOrderByIDReverse,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search repositories via options
|
// Search repositories via options
|
||||||
func Search(ctx *context.APIContext) {
|
func Search(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /repos/search repository repoSearch
|
// swagger:operation GET /repos/search repository repoSearch
|
||||||
|
@ -193,7 +176,7 @@ func Search(ctx *context.APIContext) {
|
||||||
if len(sortOrder) == 0 {
|
if len(sortOrder) == 0 {
|
||||||
sortOrder = "asc"
|
sortOrder = "asc"
|
||||||
}
|
}
|
||||||
if searchModeMap, ok := searchOrderByMap[sortOrder]; ok {
|
if searchModeMap, ok := context.SearchOrderByMap[sortOrder]; ok {
|
||||||
if orderBy, ok := searchModeMap[sortMode]; ok {
|
if orderBy, ok := searchModeMap[sortMode]; ok {
|
||||||
opts.OrderBy = orderBy
|
opts.OrderBy = orderBy
|
||||||
} else {
|
} else {
|
||||||
|
|
19
routers/api/v1/utils/page.go
Normal file
19
routers/api/v1/utils/page.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright 2017 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 utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/convert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetListOptions returns list options using the page and limit parameters
|
||||||
|
func GetListOptions(ctx *context.APIContext) db.ListOptions {
|
||||||
|
return db.ListOptions{
|
||||||
|
Page: ctx.FormInt("page"),
|
||||||
|
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||||
|
}
|
||||||
|
}
|
42
routers/web/explore/topic.go
Normal file
42
routers/web/explore/topic.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright 2022 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 explore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/convert"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TopicSearch search for creating topic
|
||||||
|
func TopicSearch(ctx *context.Context) {
|
||||||
|
opts := &repo_model.FindTopicOptions{
|
||||||
|
Keyword: ctx.FormString("q"),
|
||||||
|
ListOptions: db.ListOptions{
|
||||||
|
Page: ctx.FormInt("page"),
|
||||||
|
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
topics, total, err := repo_model.FindTopics(opts)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
topicResponses := make([]*api.TopicResponse, len(topics))
|
||||||
|
for i, topic := range topics {
|
||||||
|
topicResponses[i] = convert.ToTopicResponse(topic)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetTotalCountHeader(total)
|
||||||
|
ctx.JSON(http.StatusOK, map[string]interface{}{
|
||||||
|
"topics": topicResponses,
|
||||||
|
})
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
@ -20,7 +21,9 @@ import (
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/convert"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/utils"
|
"code.gitea.io/gitea/routers/utils"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
|
@ -329,6 +332,51 @@ func TeamRepositories(ctx *context.Context) {
|
||||||
ctx.HTML(http.StatusOK, tplTeamRepositories)
|
ctx.HTML(http.StatusOK, tplTeamRepositories)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SearchTeam api for searching teams
|
||||||
|
func SearchTeam(ctx *context.Context) {
|
||||||
|
listOptions := db.ListOptions{
|
||||||
|
Page: ctx.FormInt("page"),
|
||||||
|
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := &organization.SearchTeamOptions{
|
||||||
|
UserID: ctx.Doer.ID,
|
||||||
|
Keyword: ctx.FormTrim("q"),
|
||||||
|
OrgID: ctx.Org.Organization.ID,
|
||||||
|
IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"),
|
||||||
|
ListOptions: listOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
teams, maxResults, err := organization.SearchTeam(opts)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("SearchTeam failed: %v", err)
|
||||||
|
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||||
|
"ok": false,
|
||||||
|
"error": "SearchTeam internal failure",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiTeams := make([]*api.Team, len(teams))
|
||||||
|
for i := range teams {
|
||||||
|
if err := teams[i].GetUnits(); err != nil {
|
||||||
|
log.Error("Team GetUnits failed: %v", err)
|
||||||
|
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||||
|
"ok": false,
|
||||||
|
"error": "SearchTeam failed to get units",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apiTeams[i] = convert.ToTeam(teams[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetTotalCountHeader(maxResults)
|
||||||
|
ctx.JSON(http.StatusOK, map[string]interface{}{
|
||||||
|
"ok": true,
|
||||||
|
"data": apiTeams,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// EditTeam render team edit page
|
// EditTeam render team edit page
|
||||||
func EditTeam(ctx *context.Context) {
|
func EditTeam(ctx *context.Context) {
|
||||||
ctx.Data["Title"] = ctx.Org.Organization.FullName
|
ctx.Data["Title"] = ctx.Org.Organization.FullName
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
@ -36,6 +37,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/templates/vars"
|
"code.gitea.io/gitea/modules/templates/vars"
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/upload"
|
"code.gitea.io/gitea/modules/upload"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
@ -1762,6 +1764,20 @@ func getActionIssues(ctx *context.Context) []*models.Issue {
|
||||||
return issues
|
return issues
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetIssueInfo get an issue of a repository
|
||||||
|
func GetIssueInfo(ctx *context.Context) {
|
||||||
|
issue, err := models.GetIssueWithAttrsByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
|
||||||
|
if err != nil {
|
||||||
|
if models.IsErrIssueNotExist(err) {
|
||||||
|
ctx.Error(http.StatusNotFound)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.JSON(http.StatusOK, convert.ToAPIIssue(issue))
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateIssueTitle change issue's title
|
// UpdateIssueTitle change issue's title
|
||||||
func UpdateIssueTitle(ctx *context.Context) {
|
func UpdateIssueTitle(ctx *context.Context) {
|
||||||
issue := GetActionIssue(ctx)
|
issue := GetActionIssue(ctx)
|
||||||
|
@ -1856,6 +1872,40 @@ func UpdateIssueContent(ctx *context.Context) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateIssueDeadline updates an issue deadline
|
||||||
|
func UpdateIssueDeadline(ctx *context.Context) {
|
||||||
|
form := web.GetForm(ctx).(*api.EditDeadlineOption)
|
||||||
|
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
|
||||||
|
if err != nil {
|
||||||
|
if models.IsErrIssueNotExist(err) {
|
||||||
|
ctx.NotFound("GetIssueByIndex", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
|
||||||
|
ctx.Error(http.StatusForbidden, "", "Not repo writer")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var deadlineUnix timeutil.TimeStamp
|
||||||
|
var deadline time.Time
|
||||||
|
if form.Deadline != nil && !form.Deadline.IsZero() {
|
||||||
|
deadline = time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(),
|
||||||
|
23, 59, 59, 0, time.Local)
|
||||||
|
deadlineUnix = timeutil.TimeStamp(deadline.Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.Doer); err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusCreated, api.IssueDeadline{Deadline: &deadline})
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateIssueMilestone change issue's milestone
|
// UpdateIssueMilestone change issue's milestone
|
||||||
func UpdateIssueMilestone(ctx *context.Context) {
|
func UpdateIssueMilestone(ctx *context.Context) {
|
||||||
issues := getActionIssues(ctx)
|
issues := getActionIssues(ctx)
|
||||||
|
@ -2052,6 +2102,338 @@ func UpdatePullReviewRequest(ctx *context.Context) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SearchIssues searches for issues across the repositories that the user has access to
|
||||||
|
func SearchIssues(ctx *context.Context) {
|
||||||
|
before, since, err := context.GetQueryBeforeSince(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var isClosed util.OptionalBool
|
||||||
|
switch ctx.FormString("state") {
|
||||||
|
case "closed":
|
||||||
|
isClosed = util.OptionalBoolTrue
|
||||||
|
case "all":
|
||||||
|
isClosed = util.OptionalBoolNone
|
||||||
|
default:
|
||||||
|
isClosed = util.OptionalBoolFalse
|
||||||
|
}
|
||||||
|
|
||||||
|
// find repos user can access (for issue search)
|
||||||
|
opts := &models.SearchRepoOptions{
|
||||||
|
Private: false,
|
||||||
|
AllPublic: true,
|
||||||
|
TopicOnly: false,
|
||||||
|
Collaborate: util.OptionalBoolNone,
|
||||||
|
// This needs to be a column that is not nil in fixtures or
|
||||||
|
// MySQL will return different results when sorting by null in some cases
|
||||||
|
OrderBy: db.SearchOrderByAlphabetically,
|
||||||
|
Actor: ctx.Doer,
|
||||||
|
}
|
||||||
|
if ctx.IsSigned {
|
||||||
|
opts.Private = true
|
||||||
|
opts.AllLimited = true
|
||||||
|
}
|
||||||
|
if ctx.FormString("owner") != "" {
|
||||||
|
owner, err := user_model.GetUserByName(ctx.FormString("owner"))
|
||||||
|
if err != nil {
|
||||||
|
if user_model.IsErrUserNotExist(err) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "Owner not found", err.Error())
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetUserByName", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
opts.OwnerID = owner.ID
|
||||||
|
opts.AllLimited = false
|
||||||
|
opts.AllPublic = false
|
||||||
|
opts.Collaborate = util.OptionalBoolFalse
|
||||||
|
}
|
||||||
|
if ctx.FormString("team") != "" {
|
||||||
|
if ctx.FormString("owner") == "" {
|
||||||
|
ctx.Error(http.StatusBadRequest, "", "Owner organisation is required for filtering on team")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
team, err := organization.GetTeam(opts.OwnerID, ctx.FormString("team"))
|
||||||
|
if err != nil {
|
||||||
|
if organization.IsErrTeamNotExist(err) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "Team not found", err.Error())
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetUserByName", err.Error())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
opts.TeamID = team.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
repoIDs, _, err := models.SearchRepositoryIDs(opts)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "SearchRepositoryByName", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var issues []*models.Issue
|
||||||
|
var filteredCount int64
|
||||||
|
|
||||||
|
keyword := ctx.FormTrim("q")
|
||||||
|
if strings.IndexByte(keyword, 0) >= 0 {
|
||||||
|
keyword = ""
|
||||||
|
}
|
||||||
|
var issueIDs []int64
|
||||||
|
if len(keyword) > 0 && len(repoIDs) > 0 {
|
||||||
|
if issueIDs, err = issue_indexer.SearchIssuesByKeyword(ctx, repoIDs, keyword); err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "SearchIssuesByKeyword", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isPull util.OptionalBool
|
||||||
|
switch ctx.FormString("type") {
|
||||||
|
case "pulls":
|
||||||
|
isPull = util.OptionalBoolTrue
|
||||||
|
case "issues":
|
||||||
|
isPull = util.OptionalBoolFalse
|
||||||
|
default:
|
||||||
|
isPull = util.OptionalBoolNone
|
||||||
|
}
|
||||||
|
|
||||||
|
labels := ctx.FormTrim("labels")
|
||||||
|
var includedLabelNames []string
|
||||||
|
if len(labels) > 0 {
|
||||||
|
includedLabelNames = strings.Split(labels, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
milestones := ctx.FormTrim("milestones")
|
||||||
|
var includedMilestones []string
|
||||||
|
if len(milestones) > 0 {
|
||||||
|
includedMilestones = strings.Split(milestones, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// this api is also used in UI,
|
||||||
|
// so the default limit is set to fit UI needs
|
||||||
|
limit := ctx.FormInt("limit")
|
||||||
|
if limit == 0 {
|
||||||
|
limit = setting.UI.IssuePagingNum
|
||||||
|
} else if limit > setting.API.MaxResponseItems {
|
||||||
|
limit = setting.API.MaxResponseItems
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only fetch the issues if we either don't have a keyword or the search returned issues
|
||||||
|
// This would otherwise return all issues if no issues were found by the search.
|
||||||
|
if len(keyword) == 0 || len(issueIDs) > 0 || len(includedLabelNames) > 0 || len(includedMilestones) > 0 {
|
||||||
|
issuesOpt := &models.IssuesOptions{
|
||||||
|
ListOptions: db.ListOptions{
|
||||||
|
Page: ctx.FormInt("page"),
|
||||||
|
PageSize: limit,
|
||||||
|
},
|
||||||
|
RepoIDs: repoIDs,
|
||||||
|
IsClosed: isClosed,
|
||||||
|
IssueIDs: issueIDs,
|
||||||
|
IncludedLabelNames: includedLabelNames,
|
||||||
|
IncludeMilestones: includedMilestones,
|
||||||
|
SortType: "priorityrepo",
|
||||||
|
PriorityRepoID: ctx.FormInt64("priority_repo_id"),
|
||||||
|
IsPull: isPull,
|
||||||
|
UpdatedBeforeUnix: before,
|
||||||
|
UpdatedAfterUnix: since,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxUserID := int64(0)
|
||||||
|
if ctx.IsSigned {
|
||||||
|
ctxUserID = ctx.Doer.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter for: Created by User, Assigned to User, Mentioning User, Review of User Requested
|
||||||
|
if ctx.FormBool("created") {
|
||||||
|
issuesOpt.PosterID = ctxUserID
|
||||||
|
}
|
||||||
|
if ctx.FormBool("assigned") {
|
||||||
|
issuesOpt.AssigneeID = ctxUserID
|
||||||
|
}
|
||||||
|
if ctx.FormBool("mentioned") {
|
||||||
|
issuesOpt.MentionedID = ctxUserID
|
||||||
|
}
|
||||||
|
if ctx.FormBool("review_requested") {
|
||||||
|
issuesOpt.ReviewRequestedID = ctxUserID
|
||||||
|
}
|
||||||
|
|
||||||
|
if issues, err = models.Issues(issuesOpt); err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "Issues", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
issuesOpt.ListOptions = db.ListOptions{
|
||||||
|
Page: -1,
|
||||||
|
}
|
||||||
|
if filteredCount, err = models.CountIssues(issuesOpt); err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "CountIssues", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetTotalCountHeader(filteredCount)
|
||||||
|
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(issues))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserIDForFilter(ctx *context.Context, queryName string) int64 {
|
||||||
|
userName := ctx.FormString(queryName)
|
||||||
|
if len(userName) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := user_model.GetUserByName(userName)
|
||||||
|
if user_model.IsErrUserNotExist(err) {
|
||||||
|
ctx.NotFound("", err)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListIssues list the issues of a repository
|
||||||
|
func ListIssues(ctx *context.Context) {
|
||||||
|
before, since, err := context.GetQueryBeforeSince(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var isClosed util.OptionalBool
|
||||||
|
switch ctx.FormString("state") {
|
||||||
|
case "closed":
|
||||||
|
isClosed = util.OptionalBoolTrue
|
||||||
|
case "all":
|
||||||
|
isClosed = util.OptionalBoolNone
|
||||||
|
default:
|
||||||
|
isClosed = util.OptionalBoolFalse
|
||||||
|
}
|
||||||
|
|
||||||
|
var issues []*models.Issue
|
||||||
|
var filteredCount int64
|
||||||
|
|
||||||
|
keyword := ctx.FormTrim("q")
|
||||||
|
if strings.IndexByte(keyword, 0) >= 0 {
|
||||||
|
keyword = ""
|
||||||
|
}
|
||||||
|
var issueIDs []int64
|
||||||
|
var labelIDs []int64
|
||||||
|
if len(keyword) > 0 {
|
||||||
|
issueIDs, err = issue_indexer.SearchIssuesByKeyword(ctx, []int64{ctx.Repo.Repository.ID}, keyword)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if splitted := strings.Split(ctx.FormString("labels"), ","); len(splitted) > 0 {
|
||||||
|
labelIDs, err = models.GetLabelIDsInRepoByNames(ctx.Repo.Repository.ID, splitted)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mileIDs []int64
|
||||||
|
if part := strings.Split(ctx.FormString("milestones"), ","); len(part) > 0 {
|
||||||
|
for i := range part {
|
||||||
|
// uses names and fall back to ids
|
||||||
|
// non existent milestones are discarded
|
||||||
|
mile, err := models.GetMilestoneByRepoIDANDName(ctx.Repo.Repository.ID, part[i])
|
||||||
|
if err == nil {
|
||||||
|
mileIDs = append(mileIDs, mile.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !models.IsErrMilestoneNotExist(err) {
|
||||||
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id, err := strconv.ParseInt(part[i], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mile, err = models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, id)
|
||||||
|
if err == nil {
|
||||||
|
mileIDs = append(mileIDs, mile.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if models.IsErrMilestoneNotExist(err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listOptions := db.ListOptions{
|
||||||
|
Page: ctx.FormInt("page"),
|
||||||
|
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||||
|
}
|
||||||
|
|
||||||
|
var isPull util.OptionalBool
|
||||||
|
switch ctx.FormString("type") {
|
||||||
|
case "pulls":
|
||||||
|
isPull = util.OptionalBoolTrue
|
||||||
|
case "issues":
|
||||||
|
isPull = util.OptionalBoolFalse
|
||||||
|
default:
|
||||||
|
isPull = util.OptionalBoolNone
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: we should be more efficient here
|
||||||
|
createdByID := getUserIDForFilter(ctx, "created_by")
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assignedByID := getUserIDForFilter(ctx, "assigned_by")
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mentionedByID := getUserIDForFilter(ctx, "mentioned_by")
|
||||||
|
if ctx.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only fetch the issues if we either don't have a keyword or the search returned issues
|
||||||
|
// This would otherwise return all issues if no issues were found by the search.
|
||||||
|
if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 {
|
||||||
|
issuesOpt := &models.IssuesOptions{
|
||||||
|
ListOptions: listOptions,
|
||||||
|
RepoIDs: []int64{ctx.Repo.Repository.ID},
|
||||||
|
IsClosed: isClosed,
|
||||||
|
IssueIDs: issueIDs,
|
||||||
|
LabelIDs: labelIDs,
|
||||||
|
MilestoneIDs: mileIDs,
|
||||||
|
IsPull: isPull,
|
||||||
|
UpdatedBeforeUnix: before,
|
||||||
|
UpdatedAfterUnix: since,
|
||||||
|
PosterID: createdByID,
|
||||||
|
AssigneeID: assignedByID,
|
||||||
|
MentionedID: mentionedByID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if issues, err = models.Issues(issuesOpt); err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
issuesOpt.ListOptions = db.ListOptions{
|
||||||
|
Page: -1,
|
||||||
|
}
|
||||||
|
if filteredCount, err = models.CountIssues(issuesOpt); err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetTotalCountHeader(filteredCount)
|
||||||
|
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(issues))
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateIssueStatus change issue's status
|
// UpdateIssueStatus change issue's status
|
||||||
func UpdateIssueStatus(ctx *context.Context) {
|
func UpdateIssueStatus(ctx *context.Context) {
|
||||||
issues := getActionIssues(ctx)
|
issues := getActionIssues(ctx)
|
||||||
|
|
|
@ -20,11 +20,14 @@ import (
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/convert"
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"code.gitea.io/gitea/modules/storage"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/services/forms"
|
"code.gitea.io/gitea/services/forms"
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
|
@ -503,3 +506,112 @@ func InitiateDownload(ctx *context.Context) {
|
||||||
"complete": completed,
|
"complete": completed,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SearchRepo repositories via options
|
||||||
|
func SearchRepo(ctx *context.Context) {
|
||||||
|
opts := &models.SearchRepoOptions{
|
||||||
|
ListOptions: db.ListOptions{
|
||||||
|
Page: ctx.FormInt("page"),
|
||||||
|
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||||
|
},
|
||||||
|
Actor: ctx.Doer,
|
||||||
|
Keyword: ctx.FormTrim("q"),
|
||||||
|
OwnerID: ctx.FormInt64("uid"),
|
||||||
|
PriorityOwnerID: ctx.FormInt64("priority_owner_id"),
|
||||||
|
TeamID: ctx.FormInt64("team_id"),
|
||||||
|
TopicOnly: ctx.FormBool("topic"),
|
||||||
|
Collaborate: util.OptionalBoolNone,
|
||||||
|
Private: ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")),
|
||||||
|
Template: util.OptionalBoolNone,
|
||||||
|
StarredByID: ctx.FormInt64("starredBy"),
|
||||||
|
IncludeDescription: ctx.FormBool("includeDesc"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.FormString("template") != "" {
|
||||||
|
opts.Template = util.OptionalBoolOf(ctx.FormBool("template"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.FormBool("exclusive") {
|
||||||
|
opts.Collaborate = util.OptionalBoolFalse
|
||||||
|
}
|
||||||
|
|
||||||
|
mode := ctx.FormString("mode")
|
||||||
|
switch mode {
|
||||||
|
case "source":
|
||||||
|
opts.Fork = util.OptionalBoolFalse
|
||||||
|
opts.Mirror = util.OptionalBoolFalse
|
||||||
|
case "fork":
|
||||||
|
opts.Fork = util.OptionalBoolTrue
|
||||||
|
case "mirror":
|
||||||
|
opts.Mirror = util.OptionalBoolTrue
|
||||||
|
case "collaborative":
|
||||||
|
opts.Mirror = util.OptionalBoolFalse
|
||||||
|
opts.Collaborate = util.OptionalBoolTrue
|
||||||
|
case "":
|
||||||
|
default:
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid search mode: \"%s\"", mode))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.FormString("archived") != "" {
|
||||||
|
opts.Archived = util.OptionalBoolOf(ctx.FormBool("archived"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.FormString("is_private") != "" {
|
||||||
|
opts.IsPrivate = util.OptionalBoolOf(ctx.FormBool("is_private"))
|
||||||
|
}
|
||||||
|
|
||||||
|
sortMode := ctx.FormString("sort")
|
||||||
|
if len(sortMode) > 0 {
|
||||||
|
sortOrder := ctx.FormString("order")
|
||||||
|
if len(sortOrder) == 0 {
|
||||||
|
sortOrder = "asc"
|
||||||
|
}
|
||||||
|
if searchModeMap, ok := context.SearchOrderByMap[sortOrder]; ok {
|
||||||
|
if orderBy, ok := searchModeMap[sortMode]; ok {
|
||||||
|
opts.OrderBy = orderBy
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid sort mode: \"%s\"", sortMode))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid sort order: \"%s\"", sortOrder))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
repos, count, err := models.SearchRepository(opts)
|
||||||
|
if err != nil {
|
||||||
|
ctx.JSON(http.StatusInternalServerError, api.SearchError{
|
||||||
|
OK: false,
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
results := make([]*api.Repository, len(repos))
|
||||||
|
for i, repo := range repos {
|
||||||
|
if err = repo.GetOwner(ctx); err != nil {
|
||||||
|
ctx.JSON(http.StatusInternalServerError, api.SearchError{
|
||||||
|
OK: false,
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
accessMode, err := models.AccessLevel(ctx.Doer, repo)
|
||||||
|
if err != nil {
|
||||||
|
ctx.JSON(http.StatusInternalServerError, api.SearchError{
|
||||||
|
OK: false,
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
results[i] = convert.ToRepo(repo, accessMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetTotalCountHeader(count)
|
||||||
|
ctx.JSON(http.StatusOK, api.SearchResults{
|
||||||
|
OK: true,
|
||||||
|
Data: results,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -191,3 +192,8 @@ func NotificationPurgePost(c *context.Context) {
|
||||||
|
|
||||||
c.Redirect(setting.AppSubURL+"/notifications", http.StatusSeeOther)
|
c.Redirect(setting.AppSubURL+"/notifications", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewAvailable returns the notification counts
|
||||||
|
func NewAvailable(ctx *context.APIContext) {
|
||||||
|
ctx.JSON(http.StatusOK, api.NotificationCount{New: models.CountUnread(ctx.Doer)})
|
||||||
|
}
|
||||||
|
|
44
routers/web/user/search.go
Normal file
44
routers/web/user/search.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright 2022 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 user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/convert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Search search users
|
||||||
|
func Search(ctx *context.Context) {
|
||||||
|
listOptions := db.ListOptions{
|
||||||
|
Page: ctx.FormInt("page"),
|
||||||
|
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||||
|
}
|
||||||
|
|
||||||
|
users, maxResults, err := user_model.SearchUsers(&user_model.SearchUserOptions{
|
||||||
|
Actor: ctx.Doer,
|
||||||
|
Keyword: ctx.FormTrim("q"),
|
||||||
|
UID: ctx.FormInt64("uid"),
|
||||||
|
Type: user_model.UserTypeIndividual,
|
||||||
|
ListOptions: listOptions,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||||
|
"ok": false,
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetTotalCountHeader(maxResults)
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, map[string]interface{}{
|
||||||
|
"ok": true,
|
||||||
|
"data": convert.ToUsers(ctx.Doer, users),
|
||||||
|
})
|
||||||
|
}
|
41
routers/web/user/stop_watch.go
Normal file
41
routers/web/user/stop_watch.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright 2022 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 user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models"
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/convert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetStopwatches get all stopwatches
|
||||||
|
func GetStopwatches(ctx *context.Context) {
|
||||||
|
sws, err := models.GetUserStopwatches(ctx.Doer.ID, db.ListOptions{
|
||||||
|
Page: ctx.FormInt("page"),
|
||||||
|
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := models.CountUserStopwatches(ctx.Doer.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiSWs, err := convert.ToStopWatches(sws)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetTotalCountHeader(count)
|
||||||
|
ctx.JSON(http.StatusOK, apiSWs)
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/public"
|
"code.gitea.io/gitea/modules/public"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/storage"
|
"code.gitea.io/gitea/modules/storage"
|
||||||
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/templates"
|
"code.gitea.io/gitea/modules/templates"
|
||||||
"code.gitea.io/gitea/modules/validation"
|
"code.gitea.io/gitea/modules/validation"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
@ -289,8 +290,13 @@ func RegisterRoutes(m *web.Route) {
|
||||||
m.Get("/users", explore.Users)
|
m.Get("/users", explore.Users)
|
||||||
m.Get("/organizations", explore.Organizations)
|
m.Get("/organizations", explore.Organizations)
|
||||||
m.Get("/code", explore.Code)
|
m.Get("/code", explore.Code)
|
||||||
|
m.Get("/topics/search", explore.TopicSearch)
|
||||||
}, ignExploreSignIn)
|
}, ignExploreSignIn)
|
||||||
m.Get("/issues", reqSignIn, user.Issues)
|
m.Group("/issues", func() {
|
||||||
|
m.Get("", user.Issues)
|
||||||
|
m.Get("/search", repo.SearchIssues)
|
||||||
|
}, reqSignIn)
|
||||||
|
|
||||||
m.Get("/pulls", reqSignIn, user.Pulls)
|
m.Get("/pulls", reqSignIn, user.Pulls)
|
||||||
m.Get("/milestones", reqSignIn, reqMilestonesDashboardPageEnabled, user.Milestones)
|
m.Get("/milestones", reqSignIn, reqMilestonesDashboardPageEnabled, user.Milestones)
|
||||||
|
|
||||||
|
@ -421,6 +427,8 @@ func RegisterRoutes(m *web.Route) {
|
||||||
m.Post("/forgot_password", auth.ForgotPasswdPost)
|
m.Post("/forgot_password", auth.ForgotPasswdPost)
|
||||||
m.Post("/logout", auth.SignOut)
|
m.Post("/logout", auth.SignOut)
|
||||||
m.Get("/task/{task}", user.TaskStatus)
|
m.Get("/task/{task}", user.TaskStatus)
|
||||||
|
m.Get("/stopwatches", user.GetStopwatches, reqSignIn)
|
||||||
|
m.Get("/search", user.Search, ignExploreSignIn)
|
||||||
})
|
})
|
||||||
// ***** END: User *****
|
// ***** END: User *****
|
||||||
|
|
||||||
|
@ -605,6 +613,7 @@ func RegisterRoutes(m *web.Route) {
|
||||||
m.Group("/{org}", func() {
|
m.Group("/{org}", func() {
|
||||||
m.Get("/teams/new", org.NewTeam)
|
m.Get("/teams/new", org.NewTeam)
|
||||||
m.Post("/teams/new", bindIgnErr(forms.CreateTeamForm{}), org.NewTeamPost)
|
m.Post("/teams/new", bindIgnErr(forms.CreateTeamForm{}), org.NewTeamPost)
|
||||||
|
m.Get("/teams/-/search", org.SearchTeam)
|
||||||
m.Get("/teams/{team}/edit", org.EditTeam)
|
m.Get("/teams/{team}/edit", org.EditTeam)
|
||||||
m.Post("/teams/{team}/edit", bindIgnErr(forms.CreateTeamForm{}), org.EditTeamPost)
|
m.Post("/teams/{team}/edit", bindIgnErr(forms.CreateTeamForm{}), org.EditTeamPost)
|
||||||
m.Post("/teams/{team}/delete", org.DeleteTeam)
|
m.Post("/teams/{team}/delete", org.DeleteTeam)
|
||||||
|
@ -669,6 +678,7 @@ func RegisterRoutes(m *web.Route) {
|
||||||
m.Combo("/{repoid}").Get(repo.Fork).
|
m.Combo("/{repoid}").Get(repo.Fork).
|
||||||
Post(bindIgnErr(forms.CreateRepoForm{}), repo.ForkPost)
|
Post(bindIgnErr(forms.CreateRepoForm{}), repo.ForkPost)
|
||||||
}, context.RepoIDAssignment(), context.UnitTypes(), reqRepoCodeReader)
|
}, context.RepoIDAssignment(), context.UnitTypes(), reqRepoCodeReader)
|
||||||
|
m.Get("/search", repo.SearchRepo)
|
||||||
}, reqSignIn)
|
}, reqSignIn)
|
||||||
|
|
||||||
m.Group("/{username}/-", func() {
|
m.Group("/{username}/-", func() {
|
||||||
|
@ -811,13 +821,16 @@ func RegisterRoutes(m *web.Route) {
|
||||||
Post(bindIgnErr(forms.CreateIssueForm{}), repo.NewIssuePost)
|
Post(bindIgnErr(forms.CreateIssueForm{}), repo.NewIssuePost)
|
||||||
m.Get("/choose", context.RepoRef(), repo.NewIssueChooseTemplate)
|
m.Get("/choose", context.RepoRef(), repo.NewIssueChooseTemplate)
|
||||||
})
|
})
|
||||||
|
m.Get("/search", repo.ListIssues)
|
||||||
}, context.RepoMustNotBeArchived(), reqRepoIssueReader)
|
}, context.RepoMustNotBeArchived(), reqRepoIssueReader)
|
||||||
// FIXME: should use different URLs but mostly same logic for comments of issue and pull request.
|
// FIXME: should use different URLs but mostly same logic for comments of issue and pull request.
|
||||||
// So they can apply their own enable/disable logic on routers.
|
// So they can apply their own enable/disable logic on routers.
|
||||||
m.Group("/{type:issues|pulls}", func() {
|
m.Group("/{type:issues|pulls}", func() {
|
||||||
m.Group("/{index}", func() {
|
m.Group("/{index}", func() {
|
||||||
|
m.Get("/info", repo.GetIssueInfo)
|
||||||
m.Post("/title", repo.UpdateIssueTitle)
|
m.Post("/title", repo.UpdateIssueTitle)
|
||||||
m.Post("/content", repo.UpdateIssueContent)
|
m.Post("/content", repo.UpdateIssueContent)
|
||||||
|
m.Post("/deadline", bindIgnErr(structs.EditDeadlineOption{}), repo.UpdateIssueDeadline)
|
||||||
m.Post("/watch", repo.IssueWatch)
|
m.Post("/watch", repo.IssueWatch)
|
||||||
m.Post("/ref", repo.UpdateIssueRef)
|
m.Post("/ref", repo.UpdateIssueRef)
|
||||||
m.Group("/dependency", func() {
|
m.Group("/dependency", func() {
|
||||||
|
@ -1195,6 +1208,7 @@ func RegisterRoutes(m *web.Route) {
|
||||||
m.Get("", user.Notifications)
|
m.Get("", user.Notifications)
|
||||||
m.Post("/status", user.NotificationStatusPost)
|
m.Post("/status", user.NotificationStatusPost)
|
||||||
m.Post("/purge", user.NotificationPurgePost)
|
m.Post("/purge", user.NotificationPurgePost)
|
||||||
|
m.Get("/new", user.NewAvailable)
|
||||||
}, reqSignIn)
|
}, reqSignIn)
|
||||||
|
|
||||||
if setting.API.EnableSwagger {
|
if setting.API.EnableSwagger {
|
||||||
|
|
|
@ -429,7 +429,7 @@
|
||||||
|
|
||||||
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
||||||
<div {{if ne .Issue.DeadlineUnix 0}} style="display: none;"{{end}} id="deadlineForm">
|
<div {{if ne .Issue.DeadlineUnix 0}} style="display: none;"{{end}} id="deadlineForm">
|
||||||
<form class="ui fluid action input issue-due-form" action="{{AppSubUrl}}/api/v1/repos/{{PathEscape .Repository.Owner.Name}}/{{PathEscape .Repository.Name}}/issues/{{.Issue.Index}}" method="post" id="update-issue-deadline-form">
|
<form class="ui fluid action input issue-due-form" action="{{AppSubUrl}}/{{PathEscape .Repository.Owner.Name}}/{{PathEscape .Repository.Name}}/issues/{{.Issue.Index}}/deadline" method="post" id="update-issue-deadline-form">
|
||||||
{{$.CsrfTokenHtml}}
|
{{$.CsrfTokenHtml}}
|
||||||
<input required placeholder="{{.i18n.Tr "repo.issues.due_date_form"}}" {{if gt .Issue.DeadlineUnix 0}}value="{{.Issue.DeadlineUnix.Format "2006-01-02"}}"{{end}} type="date" name="deadlineDate" id="deadlineDate">
|
<input required placeholder="{{.i18n.Tr "repo.issues.due_date_form"}}" {{if gt .Issue.DeadlineUnix 0}}value="{{.Issue.DeadlineUnix.Format "2006-01-02"}}"{{end}} type="date" name="deadlineDate" id="deadlineDate">
|
||||||
<button class="ui green icon button">
|
<button class="ui green icon button">
|
||||||
|
|
|
@ -120,7 +120,7 @@ export default {
|
||||||
load(data, callback) {
|
load(data, callback) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.i18nErrorMessage = null;
|
this.i18nErrorMessage = null;
|
||||||
$.get(`${appSubUrl}/api/v1/repos/${data.owner}/${data.repo}/issues/${data.index}`).done((issue) => {
|
$.get(`${appSubUrl}/${data.owner}/${data.repo}/issues/${data.index}/info`).done((issue) => {
|
||||||
this.issue = issue;
|
this.issue = issue;
|
||||||
}).fail((jqXHR) => {
|
}).fail((jqXHR) => {
|
||||||
if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
|
if (jqXHR.responseJSON && jqXHR.responseJSON.message) {
|
||||||
|
|
|
@ -124,7 +124,7 @@ function initVueComponents() {
|
||||||
return this.repos.length > 0 && this.repos.length < this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`];
|
return this.repos.length > 0 && this.repos.length < this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`];
|
||||||
},
|
},
|
||||||
searchURL() {
|
searchURL() {
|
||||||
return `${this.subUrl}/api/v1/repos/search?sort=updated&order=desc&uid=${this.uid}&team_id=${this.teamId}&q=${this.searchQuery
|
return `${this.subUrl}/repo/search?sort=updated&order=desc&uid=${this.uid}&team_id=${this.teamId}&q=${this.searchQuery
|
||||||
}&page=${this.page}&limit=${this.searchLimit}&mode=${this.repoTypes[this.reposFilter].searchMode
|
}&page=${this.page}&limit=${this.searchLimit}&mode=${this.repoTypes[this.reposFilter].searchMode
|
||||||
}${this.reposFilter !== 'all' ? '&exclusive=1' : ''
|
}${this.reposFilter !== 'all' ? '&exclusive=1' : ''
|
||||||
}${this.archivedFilter === 'archived' ? '&archived=true' : ''}${this.archivedFilter === 'unarchived' ? '&archived=false' : ''
|
}${this.archivedFilter === 'archived' ? '&archived=true' : ''}${this.archivedFilter === 'unarchived' ? '&archived=false' : ''
|
||||||
|
@ -302,7 +302,7 @@ function initVueComponents() {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
|
|
||||||
if (!this.reposTotalCount) {
|
if (!this.reposTotalCount) {
|
||||||
const totalCountSearchURL = `${this.subUrl}/api/v1/repos/search?sort=updated&order=desc&uid=${this.uid}&team_id=${this.teamId}&q=&page=1&mode=`;
|
const totalCountSearchURL = `${this.subUrl}/repo/search?sort=updated&order=desc&uid=${this.uid}&team_id=${this.teamId}&q=&page=1&mode=`;
|
||||||
$.getJSON(totalCountSearchURL, (_result, _textStatus, request) => {
|
$.getJSON(totalCountSearchURL, (_result, _textStatus, request) => {
|
||||||
this.reposTotalCount = request.getResponseHeader('X-Total-Count');
|
this.reposTotalCount = request.getResponseHeader('X-Total-Count');
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,7 @@ export function initCompSearchUserBox() {
|
||||||
$searchUserBox.search({
|
$searchUserBox.search({
|
||||||
minCharacters: 2,
|
minCharacters: 2,
|
||||||
apiSettings: {
|
apiSettings: {
|
||||||
url: `${appSubUrl}/api/v1/users/search?q={query}`,
|
url: `${appSubUrl}/user/search?q={query}`,
|
||||||
onResponse(response) {
|
onResponse(response) {
|
||||||
const items = [];
|
const items = [];
|
||||||
const searchQueryUppercase = $searchUserBox.find('input').val().toUpperCase();
|
const searchQueryUppercase = $searchUserBox.find('input').val().toUpperCase();
|
||||||
|
|
|
@ -158,7 +158,7 @@ async function updateNotificationTable() {
|
||||||
async function updateNotificationCount() {
|
async function updateNotificationCount() {
|
||||||
const data = await $.ajax({
|
const data = await $.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: `${appSubUrl}/api/v1/notifications/new`,
|
url: `${appSubUrl}/notifications/new`,
|
||||||
headers: {
|
headers: {
|
||||||
'X-Csrf-Token': csrfToken,
|
'X-Csrf-Token': csrfToken,
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,7 +20,7 @@ export function initOrgTeamSearchRepoBox() {
|
||||||
$searchRepoBox.search({
|
$searchRepoBox.search({
|
||||||
minCharacters: 2,
|
minCharacters: 2,
|
||||||
apiSettings: {
|
apiSettings: {
|
||||||
url: `${appSubUrl}/api/v1/repos/search?q={query}&uid=${$searchRepoBox.data('uid')}`,
|
url: `${appSubUrl}/repo/search?q={query}&uid=${$searchRepoBox.data('uid')}`,
|
||||||
onResponse(response) {
|
onResponse(response) {
|
||||||
const items = [];
|
const items = [];
|
||||||
$.each(response.data, (_i, item) => {
|
$.each(response.data, (_i, item) => {
|
||||||
|
|
|
@ -91,7 +91,7 @@ export function initRepoTopicBar() {
|
||||||
label: 'ui small label'
|
label: 'ui small label'
|
||||||
},
|
},
|
||||||
apiSettings: {
|
apiSettings: {
|
||||||
url: `${appSubUrl}/api/v1/topics/search?q={query}`,
|
url: `${appSubUrl}/explore/topics/search?q={query}`,
|
||||||
throttle: 500,
|
throttle: 500,
|
||||||
cache: false,
|
cache: false,
|
||||||
onResponse(res) {
|
onResponse(res) {
|
||||||
|
|
|
@ -54,7 +54,7 @@ function updateDeadline(deadlineString) {
|
||||||
realDeadline = new Date(newDate);
|
realDeadline = new Date(newDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
$.ajax(`${$('#update-issue-deadline-form').attr('action')}/deadline`, {
|
$.ajax(`${$('#update-issue-deadline-form').attr('action')}`, {
|
||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
due_date: realDeadline,
|
due_date: realDeadline,
|
||||||
}),
|
}),
|
||||||
|
@ -91,9 +91,9 @@ export function initRepoIssueList() {
|
||||||
const repoId = $('#repoId').val();
|
const repoId = $('#repoId').val();
|
||||||
const crossRepoSearch = $('#crossRepoSearch').val();
|
const crossRepoSearch = $('#crossRepoSearch').val();
|
||||||
const tp = $('#type').val();
|
const tp = $('#type').val();
|
||||||
let issueSearchUrl = `${appSubUrl}/api/v1/repos/${repolink}/issues?q={query}&type=${tp}`;
|
let issueSearchUrl = `${appSubUrl}/${repolink}/issues/search?q={query}&type=${tp}`;
|
||||||
if (crossRepoSearch === 'true') {
|
if (crossRepoSearch === 'true') {
|
||||||
issueSearchUrl = `${appSubUrl}/api/v1/repos/issues/search?q={query}&priority_repo_id=${repoId}&type=${tp}`;
|
issueSearchUrl = `${appSubUrl}/issues/search?q={query}&priority_repo_id=${repoId}&type=${tp}`;
|
||||||
}
|
}
|
||||||
$('#new-dependency-drop-list')
|
$('#new-dependency-drop-list')
|
||||||
.dropdown({
|
.dropdown({
|
||||||
|
@ -292,7 +292,7 @@ export function initRepoIssueReferenceRepositorySearch() {
|
||||||
$('.issue_reference_repository_search')
|
$('.issue_reference_repository_search')
|
||||||
.dropdown({
|
.dropdown({
|
||||||
apiSettings: {
|
apiSettings: {
|
||||||
url: `${appSubUrl}/api/v1/repos/search?q={query}&limit=20`,
|
url: `${appSubUrl}/repo/search?q={query}&limit=20`,
|
||||||
onResponse(response) {
|
onResponse(response) {
|
||||||
const filteredResponse = {success: true, results: []};
|
const filteredResponse = {success: true, results: []};
|
||||||
$.each(response.data, (_r, repo) => {
|
$.each(response.data, (_r, repo) => {
|
||||||
|
|
|
@ -21,7 +21,7 @@ export function initRepoSettingSearchTeamBox() {
|
||||||
$searchTeamBox.search({
|
$searchTeamBox.search({
|
||||||
minCharacters: 2,
|
minCharacters: 2,
|
||||||
apiSettings: {
|
apiSettings: {
|
||||||
url: `${appSubUrl}/api/v1/orgs/${$searchTeamBox.data('org')}/teams/search?q={query}`,
|
url: `${appSubUrl}/org/${$searchTeamBox.data('org')}/teams/-/search?q={query}`,
|
||||||
headers: {'X-Csrf-Token': csrfToken},
|
headers: {'X-Csrf-Token': csrfToken},
|
||||||
onResponse(response) {
|
onResponse(response) {
|
||||||
const items = [];
|
const items = [];
|
||||||
|
|
|
@ -23,7 +23,7 @@ export function initRepoTemplateSearch() {
|
||||||
$('#repo_template_search')
|
$('#repo_template_search')
|
||||||
.dropdown({
|
.dropdown({
|
||||||
apiSettings: {
|
apiSettings: {
|
||||||
url: `${appSubUrl}/api/v1/repos/search?q={query}&template=true&priority_owner_id=${$('#uid').val()}`,
|
url: `${appSubUrl}/repo/search?q={query}&template=true&priority_owner_id=${$('#uid').val()}`,
|
||||||
onResponse(response) {
|
onResponse(response) {
|
||||||
const filteredResponse = {success: true, results: []};
|
const filteredResponse = {success: true, results: []};
|
||||||
filteredResponse.results.push({
|
filteredResponse.results.push({
|
||||||
|
|
|
@ -111,7 +111,7 @@ async function updateStopwatchWithCallback(callback, timeout) {
|
||||||
async function updateStopwatch() {
|
async function updateStopwatch() {
|
||||||
const data = await $.ajax({
|
const data = await $.ajax({
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
url: `${appSubUrl}/api/v1/user/stopwatches`,
|
url: `${appSubUrl}/user/stopwatches`,
|
||||||
headers: {'X-Csrf-Token': csrfToken},
|
headers: {'X-Csrf-Token': csrfToken},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Reference in a new issue