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:
Родитель
bb7e0619c3
Коммит
783a021889
|
@ -7,6 +7,7 @@ package integrations
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -20,6 +21,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/indexer/issues"
|
||||
"code.gitea.io/gitea/modules/references"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
|
@ -347,3 +349,209 @@ func TestIssueRedirect(t *testing.T) {
|
|||
resp = session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
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"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -173,3 +175,30 @@ func TestOrgRestrictedUser(t *testing.T) {
|
|||
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s", orgName, repoName))
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
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/translation/i18n"
|
||||
|
||||
|
@ -222,3 +225,26 @@ func testExportUserGPGKeys(t *testing.T, user, expected string) {
|
|||
// t.Log(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
|
||||
func (ctx *APIContext) RequireCSRF() {
|
||||
headerToken := ctx.Req.Header.Get(ctx.csrf.GetHeaderName())
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"html/template"
|
||||
"io"
|
||||
|
@ -21,6 +22,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
|
@ -577,6 +579,22 @@ func (ctx *Context) Value(key interface{}) interface{} {
|
|||
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
|
||||
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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package utils
|
||||
package context
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"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
|
||||
func GetQueryBeforeSince(ctx *context.APIContext) (before, since int64, err error) {
|
||||
func GetQueryBeforeSince(ctx *Context) (before, since int64, err error) {
|
||||
qCreatedBefore, err := prepareQueryArg(ctx, "before")
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
|
@ -53,16 +49,8 @@ func parseTime(value string) (int64, error) {
|
|||
}
|
||||
|
||||
// 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 = strings.TrimSpace(value)
|
||||
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 {
|
||||
before, since, err := utils.GetQueryBeforeSince(ctx)
|
||||
before, since, err := context.GetQueryBeforeSince(ctx.Context)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||
return nil
|
||||
|
|
|
@ -111,7 +111,7 @@ func SearchIssues(ctx *context.APIContext) {
|
|||
// "200":
|
||||
// "$ref": "#/responses/IssueList"
|
||||
|
||||
before, since, err := utils.GetQueryBeforeSince(ctx)
|
||||
before, since, err := context.GetQueryBeforeSince(ctx.Context)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||
return
|
||||
|
@ -359,7 +359,7 @@ func ListIssues(ctx *context.APIContext) {
|
|||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/IssueList"
|
||||
before, since, err := utils.GetQueryBeforeSince(ctx)
|
||||
before, since, err := context.GetQueryBeforeSince(ctx.Context)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||
return
|
||||
|
|
|
@ -58,7 +58,7 @@ func ListIssueComments(ctx *context.APIContext) {
|
|||
// "200":
|
||||
// "$ref": "#/responses/CommentList"
|
||||
|
||||
before, since, err := utils.GetQueryBeforeSince(ctx)
|
||||
before, since, err := context.GetQueryBeforeSince(ctx.Context)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||
return
|
||||
|
@ -150,7 +150,7 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) {
|
|||
// "200":
|
||||
// "$ref": "#/responses/TimelineList"
|
||||
|
||||
before, since, err := utils.GetQueryBeforeSince(ctx)
|
||||
before, since, err := context.GetQueryBeforeSince(ctx.Context)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||
return
|
||||
|
@ -253,7 +253,7 @@ func ListRepoIssueComments(ctx *context.APIContext) {
|
|||
// "200":
|
||||
// "$ref": "#/responses/CommentList"
|
||||
|
||||
before, since, err := utils.GetQueryBeforeSince(ctx)
|
||||
before, since, err := context.GetQueryBeforeSince(ctx.Context)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
|
||||
return
|
||||
|
|
|
@ -103,7 +103,7 @@ func ListTrackedTimes(ctx *context.APIContext) {
|
|||
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)
|
||||
return
|
||||
}
|
||||
|
@ -522,7 +522,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) {
|
|||
}
|
||||
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
@ -597,7 +597,7 @@ func ListMyTrackedTimes(ctx *context.APIContext) {
|
|||
}
|
||||
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -31,23 +31,6 @@ import (
|
|||
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
|
||||
func Search(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/search repository repoSearch
|
||||
|
@ -193,7 +176,7 @@ func Search(ctx *context.APIContext) {
|
|||
if len(sortOrder) == 0 {
|
||||
sortOrder = "asc"
|
||||
}
|
||||
if searchModeMap, ok := searchOrderByMap[sortOrder]; ok {
|
||||
if searchModeMap, ok := context.SearchOrderByMap[sortOrder]; ok {
|
||||
if orderBy, ok := searchModeMap[sortMode]; ok {
|
||||
opts.OrderBy = orderBy
|
||||
} else {
|
||||
|
|
|
@ -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")),
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
|
@ -20,7 +21,9 @@ import (
|
|||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/convert"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/utils"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
|
@ -329,6 +332,51 @@ func TeamRepositories(ctx *context.Context) {
|
|||
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
|
||||
func EditTeam(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Org.Organization.FullName
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
|
@ -36,6 +37,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/templates/vars"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/upload"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
|
@ -1762,6 +1764,20 @@ func getActionIssues(ctx *context.Context) []*models.Issue {
|
|||
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
|
||||
func UpdateIssueTitle(ctx *context.Context) {
|
||||
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
|
||||
func UpdateIssueMilestone(ctx *context.Context) {
|
||||
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
|
||||
func UpdateIssueStatus(ctx *context.Context) {
|
||||
issues := getActionIssues(ctx)
|
||||
|
|
|
@ -20,11 +20,14 @@ import (
|
|||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/convert"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"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/services/forms"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
|
@ -503,3 +506,112 @@ func InitiateDownload(ctx *context.Context) {
|
|||
"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/context"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -191,3 +192,8 @@ func NotificationPurgePost(c *context.Context) {
|
|||
|
||||
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)})
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
})
|
||||
}
|
|
@ -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/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
|
@ -289,8 +290,13 @@ func RegisterRoutes(m *web.Route) {
|
|||
m.Get("/users", explore.Users)
|
||||
m.Get("/organizations", explore.Organizations)
|
||||
m.Get("/code", explore.Code)
|
||||
m.Get("/topics/search", explore.TopicSearch)
|
||||
}, 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("/milestones", reqSignIn, reqMilestonesDashboardPageEnabled, user.Milestones)
|
||||
|
||||
|
@ -421,6 +427,8 @@ func RegisterRoutes(m *web.Route) {
|
|||
m.Post("/forgot_password", auth.ForgotPasswdPost)
|
||||
m.Post("/logout", auth.SignOut)
|
||||
m.Get("/task/{task}", user.TaskStatus)
|
||||
m.Get("/stopwatches", user.GetStopwatches, reqSignIn)
|
||||
m.Get("/search", user.Search, ignExploreSignIn)
|
||||
})
|
||||
// ***** END: User *****
|
||||
|
||||
|
@ -605,6 +613,7 @@ func RegisterRoutes(m *web.Route) {
|
|||
m.Group("/{org}", func() {
|
||||
m.Get("/teams/new", org.NewTeam)
|
||||
m.Post("/teams/new", bindIgnErr(forms.CreateTeamForm{}), org.NewTeamPost)
|
||||
m.Get("/teams/-/search", org.SearchTeam)
|
||||
m.Get("/teams/{team}/edit", org.EditTeam)
|
||||
m.Post("/teams/{team}/edit", bindIgnErr(forms.CreateTeamForm{}), org.EditTeamPost)
|
||||
m.Post("/teams/{team}/delete", org.DeleteTeam)
|
||||
|
@ -669,6 +678,7 @@ func RegisterRoutes(m *web.Route) {
|
|||
m.Combo("/{repoid}").Get(repo.Fork).
|
||||
Post(bindIgnErr(forms.CreateRepoForm{}), repo.ForkPost)
|
||||
}, context.RepoIDAssignment(), context.UnitTypes(), reqRepoCodeReader)
|
||||
m.Get("/search", repo.SearchRepo)
|
||||
}, reqSignIn)
|
||||
|
||||
m.Group("/{username}/-", func() {
|
||||
|
@ -811,13 +821,16 @@ func RegisterRoutes(m *web.Route) {
|
|||
Post(bindIgnErr(forms.CreateIssueForm{}), repo.NewIssuePost)
|
||||
m.Get("/choose", context.RepoRef(), repo.NewIssueChooseTemplate)
|
||||
})
|
||||
m.Get("/search", repo.ListIssues)
|
||||
}, context.RepoMustNotBeArchived(), reqRepoIssueReader)
|
||||
// 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.
|
||||
m.Group("/{type:issues|pulls}", func() {
|
||||
m.Group("/{index}", func() {
|
||||
m.Get("/info", repo.GetIssueInfo)
|
||||
m.Post("/title", repo.UpdateIssueTitle)
|
||||
m.Post("/content", repo.UpdateIssueContent)
|
||||
m.Post("/deadline", bindIgnErr(structs.EditDeadlineOption{}), repo.UpdateIssueDeadline)
|
||||
m.Post("/watch", repo.IssueWatch)
|
||||
m.Post("/ref", repo.UpdateIssueRef)
|
||||
m.Group("/dependency", func() {
|
||||
|
@ -1195,6 +1208,7 @@ func RegisterRoutes(m *web.Route) {
|
|||
m.Get("", user.Notifications)
|
||||
m.Post("/status", user.NotificationStatusPost)
|
||||
m.Post("/purge", user.NotificationPurgePost)
|
||||
m.Get("/new", user.NewAvailable)
|
||||
}, reqSignIn)
|
||||
|
||||
if setting.API.EnableSwagger {
|
||||
|
|
|
@ -429,7 +429,7 @@
|
|||
|
||||
{{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}
|
||||
<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}}
|
||||
<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">
|
||||
|
|
|
@ -120,7 +120,7 @@ export default {
|
|||
load(data, callback) {
|
||||
this.loading = true;
|
||||
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;
|
||||
}).fail((jqXHR) => {
|
||||
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}`];
|
||||
},
|
||||
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
|
||||
}${this.reposFilter !== 'all' ? '&exclusive=1' : ''
|
||||
}${this.archivedFilter === 'archived' ? '&archived=true' : ''}${this.archivedFilter === 'unarchived' ? '&archived=false' : ''
|
||||
|
@ -302,7 +302,7 @@ function initVueComponents() {
|
|||
this.isLoading = true;
|
||||
|
||||
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) => {
|
||||
this.reposTotalCount = request.getResponseHeader('X-Total-Count');
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@ export function initCompSearchUserBox() {
|
|||
$searchUserBox.search({
|
||||
minCharacters: 2,
|
||||
apiSettings: {
|
||||
url: `${appSubUrl}/api/v1/users/search?q={query}`,
|
||||
url: `${appSubUrl}/user/search?q={query}`,
|
||||
onResponse(response) {
|
||||
const items = [];
|
||||
const searchQueryUppercase = $searchUserBox.find('input').val().toUpperCase();
|
||||
|
|
|
@ -158,7 +158,7 @@ async function updateNotificationTable() {
|
|||
async function updateNotificationCount() {
|
||||
const data = await $.ajax({
|
||||
type: 'GET',
|
||||
url: `${appSubUrl}/api/v1/notifications/new`,
|
||||
url: `${appSubUrl}/notifications/new`,
|
||||
headers: {
|
||||
'X-Csrf-Token': csrfToken,
|
||||
},
|
||||
|
|
|
@ -20,7 +20,7 @@ export function initOrgTeamSearchRepoBox() {
|
|||
$searchRepoBox.search({
|
||||
minCharacters: 2,
|
||||
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) {
|
||||
const items = [];
|
||||
$.each(response.data, (_i, item) => {
|
||||
|
|
|
@ -91,7 +91,7 @@ export function initRepoTopicBar() {
|
|||
label: 'ui small label'
|
||||
},
|
||||
apiSettings: {
|
||||
url: `${appSubUrl}/api/v1/topics/search?q={query}`,
|
||||
url: `${appSubUrl}/explore/topics/search?q={query}`,
|
||||
throttle: 500,
|
||||
cache: false,
|
||||
onResponse(res) {
|
||||
|
|
|
@ -54,7 +54,7 @@ function updateDeadline(deadlineString) {
|
|||
realDeadline = new Date(newDate);
|
||||
}
|
||||
|
||||
$.ajax(`${$('#update-issue-deadline-form').attr('action')}/deadline`, {
|
||||
$.ajax(`${$('#update-issue-deadline-form').attr('action')}`, {
|
||||
data: JSON.stringify({
|
||||
due_date: realDeadline,
|
||||
}),
|
||||
|
@ -91,9 +91,9 @@ export function initRepoIssueList() {
|
|||
const repoId = $('#repoId').val();
|
||||
const crossRepoSearch = $('#crossRepoSearch').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') {
|
||||
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')
|
||||
.dropdown({
|
||||
|
@ -292,7 +292,7 @@ export function initRepoIssueReferenceRepositorySearch() {
|
|||
$('.issue_reference_repository_search')
|
||||
.dropdown({
|
||||
apiSettings: {
|
||||
url: `${appSubUrl}/api/v1/repos/search?q={query}&limit=20`,
|
||||
url: `${appSubUrl}/repo/search?q={query}&limit=20`,
|
||||
onResponse(response) {
|
||||
const filteredResponse = {success: true, results: []};
|
||||
$.each(response.data, (_r, repo) => {
|
||||
|
|
|
@ -21,7 +21,7 @@ export function initRepoSettingSearchTeamBox() {
|
|||
$searchTeamBox.search({
|
||||
minCharacters: 2,
|
||||
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},
|
||||
onResponse(response) {
|
||||
const items = [];
|
||||
|
|
|
@ -23,7 +23,7 @@ export function initRepoTemplateSearch() {
|
|||
$('#repo_template_search')
|
||||
.dropdown({
|
||||
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) {
|
||||
const filteredResponse = {success: true, results: []};
|
||||
filteredResponse.results.push({
|
||||
|
|
|
@ -111,7 +111,7 @@ async function updateStopwatchWithCallback(callback, timeout) {
|
|||
async function updateStopwatch() {
|
||||
const data = await $.ajax({
|
||||
type: 'GET',
|
||||
url: `${appSubUrl}/api/v1/user/stopwatches`,
|
||||
url: `${appSubUrl}/user/stopwatches`,
|
||||
headers: {'X-Csrf-Token': csrfToken},
|
||||
});
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче