Merge branch 'main' into create-user-system-defaults

This commit is contained in:
Jimmy Praet
2022-04-10 08:43:49 +02:00
committed by GitHub
150 changed files with 2423 additions and 1090 deletions
+2 -2
View File
@@ -63,8 +63,8 @@ func SearchPackages(ctx *context.Context) {
opts := &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: string(packages_model.TypeComposer),
QueryName: ctx.FormTrim("q"),
Type: packages_model.TypeComposer,
Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
Paginator: &paginator,
}
if ctx.FormTrim("type") != "" {
+6 -1
View File
@@ -256,7 +256,12 @@ func setPackageTag(tag string, pv *packages_model.PackageVersion, deleteOnly boo
}
defer committer.Close()
pvs, err := packages_model.FindVersionsByPropertyNameAndValue(ctx, pv.PackageID, npm_module.TagProperty, tag)
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
PackageID: pv.PackageID,
Properties: map[string]string{
npm_module.TagProperty: tag,
},
})
if err != nil {
return err
}
+3 -3
View File
@@ -39,9 +39,9 @@ func ServiceIndex(ctx *context.Context) {
// SearchService https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages
func SearchService(ctx *context.Context) {
pvs, count, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: string(packages_model.TypeNuGet),
QueryName: ctx.FormTrim("q"),
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNuGet,
Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
Paginator: db.NewAbsoluteListOptions(
ctx.FormInt("skip"),
ctx.FormInt("take"),
+12 -3
View File
@@ -41,7 +41,7 @@ func EnumeratePackages(ctx *context.Context) {
func EnumeratePackagesLatest(ctx *context.Context) {
pvs, _, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: string(packages_model.TypeRubyGems),
Type: packages_model.TypeRubyGems,
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -96,7 +96,7 @@ func ServePackageSpecification(ctx *context.Context) {
return
}
pvs, err := packages_model.GetVersionsByFilename(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems, filename[:len(filename)-10]+"gem")
pvs, err := getVersionsByFilename(ctx, filename[:len(filename)-10]+"gem")
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
@@ -158,7 +158,7 @@ func ServePackageSpecification(ctx *context.Context) {
func DownloadPackageFile(ctx *context.Context) {
filename := ctx.Params("filename")
pvs, err := packages_model.GetVersionsByFilename(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems, filename)
pvs, err := getVersionsByFilename(ctx, filename)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
@@ -283,3 +283,12 @@ func DeletePackage(ctx *context.Context) {
apiError(ctx, http.StatusInternalServerError, err)
}
}
func getVersionsByFilename(ctx *context.Context, filename string) ([]*packages_model.PackageVersion, error) {
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeRubyGems,
HasFileWithName: filename,
})
return pvs, err
}
+3 -7
View File
@@ -216,7 +216,6 @@ func reqToken() func(ctx *context.APIContext) {
return
}
if ctx.IsSigned {
ctx.RequireCSRF()
return
}
ctx.Error(http.StatusUnauthorized, "reqToken", "token is required")
@@ -584,8 +583,7 @@ func bind(obj interface{}) http.HandlerFunc {
func buildAuthGroup() *auth.Group {
group := auth.NewGroup(
&auth.OAuth2{},
&auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API
auth.SharedSession, // FIXME: this should be removed once all UI don't reference API/v1, see https://github.com/go-gitea/gitea/pull/16052
&auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API
)
if setting.Service.EnableReverseProxyAuth {
group.Add(&auth.ReverseProxy{})
@@ -596,11 +594,9 @@ func buildAuthGroup() *auth.Group {
}
// Routes registers all v1 APIs routes to web application.
func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
func Routes() *web.Route {
m := web.NewRoute()
m.Use(sessioner)
m.Use(securityHeaders())
if setting.CORSConfig.Enabled {
m.Use(cors.Handler(cors.Options{
@@ -609,7 +605,7 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
// setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option
AllowedMethods: setting.CORSConfig.Methods,
AllowCredentials: setting.CORSConfig.AllowCredentials,
AllowedHeaders: []string{"Authorization", "X-CSRFToken", "X-Gitea-OTP"},
AllowedHeaders: []string{"Authorization", "X-Gitea-OTP"},
MaxAge: int(setting.CORSConfig.MaxAge.Seconds()),
}))
}
+1 -1
View File
@@ -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
+2 -2
View File
@@ -56,8 +56,8 @@ func ListPackages(ctx *context.APIContext) {
pvs, count, err := packages.SearchVersions(ctx, &packages.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packageType,
QueryName: query,
Type: packages.Type(packageType),
Name: packages.SearchValue{Value: query},
Paginator: &listOptions,
})
if err != nil {
+7 -6
View File
@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
@@ -111,7 +112,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 +360,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
@@ -405,12 +406,12 @@ func ListIssues(ctx *context.APIContext) {
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])
mile, err := issues_model.GetMilestoneByRepoIDANDName(ctx.Repo.Repository.ID, part[i])
if err == nil {
mileIDs = append(mileIDs, mile.ID)
continue
}
if !models.IsErrMilestoneNotExist(err) {
if !issues_model.IsErrMilestoneNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoIDANDName", err)
return
}
@@ -418,12 +419,12 @@ func ListIssues(ctx *context.APIContext) {
if err != nil {
continue
}
mile, err = models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, id)
mile, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, id)
if err == nil {
mileIDs = append(mileIDs, mile.ID)
continue
}
if models.IsErrMilestoneNotExist(err) {
if issues_model.IsErrMilestoneNotExist(err) {
continue
}
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
}
+11 -11
View File
@@ -10,7 +10,7 @@ import (
"strconv"
"time"
"code.gitea.io/gitea/models"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
api "code.gitea.io/gitea/modules/structs"
@@ -57,7 +57,7 @@ func ListMilestones(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/MilestoneList"
milestones, total, err := models.GetMilestones(models.GetMilestonesOption{
milestones, total, err := issues_model.GetMilestones(issues_model.GetMilestonesOption{
ListOptions: utils.GetListOptions(ctx),
RepoID: ctx.Repo.Repository.ID,
State: api.StateType(ctx.FormString("state")),
@@ -146,7 +146,7 @@ func CreateMilestone(ctx *context.APIContext) {
form.Deadline = &defaultDeadline
}
milestone := &models.Milestone{
milestone := &issues_model.Milestone{
RepoID: ctx.Repo.Repository.ID,
Name: form.Title,
Content: form.Description,
@@ -158,7 +158,7 @@ func CreateMilestone(ctx *context.APIContext) {
milestone.ClosedDateUnix = timeutil.TimeStampNow()
}
if err := models.NewMilestone(milestone); err != nil {
if err := issues_model.NewMilestone(milestone); err != nil {
ctx.Error(http.StatusInternalServerError, "NewMilestone", err)
return
}
@@ -218,7 +218,7 @@ func EditMilestone(ctx *context.APIContext) {
milestone.IsClosed = *form.State == string(api.StateClosed)
}
if err := models.UpdateMilestone(milestone, oldIsClosed); err != nil {
if err := issues_model.UpdateMilestone(milestone, oldIsClosed); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateMilestone", err)
return
}
@@ -255,7 +255,7 @@ func DeleteMilestone(ctx *context.APIContext) {
return
}
if err := models.DeleteMilestoneByRepoID(ctx.Repo.Repository.ID, m.ID); err != nil {
if err := issues_model.DeleteMilestoneByRepoID(ctx.Repo.Repository.ID, m.ID); err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteMilestoneByRepoID", err)
return
}
@@ -263,23 +263,23 @@ func DeleteMilestone(ctx *context.APIContext) {
}
// getMilestoneByIDOrName get milestone by ID and if not available by name
func getMilestoneByIDOrName(ctx *context.APIContext) *models.Milestone {
func getMilestoneByIDOrName(ctx *context.APIContext) *issues_model.Milestone {
mile := ctx.Params(":id")
mileID, _ := strconv.ParseInt(mile, 0, 64)
if mileID != 0 {
milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, mileID)
milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, mileID)
if err == nil {
return milestone
} else if !models.IsErrMilestoneNotExist(err) {
} else if !issues_model.IsErrMilestoneNotExist(err) {
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
return nil
}
}
milestone, err := models.GetMilestoneByRepoIDANDName(ctx.Repo.Repository.ID, mile)
milestone, err := issues_model.GetMilestoneByRepoIDANDName(ctx.Repo.Repository.ID, mile)
if err != nil {
if models.IsErrMilestoneNotExist(err) {
if issues_model.IsErrMilestoneNotExist(err) {
ctx.NotFound()
return nil
}
+3 -2
View File
@@ -14,6 +14,7 @@ import (
"time"
"code.gitea.io/gitea/models"
issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
@@ -342,9 +343,9 @@ func CreatePullRequest(ctx *context.APIContext) {
}
if form.Milestone > 0 {
milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, form.Milestone)
milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, form.Milestone)
if err != nil {
if models.IsErrMilestoneNotExist(err) {
if issues_model.IsErrMilestoneNotExist(err) {
ctx.NotFound()
} else {
ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
+3 -3
View File
@@ -75,7 +75,7 @@ func ListPullReviews(ctx *context.APIContext) {
return
}
if err = pr.Issue.LoadRepo(); err != nil {
if err = pr.Issue.LoadRepo(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadRepo", err)
return
}
@@ -322,7 +322,7 @@ func CreatePullReview(ctx *context.APIContext) {
return
}
if err := pr.Issue.LoadRepo(); err != nil {
if err := pr.Issue.LoadRepo(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "pr.Issue.LoadRepo", err)
return
}
@@ -657,7 +657,7 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions
return
}
if err := pr.Issue.LoadRepo(); err != nil {
if err := pr.Issue.LoadRepo(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "pr.Issue.LoadRepo", err)
return
}
+1 -18
View File
@@ -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 {
+19
View 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")),
}
}
-68
View File
@@ -1,68 +0,0 @@
// 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 (
"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) {
qCreatedBefore, err := prepareQueryArg(ctx, "before")
if err != nil {
return 0, 0, err
}
qCreatedSince, err := prepareQueryArg(ctx, "since")
if err != nil {
return 0, 0, err
}
before, err = parseTime(qCreatedBefore)
if err != nil {
return 0, 0, err
}
since, err = parseTime(qCreatedSince)
if err != nil {
return 0, 0, err
}
return before, since, nil
}
// parseTime parse time and return unix timestamp
func parseTime(value string) (int64, error) {
if len(value) != 0 {
t, err := time.Parse(time.RFC3339, value)
if err != nil {
return 0, err
}
if !t.IsZero() {
return t.Unix(), nil
}
}
return 0, nil
}
// prepareQueryArg unescape and trim a query arg
func prepareQueryArg(ctx *context.APIContext, 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")),
}
}
+2 -16
View File
@@ -48,8 +48,6 @@ import (
"code.gitea.io/gitea/services/repository/archiver"
"code.gitea.io/gitea/services/task"
"code.gitea.io/gitea/services/webhook"
"gitea.com/go-chi/session"
)
func mustInit(fn func() error) {
@@ -174,20 +172,8 @@ func NormalRoutes() *web.Route {
r.Use(middle)
}
sessioner := session.Sessioner(session.Options{
Provider: setting.SessionConfig.Provider,
ProviderConfig: setting.SessionConfig.ProviderConfig,
CookieName: setting.SessionConfig.CookieName,
CookiePath: setting.SessionConfig.CookiePath,
Gclifetime: setting.SessionConfig.Gclifetime,
Maxlifetime: setting.SessionConfig.Maxlifetime,
Secure: setting.SessionConfig.Secure,
SameSite: setting.SessionConfig.SameSite,
Domain: setting.SessionConfig.Domain,
})
r.Mount("/", web_routers.Routes(sessioner))
r.Mount("/api/v1", apiv1.Routes(sessioner))
r.Mount("/", web_routers.Routes())
r.Mount("/api/v1", apiv1.Routes())
r.Mount("/api/internal", private.Routes())
if setting.Packages.Enabled {
r.Mount("/api/packages", packages_router.Routes())
+3 -3
View File
@@ -31,9 +31,9 @@ func Packages(ctx *context.Context) {
sort := ctx.FormTrim("sort")
pvs, total, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
QueryName: query,
Type: packageType,
Sort: sort,
Type: packages_model.Type(packageType),
Name: packages_model.SearchValue{Value: query},
Sort: sort,
Paginator: &db.ListOptions{
PageSize: setting.UI.PackagesPagingNum,
Page: page,
+1 -1
View File
@@ -345,7 +345,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req)
}
// Clear whatever CSRF has right now, force to generate a new one
// Clear whatever CSRF cookie has right now, force to generate a new one
middleware.DeleteCSRFCookie(ctx.Resp)
// Register last login
+1 -1
View File
@@ -1010,7 +1010,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
log.Error("Error storing session: %v", err)
}
// Clear whatever CSRF has right now, force to generate a new one
// Clear whatever CSRF cookie has right now, force to generate a new one
middleware.DeleteCSRFCookie(ctx.Resp)
// Register last login
+42
View 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,
})
}
+98
View File
@@ -0,0 +1,98 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// 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 misc
import (
"net/http"
"strings"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"mvdan.cc/xurls/v2"
)
// Markdown render markdown document to HTML
func Markdown(ctx *context.Context) {
// swagger:operation POST /markdown miscellaneous renderMarkdown
// ---
// summary: Render a markdown document as HTML
// parameters:
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/MarkdownOption"
// consumes:
// - application/json
// produces:
// - text/html
// responses:
// "200":
// "$ref": "#/responses/MarkdownRender"
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.MarkdownOption)
if ctx.HasAPIError() {
ctx.Error(http.StatusUnprocessableEntity, "", ctx.GetErrMsg())
return
}
if len(form.Text) == 0 {
_, _ = ctx.Write([]byte(""))
return
}
switch form.Mode {
case "comment":
fallthrough
case "gfm":
urlPrefix := form.Context
meta := map[string]string{}
if !strings.HasPrefix(setting.AppSubURL+"/", urlPrefix) {
// check if urlPrefix is already set to a URL
linkRegex, _ := xurls.StrictMatchingScheme("https?://")
m := linkRegex.FindStringIndex(urlPrefix)
if m == nil {
urlPrefix = util.URLJoin(setting.AppURL, form.Context)
}
}
if ctx.Repo != nil && ctx.Repo.Repository != nil {
// "gfm" = Github Flavored Markdown - set this to render as a document
if form.Mode == "gfm" {
meta = ctx.Repo.Repository.ComposeDocumentMetas()
} else {
meta = ctx.Repo.Repository.ComposeMetas()
}
}
if form.Mode == "gfm" {
meta["mode"] = "document"
}
if err := markdown.Render(&markup.RenderContext{
Ctx: ctx,
URLPrefix: urlPrefix,
Metas: meta,
IsWiki: form.Wiki,
}, strings.NewReader(form.Text), ctx.Resp); err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
default:
if err := markdown.RenderRaw(&markup.RenderContext{
Ctx: ctx,
URLPrefix: form.Context,
}, strings.NewReader(form.Text), ctx.Resp); err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
}
}
+48
View File
@@ -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
+390 -8
View File
@@ -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"
@@ -401,7 +403,7 @@ func Issues(ctx *context.Context) {
var err error
// Get milestones
ctx.Data["Milestones"], _, err = models.GetMilestones(models.GetMilestonesOption{
ctx.Data["Milestones"], _, err = issues_model.GetMilestones(issues_model.GetMilestonesOption{
RepoID: ctx.Repo.Repository.ID,
State: api.StateType(ctx.FormString("state")),
})
@@ -418,7 +420,7 @@ func Issues(ctx *context.Context) {
// RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository
func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.Repository) {
var err error
ctx.Data["OpenMilestones"], _, err = models.GetMilestones(models.GetMilestonesOption{
ctx.Data["OpenMilestones"], _, err = issues_model.GetMilestones(issues_model.GetMilestonesOption{
RepoID: repo.ID,
State: api.StateOpen,
})
@@ -426,7 +428,7 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.R
ctx.ServerError("GetMilestones", err)
return
}
ctx.Data["ClosedMilestones"], _, err = models.GetMilestones(models.GetMilestonesOption{
ctx.Data["ClosedMilestones"], _, err = issues_model.GetMilestones(issues_model.GetMilestonesOption{
RepoID: repo.ID,
State: api.StateClosed,
})
@@ -804,7 +806,7 @@ func NewIssue(ctx *context.Context) {
milestoneID := ctx.FormInt64("milestone")
if milestoneID > 0 {
milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, milestoneID)
milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID)
if err != nil {
log.Error("GetMilestoneByID: %d: %v", milestoneID, err)
} else {
@@ -913,7 +915,7 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull
// Check milestone.
milestoneID := form.MilestoneID
if milestoneID > 0 {
milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, milestoneID)
milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID)
if err != nil {
ctx.ServerError("GetMilestoneByID", err)
return nil, nil, 0, 0
@@ -1323,7 +1325,7 @@ func ViewIssue(ctx *context.Context) {
ctx.ServerError("GetIssueByID", err)
return
}
if err = otherIssue.LoadRepo(); err != nil {
if err = otherIssue.LoadRepo(ctx); err != nil {
ctx.ServerError("LoadRepo", err)
return
}
@@ -1403,7 +1405,7 @@ func ViewIssue(ctx *context.Context) {
ctx.ServerError("LoadMilestone", err)
return
}
ghostMilestone := &models.Milestone{
ghostMilestone := &issues_model.Milestone{
ID: -1,
Name: ctx.Tr("repo.issues.deleted_milestone"),
}
@@ -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)
@@ -1944,7 +1994,7 @@ func UpdatePullReviewRequest(ctx *context.Context) {
}
for _, issue := range issues {
if err := issue.LoadRepo(); err != nil {
if err := issue.LoadRepo(ctx); err != nil {
ctx.ServerError("issue.LoadRepo", err)
return
}
@@ -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 := issues_model.GetMilestoneByRepoIDANDName(ctx.Repo.Repository.ID, part[i])
if err == nil {
mileIDs = append(mileIDs, mile.ID)
continue
}
if !issues_model.IsErrMilestoneNotExist(err) {
ctx.Error(http.StatusInternalServerError, err.Error())
return
}
id, err := strconv.ParseInt(part[i], 10, 64)
if err != nil {
continue
}
mile, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, id)
if err == nil {
mileIDs = append(mileIDs, mile.ID)
continue
}
if issues_model.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)
+2 -2
View File
@@ -29,7 +29,7 @@ func AddDependency(ctx *context.Context) {
depID := ctx.FormInt64("newDependency")
if err = issue.LoadRepo(); err != nil {
if err = issue.LoadRepo(ctx); err != nil {
ctx.ServerError("LoadRepo", err)
return
}
@@ -88,7 +88,7 @@ func RemoveDependency(ctx *context.Context) {
depID := ctx.FormInt64("removeDependencyID")
if err = issue.LoadRepo(); err != nil {
if err = issue.LoadRepo(ctx); err != nil {
ctx.ServerError("LoadRepo", err)
return
}
+9 -9
View File
@@ -64,18 +64,18 @@ func CancelStopwatch(c *context.Context) {
}
// GetActiveStopwatch is the middleware that sets .ActiveStopwatch on context
func GetActiveStopwatch(c *context.Context) {
if strings.HasPrefix(c.Req.URL.Path, "/api") {
func GetActiveStopwatch(ctx *context.Context) {
if strings.HasPrefix(ctx.Req.URL.Path, "/api") {
return
}
if !c.IsSigned {
if !ctx.IsSigned {
return
}
_, sw, err := models.HasUserStopwatch(c.Doer.ID)
_, sw, err := models.HasUserStopwatch(ctx.Doer.ID)
if err != nil {
c.ServerError("HasUserStopwatch", err)
ctx.ServerError("HasUserStopwatch", err)
return
}
@@ -85,15 +85,15 @@ func GetActiveStopwatch(c *context.Context) {
issue, err := models.GetIssueByID(sw.IssueID)
if err != nil || issue == nil {
c.ServerError("GetIssueByID", err)
ctx.ServerError("GetIssueByID", err)
return
}
if err = issue.LoadRepo(); err != nil {
c.ServerError("LoadRepo", err)
if err = issue.LoadRepo(ctx); err != nil {
ctx.ServerError("LoadRepo", err)
return
}
c.Data["ActiveStopwatch"] = StopwatchTmplInfo{
ctx.Data["ActiveStopwatch"] = StopwatchTmplInfo{
issue.Link(),
issue.Repo.FullName(),
issue.Index,
+14 -14
View File
@@ -9,8 +9,8 @@ import (
"net/url"
"time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markup"
@@ -38,7 +38,7 @@ func Milestones(ctx *context.Context) {
ctx.Data["PageIsMilestones"] = true
isShowClosed := ctx.FormString("state") == "closed"
stats, err := models.GetMilestonesStatsByRepoCond(builder.And(builder.Eq{"id": ctx.Repo.Repository.ID}))
stats, err := issues_model.GetMilestonesStatsByRepoCond(builder.And(builder.Eq{"id": ctx.Repo.Repository.ID}))
if err != nil {
ctx.ServerError("MilestoneStats", err)
return
@@ -60,7 +60,7 @@ func Milestones(ctx *context.Context) {
state = structs.StateClosed
}
miles, total, err := models.GetMilestones(models.GetMilestonesOption{
miles, total, err := issues_model.GetMilestones(issues_model.GetMilestonesOption{
ListOptions: db.ListOptions{
Page: page,
PageSize: setting.UI.IssuePagingNum,
@@ -143,7 +143,7 @@ func NewMilestonePost(ctx *context.Context) {
}
deadline = time.Date(deadline.Year(), deadline.Month(), deadline.Day(), 23, 59, 59, 0, deadline.Location())
if err = models.NewMilestone(&models.Milestone{
if err = issues_model.NewMilestone(&issues_model.Milestone{
RepoID: ctx.Repo.Repository.ID,
Name: form.Title,
Content: form.Content,
@@ -163,9 +163,9 @@ func EditMilestone(ctx *context.Context) {
ctx.Data["PageIsMilestones"] = true
ctx.Data["PageIsEditMilestone"] = true
m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
m, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
if err != nil {
if models.IsErrMilestoneNotExist(err) {
if issues_model.IsErrMilestoneNotExist(err) {
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetMilestoneByRepoID", err)
@@ -203,9 +203,9 @@ func EditMilestonePost(ctx *context.Context) {
}
deadline = time.Date(deadline.Year(), deadline.Month(), deadline.Day(), 23, 59, 59, 0, deadline.Location())
m, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
m, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
if err != nil {
if models.IsErrMilestoneNotExist(err) {
if issues_model.IsErrMilestoneNotExist(err) {
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetMilestoneByRepoID", err)
@@ -215,7 +215,7 @@ func EditMilestonePost(ctx *context.Context) {
m.Name = form.Title
m.Content = form.Content
m.DeadlineUnix = timeutil.TimeStamp(deadline.Unix())
if err = models.UpdateMilestone(m, m.IsClosed); err != nil {
if err = issues_model.UpdateMilestone(m, m.IsClosed); err != nil {
ctx.ServerError("UpdateMilestone", err)
return
}
@@ -237,8 +237,8 @@ func ChangeMilestoneStatus(ctx *context.Context) {
}
id := ctx.ParamsInt64(":id")
if err := models.ChangeMilestoneStatusByRepoIDAndID(ctx.Repo.Repository.ID, id, toClose); err != nil {
if models.IsErrMilestoneNotExist(err) {
if err := issues_model.ChangeMilestoneStatusByRepoIDAndID(ctx.Repo.Repository.ID, id, toClose); err != nil {
if issues_model.IsErrMilestoneNotExist(err) {
ctx.NotFound("", err)
} else {
ctx.ServerError("ChangeMilestoneStatusByIDAndRepoID", err)
@@ -250,7 +250,7 @@ func ChangeMilestoneStatus(ctx *context.Context) {
// DeleteMilestone delete a milestone
func DeleteMilestone(ctx *context.Context) {
if err := models.DeleteMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil {
if err := issues_model.DeleteMilestoneByRepoID(ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil {
ctx.Flash.Error("DeleteMilestoneByRepoID: " + err.Error())
} else {
ctx.Flash.Success(ctx.Tr("repo.milestones.deletion_success"))
@@ -264,9 +264,9 @@ func DeleteMilestone(ctx *context.Context) {
// MilestoneIssuesAndPulls lists all the issues and pull requests of the milestone
func MilestoneIssuesAndPulls(ctx *context.Context) {
milestoneID := ctx.ParamsInt64(":id")
milestone, err := models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, milestoneID)
milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID)
if err != nil {
if models.IsErrMilestoneNotExist(err) {
if issues_model.IsErrMilestoneNotExist(err) {
ctx.NotFound("GetMilestoneByID", err)
return
}
+4 -4
View File
@@ -32,10 +32,10 @@ func Packages(ctx *context.Context) {
PageSize: setting.UI.PackagesPagingNum,
Page: page,
},
OwnerID: ctx.ContextUser.ID,
RepoID: ctx.Repo.Repository.ID,
QueryName: query,
Type: packageType,
OwnerID: ctx.ContextUser.ID,
RepoID: ctx.Repo.Repository.ID,
Type: packages.Type(packageType),
Name: packages.SearchValue{Value: query},
})
if err != nil {
ctx.ServerError("SearchLatestVersions", err)
+1 -1
View File
@@ -266,7 +266,7 @@ func checkPullInfo(ctx *context.Context) *models.Issue {
ctx.ServerError("LoadPoster", err)
return nil
}
if err := issue.LoadRepo(); err != nil {
if err := issue.LoadRepo(ctx); err != nil {
ctx.ServerError("LoadRepo", err)
return nil
}
+112
View File
@@ -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,
})
}
+6 -5
View File
@@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/models"
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
@@ -219,13 +220,13 @@ func Milestones(ctx *context.Context) {
}
}
counts, err := models.CountMilestonesByRepoCondAndKw(userRepoCond, keyword, isShowClosed)
counts, err := issues_model.CountMilestonesByRepoCondAndKw(userRepoCond, keyword, isShowClosed)
if err != nil {
ctx.ServerError("CountMilestonesByRepoIDs", err)
return
}
milestones, err := models.SearchMilestones(repoCond, page, isShowClosed, sortType, keyword)
milestones, err := issues_model.SearchMilestones(repoCond, page, isShowClosed, sortType, keyword)
if err != nil {
ctx.ServerError("SearchMilestones", err)
return
@@ -271,17 +272,17 @@ func Milestones(ctx *context.Context) {
i++
}
milestoneStats, err := models.GetMilestonesStatsByRepoCondAndKw(repoCond, keyword)
milestoneStats, err := issues_model.GetMilestonesStatsByRepoCondAndKw(repoCond, keyword)
if err != nil {
ctx.ServerError("GetMilestoneStats", err)
return
}
var totalMilestoneStats *models.MilestonesStats
var totalMilestoneStats *issues_model.MilestonesStats
if len(repoIDs) == 0 {
totalMilestoneStats = milestoneStats
} else {
totalMilestoneStats, err = models.GetMilestonesStatsByRepoCondAndKw(userRepoCond, keyword)
totalMilestoneStats, err = issues_model.GetMilestonesStatsByRepoCondAndKw(userRepoCond, keyword)
if err != nil {
ctx.ServerError("GetMilestoneStats", err)
return
+6
View File
@@ -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)})
}
+11 -7
View File
@@ -43,9 +43,9 @@ func ListPackages(ctx *context.Context) {
PageSize: setting.UI.PackagesPagingNum,
Page: page,
},
OwnerID: ctx.ContextUser.ID,
Type: packageType,
QueryName: query,
OwnerID: ctx.ContextUser.ID,
Type: packages_model.Type(packageType),
Name: packages_model.SearchValue{Value: query},
})
if err != nil {
ctx.ServerError("SearchLatestVersions", err)
@@ -219,9 +219,12 @@ func ListPackageVersions(ctx *context.Context) {
}
default:
pvs, total, err = packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
Paginator: pagination,
PackageID: p.ID,
QueryVersion: query,
Paginator: pagination,
PackageID: p.ID,
Version: packages_model.SearchValue{
ExactMatch: false,
Value: query,
},
})
if err != nil {
ctx.ServerError("SearchVersions", err)
@@ -256,7 +259,8 @@ func PackageSettings(ctx *context.Context) {
ctx.Data["PackageDescriptor"] = pd
repos, _, _ := models.GetUserRepositories(&models.SearchRepoOptions{
Actor: pd.Owner,
Actor: pd.Owner,
Private: true,
})
ctx.Data["Repos"] = repos
ctx.Data["CanWritePackages"] = ctx.Package.AccessMode >= perm.AccessModeWrite || ctx.IsUserSiteAdmin()
+44
View 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
View 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)
}
+31 -4
View File
@@ -20,17 +20,18 @@ 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"
"code.gitea.io/gitea/modules/web/routing"
"code.gitea.io/gitea/routers/api/v1/misc"
"code.gitea.io/gitea/routers/web/admin"
"code.gitea.io/gitea/routers/web/auth"
"code.gitea.io/gitea/routers/web/dev"
"code.gitea.io/gitea/routers/web/events"
"code.gitea.io/gitea/routers/web/explore"
"code.gitea.io/gitea/routers/web/feed"
"code.gitea.io/gitea/routers/web/misc"
"code.gitea.io/gitea/routers/web/org"
"code.gitea.io/gitea/routers/web/repo"
"code.gitea.io/gitea/routers/web/user"
@@ -45,6 +46,7 @@ import (
_ "code.gitea.io/gitea/modules/session" // to registers all internal adapters
"gitea.com/go-chi/captcha"
"gitea.com/go-chi/session"
"github.com/NYTimes/gziphandler"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
@@ -84,7 +86,7 @@ func buildAuthGroup() *auth_service.Group {
group := auth_service.NewGroup(
&auth_service.OAuth2{}, // FIXME: this should be removed and only applied in download and oauth realted routers
&auth_service.Basic{}, // FIXME: this should be removed and only applied in download and git/lfs routers
auth_service.SharedSession,
&auth_service.Session{},
)
if setting.Service.EnableReverseProxyAuth {
group.Add(&auth_service.ReverseProxy{})
@@ -95,7 +97,7 @@ func buildAuthGroup() *auth_service.Group {
}
// Routes returns all web routes
func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
func Routes() *web.Route {
routes := web.NewRoute()
routes.Use(web.WrapWithPrefix(public.AssetsURLPathPrefix, public.AssetsHandlerFunc(&public.Options{
@@ -104,6 +106,17 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
CorsHandler: CorsHandler(),
}), "AssetsHandler"))
sessioner := session.Sessioner(session.Options{
Provider: setting.SessionConfig.Provider,
ProviderConfig: setting.SessionConfig.ProviderConfig,
CookieName: setting.SessionConfig.CookieName,
CookiePath: setting.SessionConfig.CookiePath,
Gclifetime: setting.SessionConfig.Gclifetime,
Maxlifetime: setting.SessionConfig.Maxlifetime,
Secure: setting.SessionConfig.Secure,
SameSite: setting.SessionConfig.SameSite,
Domain: setting.SessionConfig.Domain,
})
routes.Use(sessioner)
routes.Use(Recovery())
@@ -289,8 +302,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 +439,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 +625,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 +690,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 +833,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() {
@@ -865,6 +890,7 @@ func RegisterRoutes(m *web.Route) {
m.Group("/comments/{id}", func() {
m.Get("/attachments", repo.GetCommentAttachments)
})
m.Post("/markdown", bindIgnErr(structs.MarkdownOption{}), misc.Markdown)
m.Group("/labels", func() {
m.Post("/new", bindIgnErr(forms.CreateLabelForm{}), repo.NewLabel)
m.Post("/edit", bindIgnErr(forms.CreateLabelForm{}), repo.UpdateLabel)
@@ -1195,6 +1221,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 {