feat: vendor gitea 1.16.2
This commit is contained in:
@@ -4,103 +4,11 @@
|
||||
package activitypub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/activitypub"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
|
||||
ap "github.com/go-ap/activitypub"
|
||||
"github.com/go-ap/jsonld"
|
||||
)
|
||||
|
||||
// Person function returns the Person actor for a user
|
||||
func Person(ctx *context.APIContext) {
|
||||
// swagger:operation GET /activitypub/user-id/{user-id} activitypub activitypubPerson
|
||||
// ---
|
||||
// summary: Returns the Person actor for a user
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: user-id
|
||||
// in: path
|
||||
// description: user ID of the user
|
||||
// type: integer
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/ActivityPub"
|
||||
|
||||
// TODO: the setting.AppURL during the test doesn't follow the definition: "It always has a '/' suffix"
|
||||
link := fmt.Sprintf("%s/api/v1/activitypub/user-id/%d", strings.TrimSuffix(setting.AppURL, "/"), ctx.ContextUser.ID)
|
||||
person := ap.PersonNew(ap.IRI(link))
|
||||
|
||||
person.Name = ap.NaturalLanguageValuesNew()
|
||||
err := person.Name.Set("en", ap.Content(ctx.ContextUser.FullName))
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
person.PreferredUsername = ap.NaturalLanguageValuesNew()
|
||||
err = person.PreferredUsername.Set("en", ap.Content(ctx.ContextUser.Name))
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
person.URL = ap.IRI(ctx.ContextUser.HTMLURL(ctx))
|
||||
|
||||
person.Icon = ap.Image{
|
||||
Type: ap.ImageType,
|
||||
MediaType: "image/png",
|
||||
URL: ap.IRI(ctx.ContextUser.AvatarLink(ctx)),
|
||||
}
|
||||
|
||||
person.Inbox = ap.IRI(link + "/inbox")
|
||||
person.Outbox = ap.IRI(link + "/outbox")
|
||||
|
||||
person.PublicKey.ID = ap.IRI(link + "#main-key")
|
||||
person.PublicKey.Owner = ap.IRI(link)
|
||||
|
||||
publicKeyPem, err := activitypub.GetPublicKey(ctx, ctx.ContextUser)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
person.PublicKey.PublicKeyPem = publicKeyPem
|
||||
|
||||
binary, err := jsonld.WithContext(jsonld.IRI(ap.ActivityBaseURI), jsonld.IRI(ap.SecurityContextURI)).Marshal(person)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.Resp.Header().Add("Content-Type", activitypub.ActivityStreamsContentType)
|
||||
ctx.Resp.WriteHeader(http.StatusOK)
|
||||
if _, err = ctx.Resp.Write(binary); err != nil {
|
||||
log.Error("write to resp err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// PersonInbox function handles the incoming data for a user inbox
|
||||
func PersonInbox(ctx *context.APIContext) {
|
||||
// swagger:operation POST /activitypub/user-id/{user-id}/inbox activitypub activitypubPersonInbox
|
||||
// ---
|
||||
// summary: Send to the inbox
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: user-id
|
||||
// in: path
|
||||
// description: user ID of the user
|
||||
// type: integer
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
func NotImplemented(ctx *context.APIContext) {
|
||||
http.Error(ctx.Resp, "Not implemented", http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package activitypub
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"code.gitea.io/gitea/modules/activitypub"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
gitea_context "code.gitea.io/gitea/services/context"
|
||||
|
||||
"github.com/42wim/httpsig"
|
||||
ap "github.com/go-ap/activitypub"
|
||||
)
|
||||
|
||||
func getPublicKeyFromResponse(b []byte, keyID *url.URL) (p crypto.PublicKey, err error) {
|
||||
person := ap.PersonNew(ap.IRI(keyID.String()))
|
||||
err = person.UnmarshalJSON(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ActivityStreams type cannot be converted to one known to have publicKey property: %w", err)
|
||||
}
|
||||
pubKey := person.PublicKey
|
||||
if pubKey.ID.String() != keyID.String() {
|
||||
return nil, fmt.Errorf("cannot find publicKey with id: %s in %s", keyID, string(b))
|
||||
}
|
||||
pubKeyPem := pubKey.PublicKeyPem
|
||||
block, _ := pem.Decode([]byte(pubKeyPem))
|
||||
if block == nil || block.Type != "PUBLIC KEY" {
|
||||
return nil, errors.New("could not decode publicKeyPem to PUBLIC KEY pem block type")
|
||||
}
|
||||
p, err = x509.ParsePKIXPublicKey(block.Bytes)
|
||||
return p, err
|
||||
}
|
||||
|
||||
func fetch(iri *url.URL) (b []byte, err error) {
|
||||
req := httplib.NewRequest(iri.String(), http.MethodGet)
|
||||
req.Header("Accept", activitypub.ActivityStreamsContentType)
|
||||
req.Header("User-Agent", "Gitea/"+setting.AppVer)
|
||||
resp, err := req.Response()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("url IRI fetch [%s] failed with status (%d): %s", iri, resp.StatusCode, resp.Status)
|
||||
}
|
||||
b, err = io.ReadAll(io.LimitReader(resp.Body, setting.Federation.MaxSize))
|
||||
return b, err
|
||||
}
|
||||
|
||||
func verifyHTTPSignatures(ctx *gitea_context.APIContext) (authenticated bool, err error) {
|
||||
r := ctx.Req
|
||||
|
||||
// 1. Figure out what key we need to verify
|
||||
v, err := httpsig.NewVerifier(r)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ID := v.KeyId()
|
||||
idIRI, err := url.Parse(ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// 2. Fetch the public key of the other actor
|
||||
b, err := fetch(idIRI)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
pubKey, err := getPublicKeyFromResponse(b, idIRI)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// 3. Verify the other actor's key
|
||||
algo := httpsig.Algorithm(setting.Federation.Algorithms[0])
|
||||
authenticated = v.Verify(pubKey, algo) == nil
|
||||
return authenticated, err
|
||||
}
|
||||
|
||||
// ReqHTTPSignature function
|
||||
func ReqHTTPSignature() func(ctx *gitea_context.APIContext) {
|
||||
return func(ctx *gitea_context.APIContext) {
|
||||
if authenticated, err := verifyHTTPSignatures(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
} else if !authenticated {
|
||||
ctx.APIError(http.StatusForbidden, "request signature verification failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
repo_service "code.gitea.io/gitea/services/repository"
|
||||
@@ -50,7 +50,7 @@ func ListUnadoptedRepositories(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(int64(count))
|
||||
ctx.SetTotalCountHeader(count)
|
||||
|
||||
ctx.JSON(http.StatusOK, repoNames)
|
||||
}
|
||||
@@ -99,12 +99,12 @@ func AdoptRepository(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
isDir, err := util.IsDir(repo_model.RepoPath(ctxUser.Name, repoName))
|
||||
exist, err := gitrepo.IsRepositoryExist(ctx, repo_model.StorageRepo(repo_model.RelativePath(ctxUser.Name, repoName)))
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if has || !isDir {
|
||||
if has || !exist {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
@@ -161,12 +161,12 @@ func DeleteUnadoptedRepository(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
isDir, err := util.IsDir(repo_model.RepoPath(ctxUser.Name, repoName))
|
||||
exist, err := gitrepo.IsRepositoryExist(ctx, repo_model.StorageRepo(repo_model.RelativePath(ctxUser.Name, repoName)))
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if has || !isDir {
|
||||
if has || !exist {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func GetAllEmails(ctx *context.APIContext) {
|
||||
results[i] = convert.ToEmailSearch(emails[i])
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
|
||||
ctx.SetLinkHeader(maxResults, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(maxResults)
|
||||
ctx.JSON(http.StatusOK, &results)
|
||||
}
|
||||
|
||||
@@ -57,8 +57,13 @@ func ListHooks(ctx *context.APIContext) {
|
||||
case "all":
|
||||
isSystemWebhook = optional.None[bool]()
|
||||
}
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
opts := &webhook.ListSystemWebhookOptions{
|
||||
ListOptions: listOptions,
|
||||
IsSystem: isSystemWebhook,
|
||||
}
|
||||
|
||||
sysHooks, err := webhook.GetSystemOrDefaultWebhooks(ctx, isSystemWebhook)
|
||||
sysHooks, total, err := webhook.GetGlobalWebhooks(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -72,6 +77,8 @@ func ListHooks(ctx *context.APIContext) {
|
||||
}
|
||||
hooks[i] = h
|
||||
}
|
||||
ctx.SetLinkHeader(total, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, hooks)
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ func GetAllOrgs(ctx *context.APIContext) {
|
||||
|
||||
users, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
|
||||
Actor: ctx.Doer,
|
||||
Type: user_model.UserTypeOrganization,
|
||||
Types: []user_model.UserType{user_model.UserTypeOrganization},
|
||||
OrderBy: db.SearchOrderByAlphabetically,
|
||||
ListOptions: listOptions,
|
||||
Visible: []api.VisibleType{api.VisibleTypePublic, api.VisibleTypeLimited, api.VisibleTypePrivate},
|
||||
@@ -117,7 +117,7 @@ func GetAllOrgs(ctx *context.APIContext) {
|
||||
orgs[i] = convert.ToOrganization(ctx, organization.OrgFromUser(users[i]))
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
|
||||
ctx.SetLinkHeader(maxResults, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(maxResults)
|
||||
ctx.JSON(http.StatusOK, &orgs)
|
||||
}
|
||||
|
||||
@@ -10,26 +10,11 @@ import (
|
||||
|
||||
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
|
||||
|
||||
// GetRegistrationToken returns the token to register global runners
|
||||
func GetRegistrationToken(ctx *context.APIContext) {
|
||||
// swagger:operation GET /admin/runners/registration-token admin adminGetRunnerRegistrationToken
|
||||
// ---
|
||||
// summary: Get an global actions runner registration token
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RegistrationToken"
|
||||
|
||||
shared.GetRegistrationToken(ctx, 0, 0)
|
||||
}
|
||||
|
||||
// CreateRegistrationToken returns the token to register global runners
|
||||
func CreateRegistrationToken(ctx *context.APIContext) {
|
||||
// swagger:operation POST /admin/actions/runners/registration-token admin adminCreateRunnerRegistrationToken
|
||||
// ---
|
||||
// summary: Get an global actions runner registration token
|
||||
// summary: Get a global actions runner registration token
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
@@ -47,9 +32,15 @@ func ListRunners(ctx *context.APIContext) {
|
||||
// summary: Get all runners
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: disabled
|
||||
// in: query
|
||||
// description: filter by disabled status (true or false)
|
||||
// type: boolean
|
||||
// required: false
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/definitions/ActionRunnersResponse"
|
||||
// "$ref": "#/responses/RunnerList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
@@ -57,11 +48,11 @@ func ListRunners(ctx *context.APIContext) {
|
||||
shared.ListRunners(ctx, 0, 0)
|
||||
}
|
||||
|
||||
// GetRunner get an global runner
|
||||
// GetRunner get a global runner
|
||||
func GetRunner(ctx *context.APIContext) {
|
||||
// swagger:operation GET /admin/actions/runners/{runner_id} admin getAdminRunner
|
||||
// ---
|
||||
// summary: Get an global runner
|
||||
// summary: Get a global runner
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
@@ -72,7 +63,7 @@ func GetRunner(ctx *context.APIContext) {
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/definitions/ActionRunner"
|
||||
// "$ref": "#/responses/Runner"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
@@ -80,11 +71,11 @@ func GetRunner(ctx *context.APIContext) {
|
||||
shared.GetRunner(ctx, 0, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
// DeleteRunner delete an global runner
|
||||
// DeleteRunner delete a global runner
|
||||
func DeleteRunner(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /admin/actions/runners/{runner_id} admin deleteAdminRunner
|
||||
// ---
|
||||
// summary: Delete an global runner
|
||||
// summary: Delete a global runner
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
@@ -102,3 +93,34 @@ func DeleteRunner(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.DeleteRunner(ctx, 0, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
// UpdateRunner update a global runner
|
||||
func UpdateRunner(ctx *context.APIContext) {
|
||||
// swagger:operation PATCH /admin/actions/runners/{runner_id} admin updateAdminRunner
|
||||
// ---
|
||||
// summary: Update a global runner
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/EditActionRunnerOption"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Runner"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
shared.UpdateRunner(ctx, 0, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
@@ -414,22 +414,116 @@ func SearchUsers(ctx *context.APIContext) {
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// - name: sort
|
||||
// in: query
|
||||
// description: sort users by attribute. Supported values are
|
||||
// "name", "created", "updated" and "id".
|
||||
// Default is "name"
|
||||
// type: string
|
||||
// - name: order
|
||||
// in: query
|
||||
// description: sort order, either "asc" (ascending) or "desc" (descending).
|
||||
// Default is "asc", ignored if "sort" is not specified.
|
||||
// type: string
|
||||
// - name: q
|
||||
// in: query
|
||||
// description: search term (username, full name, email)
|
||||
// type: string
|
||||
// - name: visibility
|
||||
// in: query
|
||||
// description: visibility filter. Supported values are
|
||||
// "public", "limited" and "private".
|
||||
// type: string
|
||||
// - name: is_active
|
||||
// in: query
|
||||
// description: filter active users
|
||||
// type: boolean
|
||||
// - name: is_admin
|
||||
// in: query
|
||||
// description: filter admin users
|
||||
// type: boolean
|
||||
// - name: is_restricted
|
||||
// in: query
|
||||
// description: filter restricted users
|
||||
// type: boolean
|
||||
// - name: is_2fa_enabled
|
||||
// in: query
|
||||
// description: filter 2FA enabled users
|
||||
// type: boolean
|
||||
// - name: is_prohibit_login
|
||||
// in: query
|
||||
// description: filter login prohibited users
|
||||
// type: boolean
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/UserList"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
users, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
|
||||
Actor: ctx.Doer,
|
||||
Type: user_model.UserTypeIndividual,
|
||||
LoginName: ctx.FormTrim("login_name"),
|
||||
SourceID: ctx.FormInt64("source_id"),
|
||||
OrderBy: db.SearchOrderByAlphabetically,
|
||||
ListOptions: listOptions,
|
||||
})
|
||||
orderBy := db.SearchOrderByAlphabetically
|
||||
sortMode := ctx.FormString("sort")
|
||||
if len(sortMode) > 0 {
|
||||
sortOrder := ctx.FormString("order")
|
||||
if len(sortOrder) == 0 {
|
||||
sortOrder = "asc"
|
||||
}
|
||||
if searchModeMap, ok := user_model.AdminUserOrderByMap[sortOrder]; ok {
|
||||
if order, ok := searchModeMap[sortMode]; ok {
|
||||
orderBy = order
|
||||
} else {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort mode: \"%s\"", sortMode))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid sort order: \"%s\"", sortOrder))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var visible []api.VisibleType
|
||||
visibilityParam := ctx.FormString("visibility")
|
||||
if len(visibilityParam) > 0 {
|
||||
if visibility, ok := api.VisibilityModes[visibilityParam]; ok {
|
||||
visible = []api.VisibleType{visibility}
|
||||
} else {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("Invalid visibility: \"%s\"", visibilityParam))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
searchOpts := user_model.SearchUserOptions{
|
||||
Actor: ctx.Doer,
|
||||
Types: []user_model.UserType{user_model.UserTypeIndividual},
|
||||
LoginName: ctx.FormTrim("login_name"),
|
||||
SourceID: ctx.FormInt64("source_id"),
|
||||
Keyword: ctx.FormTrim("q"),
|
||||
Visible: visible,
|
||||
OrderBy: orderBy,
|
||||
ListOptions: listOptions,
|
||||
SearchByEmail: true,
|
||||
}
|
||||
|
||||
if ctx.FormString("is_active") != "" {
|
||||
searchOpts.IsActive = optional.Some(ctx.FormBool("is_active"))
|
||||
}
|
||||
if ctx.FormString("is_admin") != "" {
|
||||
searchOpts.IsAdmin = optional.Some(ctx.FormBool("is_admin"))
|
||||
}
|
||||
if ctx.FormString("is_restricted") != "" {
|
||||
searchOpts.IsRestricted = optional.Some(ctx.FormBool("is_restricted"))
|
||||
}
|
||||
if ctx.FormString("is_2fa_enabled") != "" {
|
||||
searchOpts.IsTwoFactorEnabled = optional.Some(ctx.FormBool("is_2fa_enabled"))
|
||||
}
|
||||
if ctx.FormString("is_prohibit_login") != "" {
|
||||
searchOpts.IsProhibitLogin = optional.Some(ctx.FormBool("is_prohibit_login"))
|
||||
}
|
||||
|
||||
users, maxResults, err := user_model.SearchUsers(ctx, searchOpts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -440,7 +534,7 @@ func SearchUsers(ctx *context.APIContext) {
|
||||
results[i] = convert.ToUser(ctx, users[i], ctx.Doer)
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
|
||||
ctx.SetLinkHeader(maxResults, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(maxResults)
|
||||
ctx.JSON(http.StatusOK, &results)
|
||||
}
|
||||
@@ -479,7 +573,7 @@ func RenameUser(ctx *context.APIContext) {
|
||||
newName := web.GetForm(ctx).(*api.RenameUserOption).NewName
|
||||
|
||||
// Check if username has been changed
|
||||
if err := user_service.RenameUser(ctx, ctx.ContextUser, newName); err != nil {
|
||||
if err := user_service.RenameUser(ctx, ctx.ContextUser, newName, ctx.Doer); err != nil {
|
||||
if user_model.IsErrUserAlreadyExist(err) || db.IsErrNameReserved(err) || db.IsErrNamePatternNotAllowed(err) || db.IsErrNameCharsNotAllowed(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
} else {
|
||||
|
||||
@@ -11,11 +11,9 @@
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
// - text/plain
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
// - text/html
|
||||
//
|
||||
// Security:
|
||||
// - BasicAuth :
|
||||
@@ -70,7 +68,6 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
@@ -78,7 +75,6 @@ import (
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
@@ -189,29 +185,12 @@ func repoAssignment() func(ctx *context.APIContext) {
|
||||
repo.Owner = owner
|
||||
ctx.Repo.Repository = repo
|
||||
|
||||
if ctx.Doer != nil && ctx.Doer.ID == user_model.ActionsUserID {
|
||||
taskID := ctx.Data["ActionsTaskID"].(int64)
|
||||
task, err := actions_model.GetTaskByID(ctx, taskID)
|
||||
if taskID, ok := user_model.GetActionsUserTaskID(ctx.Doer); ok {
|
||||
ctx.Repo.Permission, err = access_model.GetActionsUserRepoPermission(ctx, repo, ctx.Doer, taskID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if task.RepoID != repo.ID {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if task.IsForkPullRequest {
|
||||
ctx.Repo.Permission.AccessMode = perm.AccessModeRead
|
||||
} else {
|
||||
ctx.Repo.Permission.AccessMode = perm.AccessModeWrite
|
||||
}
|
||||
|
||||
if err := ctx.Repo.Repository.LoadUnits(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.Permission.SetUnitsWithDefaultAccessMode(ctx.Repo.Repository.Units, ctx.Repo.Permission.AccessMode)
|
||||
} else {
|
||||
needTwoFactor, err := doerNeedTwoFactorAuth(ctx, ctx.Doer)
|
||||
if err != nil {
|
||||
@@ -221,7 +200,7 @@ func repoAssignment() func(ctx *context.APIContext) {
|
||||
if needTwoFactor {
|
||||
ctx.Repo.Permission = access_model.PermissionNoAccess()
|
||||
} else {
|
||||
ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||
ctx.Repo.Permission, err = access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -233,6 +212,11 @@ func repoAssignment() func(ctx *context.APIContext) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.TokenCanAccessRepo(repo) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,51 +254,66 @@ func checkTokenPublicOnly() func(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
// public Only permission check
|
||||
switch {
|
||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository):
|
||||
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
|
||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public repos")
|
||||
return
|
||||
}
|
||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryIssue):
|
||||
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
|
||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public issues")
|
||||
return
|
||||
}
|
||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization):
|
||||
if ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic {
|
||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public orgs")
|
||||
return
|
||||
}
|
||||
if ctx.ContextUser != nil && ctx.ContextUser.IsOrganization() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
|
||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public orgs")
|
||||
return
|
||||
}
|
||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryUser):
|
||||
if ctx.ContextUser != nil && ctx.ContextUser.IsTokenAccessAllowed() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
|
||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public users")
|
||||
return
|
||||
}
|
||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryActivityPub):
|
||||
if ctx.ContextUser != nil && ctx.ContextUser.IsTokenAccessAllowed() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
|
||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public activitypub")
|
||||
return
|
||||
}
|
||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryNotification):
|
||||
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
|
||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public notifications")
|
||||
return
|
||||
}
|
||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryPackage):
|
||||
if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
|
||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public packages")
|
||||
return
|
||||
for _, category := range requiredScopeCategories {
|
||||
switch category {
|
||||
case auth_model.AccessTokenScopeCategoryRepository:
|
||||
if !ctx.TokenCanAccessRepo(ctx.Repo.Repository) {
|
||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public repos")
|
||||
return
|
||||
}
|
||||
case auth_model.AccessTokenScopeCategoryIssue:
|
||||
if !ctx.TokenCanAccessRepo(ctx.Repo.Repository) {
|
||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public issues")
|
||||
return
|
||||
}
|
||||
case auth_model.AccessTokenScopeCategoryOrganization:
|
||||
orgPrivate := ctx.Org.Organization != nil && !ctx.Org.Organization.Visibility.IsPublic()
|
||||
userOrgPrivate := ctx.ContextUser != nil && ctx.ContextUser.IsOrganization() && !ctx.ContextUser.Visibility.IsPublic()
|
||||
if orgPrivate || userOrgPrivate {
|
||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public orgs")
|
||||
return
|
||||
}
|
||||
case auth_model.AccessTokenScopeCategoryUser:
|
||||
if ctx.ContextUser != nil && ctx.ContextUser.IsTokenAccessAllowed() && !ctx.ContextUser.Visibility.IsPublic() {
|
||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public users")
|
||||
return
|
||||
}
|
||||
case auth_model.AccessTokenScopeCategoryActivityPub:
|
||||
if ctx.ContextUser != nil && ctx.ContextUser.IsTokenAccessAllowed() && !ctx.ContextUser.Visibility.IsPublic() {
|
||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public activitypub")
|
||||
return
|
||||
}
|
||||
case auth_model.AccessTokenScopeCategoryNotification:
|
||||
if !ctx.TokenCanAccessRepo(ctx.Repo.Repository) {
|
||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public notifications")
|
||||
return
|
||||
}
|
||||
case auth_model.AccessTokenScopeCategoryPackage:
|
||||
if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
|
||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public packages")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func rejectPublicOnly() func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
if !ctx.PublicOnly {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.APIError(http.StatusForbidden, "this endpoint is not available for public-only tokens")
|
||||
}
|
||||
}
|
||||
|
||||
func contextAuthenticatedUser() func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
ctx.ContextUser = ctx.Doer
|
||||
}
|
||||
}
|
||||
|
||||
// if a token is being used for auth, we check that it contains the required scope
|
||||
// if a token is not being used, reqToken will enforce other sign in methods
|
||||
func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeCategory) func(ctx *context.APIContext) {
|
||||
@@ -366,11 +365,7 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
|
||||
// Contexter middleware already checks token for user sign in process.
|
||||
func reqToken() func(ctx *context.APIContext) {
|
||||
return func(ctx *context.APIContext) {
|
||||
// If actions token is present
|
||||
if true == ctx.Data["IsActionsToken"] {
|
||||
return
|
||||
}
|
||||
|
||||
// if a real user is signed in, or the user is from a Actions task, we are good
|
||||
if ctx.IsSigned {
|
||||
return
|
||||
}
|
||||
@@ -778,13 +773,9 @@ func buildAuthGroup() *auth.Group {
|
||||
&auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API
|
||||
)
|
||||
if setting.Service.EnableReverseProxyAuthAPI {
|
||||
group.Add(&auth.ReverseProxy{})
|
||||
group.Add(&auth.ReverseProxy{}) // TODO: does it still make sense to support reverse proxy auth in API?
|
||||
}
|
||||
|
||||
if setting.IsWindows && auth_model.IsSSPIEnabled(graceful.GetManager().ShutdownContext()) {
|
||||
group.Add(&auth.SSPI{}) // it MUST be the last, see the comment of SSPI
|
||||
}
|
||||
|
||||
// others: API doesn't support SSPI auth because the caller should use token
|
||||
return group
|
||||
}
|
||||
|
||||
@@ -894,9 +885,9 @@ func checkDeprecatedAuthMethods(ctx *context.APIContext) {
|
||||
func Routes() *web.Router {
|
||||
m := web.NewRouter()
|
||||
|
||||
m.Use(securityHeaders())
|
||||
m.BeforeRouting(securityHeaders())
|
||||
if setting.CORSConfig.Enabled {
|
||||
m.Use(cors.Handler(cors.Options{
|
||||
m.BeforeRouting(cors.Handler(cors.Options{
|
||||
AllowedOrigins: setting.CORSConfig.AllowDomain,
|
||||
AllowedMethods: setting.CORSConfig.Methods,
|
||||
AllowCredentials: setting.CORSConfig.AllowCredentials,
|
||||
@@ -904,48 +895,49 @@ func Routes() *web.Router {
|
||||
MaxAge: int(setting.CORSConfig.MaxAge.Seconds()),
|
||||
}))
|
||||
}
|
||||
m.Use(context.APIContexter())
|
||||
|
||||
m.Use(checkDeprecatedAuthMethods)
|
||||
m.AfterRouting(context.APIContexter())
|
||||
m.AfterRouting(checkDeprecatedAuthMethods)
|
||||
|
||||
// Get user from session if logged in.
|
||||
m.Use(apiAuth(buildAuthGroup()))
|
||||
m.AfterRouting(apiAuth(buildAuthGroup()))
|
||||
|
||||
m.Use(verifyAuthWithOptions(&common.VerifyOptions{
|
||||
m.AfterRouting(verifyAuthWithOptions(&common.VerifyOptions{
|
||||
SignInRequired: setting.Service.RequireSignInViewStrict,
|
||||
}))
|
||||
|
||||
addActionsRoutes := func(
|
||||
m *web.Router,
|
||||
reqChecker func(ctx *context.APIContext),
|
||||
reqReaderCheck func(ctx *context.APIContext),
|
||||
reqOwnerCheck func(ctx *context.APIContext),
|
||||
act actions.API,
|
||||
) {
|
||||
m.Group("/actions", func() {
|
||||
m.Group("/secrets", func() {
|
||||
m.Get("", reqToken(), reqChecker, act.ListActionsSecrets)
|
||||
m.Get("", reqToken(), reqOwnerCheck, act.ListActionsSecrets)
|
||||
m.Combo("/{secretname}").
|
||||
Put(reqToken(), reqChecker, bind(api.CreateOrUpdateSecretOption{}), act.CreateOrUpdateSecret).
|
||||
Delete(reqToken(), reqChecker, act.DeleteSecret)
|
||||
Put(reqToken(), reqOwnerCheck, bind(api.CreateOrUpdateSecretOption{}), act.CreateOrUpdateSecret).
|
||||
Delete(reqToken(), reqOwnerCheck, act.DeleteSecret)
|
||||
})
|
||||
|
||||
m.Group("/variables", func() {
|
||||
m.Get("", reqToken(), reqChecker, act.ListVariables)
|
||||
m.Get("", reqToken(), reqOwnerCheck, act.ListVariables)
|
||||
m.Combo("/{variablename}").
|
||||
Get(reqToken(), reqChecker, act.GetVariable).
|
||||
Delete(reqToken(), reqChecker, act.DeleteVariable).
|
||||
Post(reqToken(), reqChecker, bind(api.CreateVariableOption{}), act.CreateVariable).
|
||||
Put(reqToken(), reqChecker, bind(api.UpdateVariableOption{}), act.UpdateVariable)
|
||||
Get(reqToken(), reqOwnerCheck, act.GetVariable).
|
||||
Delete(reqToken(), reqOwnerCheck, act.DeleteVariable).
|
||||
Post(reqToken(), reqOwnerCheck, bind(api.CreateVariableOption{}), act.CreateVariable).
|
||||
Put(reqToken(), reqOwnerCheck, bind(api.UpdateVariableOption{}), act.UpdateVariable)
|
||||
})
|
||||
|
||||
m.Group("/runners", func() {
|
||||
m.Get("", reqToken(), reqChecker, act.ListRunners)
|
||||
m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken)
|
||||
m.Post("/registration-token", reqToken(), reqChecker, act.CreateRegistrationToken)
|
||||
m.Get("/{runner_id}", reqToken(), reqChecker, act.GetRunner)
|
||||
m.Delete("/{runner_id}", reqToken(), reqChecker, act.DeleteRunner)
|
||||
m.Get("", reqToken(), reqOwnerCheck, act.ListRunners)
|
||||
m.Post("/registration-token", reqToken(), reqOwnerCheck, act.CreateRegistrationToken)
|
||||
m.Get("/{runner_id}", reqToken(), reqOwnerCheck, act.GetRunner)
|
||||
m.Delete("/{runner_id}", reqToken(), reqOwnerCheck, act.DeleteRunner)
|
||||
m.Patch("/{runner_id}", reqToken(), reqOwnerCheck, bind(api.EditActionRunnerOption{}), act.UpdateRunner)
|
||||
})
|
||||
m.Get("/runs", reqToken(), reqChecker, act.ListWorkflowRuns)
|
||||
m.Get("/jobs", reqToken(), reqChecker, act.ListWorkflowJobs)
|
||||
m.Get("/runs", reqToken(), reqReaderCheck, act.ListWorkflowRuns)
|
||||
m.Get("/jobs", reqToken(), reqReaderCheck, act.ListWorkflowJobs)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -958,18 +950,8 @@ func Routes() *web.Router {
|
||||
}
|
||||
|
||||
if setting.Federation.Enabled {
|
||||
m.Get("/nodeinfo", misc.NodeInfo)
|
||||
m.Group("/activitypub", func() {
|
||||
// deprecated, remove in 1.20, use /user-id/{user-id} instead
|
||||
m.Group("/user/{username}", func() {
|
||||
m.Get("", activitypub.Person)
|
||||
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
|
||||
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
|
||||
m.Group("/user-id/{user-id}", func() {
|
||||
m.Get("", activitypub.Person)
|
||||
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
|
||||
}, context.UserIDAssignmentAPI(), checkTokenPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub))
|
||||
m.Get("/nodeinfo", activitypub.NotImplemented)
|
||||
m.Any("/activitypub/*", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub), activitypub.NotImplemented)
|
||||
}
|
||||
|
||||
// Misc (public accessible)
|
||||
@@ -996,6 +978,8 @@ func Routes() *web.Router {
|
||||
})
|
||||
|
||||
// Notifications (requires 'notifications' scope)
|
||||
// The notifications API is not available for public-only tokens because a user's notifications mix
|
||||
// public and private repository events in the same mailbox.
|
||||
m.Group("/notifications", func() {
|
||||
m.Combo("").
|
||||
Get(reqToken(), notify.ListNotifications).
|
||||
@@ -1004,7 +988,7 @@ func Routes() *web.Router {
|
||||
m.Combo("/threads/{id}").
|
||||
Get(reqToken(), notify.GetThread).
|
||||
Patch(reqToken(), notify.ReadThread)
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification))
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification), rejectPublicOnly())
|
||||
|
||||
// Users (requires user scope)
|
||||
m.Group("/users", func() {
|
||||
@@ -1052,8 +1036,9 @@ func Routes() *web.Router {
|
||||
m.Group("/settings", func() {
|
||||
m.Get("", user.GetUserSettings)
|
||||
m.Patch("", bind(api.UserSettingsOptions{}), user.UpdateUserSettings)
|
||||
}, reqToken())
|
||||
m.Combo("/emails").
|
||||
}, rejectPublicOnly())
|
||||
// Email addresses are always private account data.
|
||||
m.Combo("/emails", rejectPublicOnly()).
|
||||
Get(user.ListEmails).
|
||||
Post(bind(api.CreateEmailOption{}), user.AddEmail).
|
||||
Delete(bind(api.DeleteEmailOption{}), user.DeleteEmail)
|
||||
@@ -1077,15 +1062,15 @@ func Routes() *web.Router {
|
||||
|
||||
m.Group("/runners", func() {
|
||||
m.Get("", reqToken(), user.ListRunners)
|
||||
m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
|
||||
m.Post("/registration-token", reqToken(), user.CreateRegistrationToken)
|
||||
m.Get("/{runner_id}", reqToken(), user.GetRunner)
|
||||
m.Delete("/{runner_id}", reqToken(), user.DeleteRunner)
|
||||
m.Patch("/{runner_id}", reqToken(), bind(api.EditActionRunnerOption{}), user.UpdateRunner)
|
||||
})
|
||||
|
||||
m.Get("/runs", reqToken(), user.ListWorkflowRuns)
|
||||
m.Get("/jobs", reqToken(), user.ListWorkflowJobs)
|
||||
})
|
||||
}, rejectPublicOnly())
|
||||
|
||||
m.Get("/followers", user.ListMyFollowers)
|
||||
m.Group("/following", func() {
|
||||
@@ -1103,7 +1088,7 @@ func Routes() *web.Router {
|
||||
Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
|
||||
m.Combo("/{id}").Get(user.GetPublicKey).
|
||||
Delete(user.DeletePublicKey)
|
||||
})
|
||||
}, rejectPublicOnly())
|
||||
|
||||
// (admin:application scope)
|
||||
m.Group("/applications", func() {
|
||||
@@ -1114,7 +1099,7 @@ func Routes() *web.Router {
|
||||
Delete(user.DeleteOauth2Application).
|
||||
Patch(bind(api.CreateOAuth2ApplicationOptions{}), user.UpdateOauth2Application).
|
||||
Get(user.GetOauth2Application)
|
||||
})
|
||||
}, rejectPublicOnly())
|
||||
|
||||
// (admin:gpg_key scope)
|
||||
m.Group("/gpg_keys", func() {
|
||||
@@ -1122,13 +1107,13 @@ func Routes() *web.Router {
|
||||
Post(bind(api.CreateGPGKeyOption{}), user.CreateGPGKey)
|
||||
m.Combo("/{id}").Get(user.GetGPGKey).
|
||||
Delete(user.DeleteGPGKey)
|
||||
})
|
||||
m.Get("/gpg_key_token", user.GetVerificationToken)
|
||||
m.Post("/gpg_key_verify", bind(api.VerifyGPGKeyOption{}), user.VerifyUserGPGKey)
|
||||
}, rejectPublicOnly())
|
||||
m.Get("/gpg_key_token", rejectPublicOnly(), user.GetVerificationToken)
|
||||
m.Post("/gpg_key_verify", rejectPublicOnly(), bind(api.VerifyGPGKeyOption{}), user.VerifyUserGPGKey)
|
||||
|
||||
// (repo scope)
|
||||
m.Combo("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(user.ListMyRepos).
|
||||
Post(bind(api.CreateRepoOption{}), repo.Create)
|
||||
Post(rejectPublicOnly(), bind(api.CreateRepoOption{}), repo.Create)
|
||||
|
||||
// (repo scope)
|
||||
m.Group("/starred", func() {
|
||||
@@ -1139,22 +1124,22 @@ func Routes() *web.Router {
|
||||
m.Delete("", user.Unstar)
|
||||
}, repoAssignment(), checkTokenPublicOnly())
|
||||
}, reqStarsEnabled(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
||||
m.Get("/times", repo.ListMyTrackedTimes)
|
||||
m.Get("/stopwatches", repo.GetStopwatches)
|
||||
m.Get("/times", rejectPublicOnly(), repo.ListMyTrackedTimes)
|
||||
m.Get("/stopwatches", rejectPublicOnly(), repo.GetStopwatches)
|
||||
m.Get("/subscriptions", user.GetMyWatchedRepos)
|
||||
m.Get("/teams", org.ListUserTeams)
|
||||
m.Get("/teams", rejectPublicOnly(), org.ListUserTeams)
|
||||
m.Group("/hooks", func() {
|
||||
m.Combo("").Get(user.ListHooks).
|
||||
Post(bind(api.CreateHookOption{}), user.CreateHook)
|
||||
m.Combo("/{id}").Get(user.GetHook).
|
||||
Patch(bind(api.EditHookOption{}), user.EditHook).
|
||||
Delete(user.DeleteHook)
|
||||
}, reqWebhooksEnabled())
|
||||
}, reqWebhooksEnabled(), rejectPublicOnly())
|
||||
|
||||
m.Group("/avatar", func() {
|
||||
m.Post("", bind(api.UpdateUserAvatarOption{}), user.UpdateAvatar)
|
||||
m.Delete("", user.DeleteAvatar)
|
||||
})
|
||||
}, rejectPublicOnly())
|
||||
|
||||
m.Group("/blocks", func() {
|
||||
m.Get("", user.ListBlocks)
|
||||
@@ -1163,8 +1148,8 @@ func Routes() *web.Router {
|
||||
m.Put("", user.BlockUser)
|
||||
m.Delete("", user.UnblockUser)
|
||||
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
|
||||
})
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
|
||||
}, rejectPublicOnly())
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken(), contextAuthenticatedUser(), checkTokenPublicOnly())
|
||||
|
||||
// Repositories (requires repo scope, org scope)
|
||||
m.Post("/org/{org}/repos",
|
||||
@@ -1198,7 +1183,8 @@ func Routes() *web.Router {
|
||||
m.Post("/reject", repo.RejectTransfer)
|
||||
}, reqToken())
|
||||
|
||||
addActionsRoutes(m, reqOwner(), repo.NewAction()) // it adds the routes for secrets/variables and runner management
|
||||
// Adds the routes for secrets/variables and runner management
|
||||
addActionsRoutes(m, reqRepoReader(unit.TypeActions), reqOwner(), repo.NewAction())
|
||||
|
||||
m.Group("/actions/workflows", func() {
|
||||
m.Get("", repo.ActionsListRepositoryWorkflows)
|
||||
@@ -1259,15 +1245,16 @@ func Routes() *web.Router {
|
||||
m.Get("/*", repo.GetBranch)
|
||||
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch)
|
||||
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), repo.CreateBranch)
|
||||
m.Put("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.UpdateBranchRepoOption{}), repo.UpdateBranch)
|
||||
m.Patch("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.RenameBranchRepoOption{}), repo.RenameBranch)
|
||||
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
|
||||
m.Group("/branch_protections", func() {
|
||||
m.Get("", repo.ListBranchProtections)
|
||||
m.Post("", bind(api.CreateBranchProtectionOption{}), mustNotBeArchived, repo.CreateBranchProtection)
|
||||
m.Group("/{name}", func() {
|
||||
m.Group("/*", func() {
|
||||
m.Get("", repo.GetBranchProtection)
|
||||
m.Patch("", bind(api.EditBranchProtectionOption{}), mustNotBeArchived, repo.EditBranchProtection)
|
||||
m.Delete("", repo.DeleteBranchProtection)
|
||||
m.Delete("", mustNotBeArchived, repo.DeleteBranchProtection)
|
||||
})
|
||||
m.Post("/priority", bind(api.UpdateBranchProtectionPriories{}), mustNotBeArchived, repo.UpdateBranchProtectionPriories)
|
||||
}, reqToken(), reqAdmin())
|
||||
@@ -1292,7 +1279,10 @@ func Routes() *web.Router {
|
||||
m.Group("/{run}", func() {
|
||||
m.Get("", repo.GetWorkflowRun)
|
||||
m.Delete("", reqToken(), reqRepoWriter(unit.TypeActions), repo.DeleteActionRun)
|
||||
m.Post("/rerun", reqToken(), reqRepoWriter(unit.TypeActions), repo.RerunWorkflowRun)
|
||||
m.Post("/rerun-failed-jobs", reqToken(), reqRepoWriter(unit.TypeActions), repo.RerunFailedWorkflowRun)
|
||||
m.Get("/jobs", repo.ListWorkflowRunJobs)
|
||||
m.Post("/jobs/{job_id}/rerun", reqToken(), reqRepoWriter(unit.TypeActions), repo.RerunWorkflowJob)
|
||||
m.Get("/artifacts", repo.GetArtifactsOfRun)
|
||||
})
|
||||
})
|
||||
@@ -1369,6 +1359,8 @@ func Routes() *web.Router {
|
||||
m.Combo("").Get(repo.ListPullRequests).
|
||||
Post(reqToken(), mustNotBeArchived, bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)
|
||||
m.Get("/pinned", repo.ListPinnedPullRequests)
|
||||
m.Post("/comments/{id}/resolve", reqToken(), mustNotBeArchived, repo.ResolvePullReviewComment)
|
||||
m.Post("/comments/{id}/unresolve", reqToken(), mustNotBeArchived, repo.UnresolvePullReviewComment)
|
||||
m.Group("/{index}", func() {
|
||||
m.Combo("").Get(repo.GetPullRequest).
|
||||
Patch(reqToken(), bind(api.EditPullRequestOption{}), repo.EditPullRequest)
|
||||
@@ -1457,9 +1449,9 @@ func Routes() *web.Router {
|
||||
Delete(reqToken(), repo.DeleteTopic)
|
||||
}, reqAdmin())
|
||||
}, reqAnyRepoReader())
|
||||
m.Get("/issue_templates", context.ReferencesGitRepo(), repo.GetIssueTemplates)
|
||||
m.Get("/issue_config", context.ReferencesGitRepo(), repo.GetIssueConfig)
|
||||
m.Get("/issue_config/validate", context.ReferencesGitRepo(), repo.ValidateIssueConfig)
|
||||
m.Get("/issue_templates", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(), repo.GetIssueTemplates)
|
||||
m.Get("/issue_config", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(), repo.GetIssueConfig)
|
||||
m.Get("/issue_config/validate", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(), repo.ValidateIssueConfig)
|
||||
m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages)
|
||||
m.Get("/licenses", reqRepoReader(unit.TypeCode), repo.GetLicenses)
|
||||
m.Get("/activities/feeds", repo.ListRepoActivityFeeds)
|
||||
@@ -1609,10 +1601,11 @@ func Routes() *web.Router {
|
||||
m.Group("/packages/{username}", func() {
|
||||
m.Group("/{type}/{name}", func() {
|
||||
m.Get("/", packages.ListPackageVersions)
|
||||
m.Delete("", reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage)
|
||||
|
||||
m.Group("/{version}", func() {
|
||||
m.Get("", packages.GetPackage)
|
||||
m.Delete("", reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage)
|
||||
m.Delete("", reqPackageAccess(perm.AccessModeWrite), packages.DeletePackageVersion)
|
||||
m.Get("/files", packages.ListPackageFiles)
|
||||
})
|
||||
|
||||
@@ -1627,7 +1620,7 @@ func Routes() *web.Router {
|
||||
}, reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead), checkTokenPublicOnly())
|
||||
|
||||
// Organizations
|
||||
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
|
||||
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), checkTokenPublicOnly(), org.ListMyOrgs)
|
||||
m.Group("/users/{username}/orgs", func() {
|
||||
m.Get("", reqToken(), org.ListUserOrgs)
|
||||
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
|
||||
@@ -1648,6 +1641,7 @@ func Routes() *web.Router {
|
||||
})
|
||||
addActionsRoutes(
|
||||
m,
|
||||
reqOrgMembership(),
|
||||
reqOrgOwnership(),
|
||||
org.NewAction(),
|
||||
)
|
||||
@@ -1759,13 +1753,11 @@ func Routes() *web.Router {
|
||||
m.Post("/registration-token", admin.CreateRegistrationToken)
|
||||
m.Get("/{runner_id}", admin.GetRunner)
|
||||
m.Delete("/{runner_id}", admin.DeleteRunner)
|
||||
m.Patch("/{runner_id}", bind(api.EditActionRunnerOption{}), admin.UpdateRunner)
|
||||
})
|
||||
m.Get("/runs", admin.ListWorkflowRuns)
|
||||
m.Get("/jobs", admin.ListWorkflowJobs)
|
||||
})
|
||||
m.Group("/runners", func() {
|
||||
m.Get("/registration-token", admin.GetRegistrationToken)
|
||||
})
|
||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryAdmin), reqToken(), reqSiteAdmin())
|
||||
|
||||
m.Group("/topics", func() {
|
||||
|
||||
@@ -173,8 +173,8 @@ Here are some links to the most important topics. You can find the full list of
|
||||
<a href="http://localhost:3000/user2/repo1/src/branch/main/path/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/user2/repo1/media/branch/main/path/image.png" alt="Image"/></a></p>
|
||||
`, http.StatusOK)
|
||||
|
||||
testRenderMarkup(t, "file", false, "path/test.unknown", "## Test", "unsupported file to render: \"path/test.unknown\"\n", http.StatusUnprocessableEntity)
|
||||
testRenderMarkup(t, "unknown", false, "", "## Test", "Unknown mode: unknown\n", http.StatusUnprocessableEntity)
|
||||
testRenderMarkup(t, "file", false, "path/test.unknown", "## Test", "unable to find a render\n", http.StatusUnprocessableEntity)
|
||||
testRenderMarkup(t, "unknown", false, "", "## Test", "unsupported render mode: unknown\n", http.StatusUnprocessableEntity)
|
||||
}
|
||||
|
||||
var simpleCases = []string{
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package misc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
const cacheKeyNodeInfoUsage = "API_NodeInfoUsage"
|
||||
|
||||
// NodeInfo returns the NodeInfo for the Gitea instance to allow for federation
|
||||
func NodeInfo(ctx *context.APIContext) {
|
||||
// swagger:operation GET /nodeinfo miscellaneous getNodeInfo
|
||||
// ---
|
||||
// summary: Returns the nodeinfo of the Gitea application
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/NodeInfo"
|
||||
|
||||
nodeInfoUsage := structs.NodeInfoUsage{}
|
||||
if setting.Federation.ShareUserStatistics {
|
||||
cached, _ := ctx.Cache.GetJSON(cacheKeyNodeInfoUsage, &nodeInfoUsage)
|
||||
if !cached {
|
||||
usersTotal := int(user_model.CountUsers(ctx, nil))
|
||||
now := time.Now()
|
||||
timeOneMonthAgo := now.AddDate(0, -1, 0).Unix()
|
||||
timeHaveYearAgo := now.AddDate(0, -6, 0).Unix()
|
||||
usersActiveMonth := int(user_model.CountUsers(ctx, &user_model.CountUserFilter{LastLoginSince: &timeOneMonthAgo}))
|
||||
usersActiveHalfyear := int(user_model.CountUsers(ctx, &user_model.CountUserFilter{LastLoginSince: &timeHaveYearAgo}))
|
||||
|
||||
allIssues, _ := issues_model.CountIssues(ctx, &issues_model.IssuesOptions{})
|
||||
allComments, _ := issues_model.CountComments(ctx, &issues_model.FindCommentsOptions{})
|
||||
|
||||
nodeInfoUsage = structs.NodeInfoUsage{
|
||||
Users: structs.NodeInfoUsageUsers{
|
||||
Total: usersTotal,
|
||||
ActiveMonth: usersActiveMonth,
|
||||
ActiveHalfyear: usersActiveHalfyear,
|
||||
},
|
||||
LocalPosts: int(allIssues),
|
||||
LocalComments: int(allComments),
|
||||
}
|
||||
|
||||
if err := ctx.Cache.PutJSON(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodeInfo := &structs.NodeInfo{
|
||||
Version: "2.1",
|
||||
Software: structs.NodeInfoSoftware{
|
||||
Name: "gitea",
|
||||
Version: setting.AppVer,
|
||||
Repository: "https://github.com/go-gitea/gitea.git",
|
||||
Homepage: "https://gitea.io/",
|
||||
},
|
||||
Protocols: []string{"activitypub"},
|
||||
Services: structs.NodeInfoServices{
|
||||
Inbound: []string{},
|
||||
Outbound: []string{"rss2.0"},
|
||||
},
|
||||
OpenRegistrations: setting.Service.ShowRegistrationButton,
|
||||
Usage: nodeInfoUsage,
|
||||
}
|
||||
ctx.JSON(http.StatusOK, nodeInfo)
|
||||
}
|
||||
@@ -10,13 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func getSigningKey(ctx *context.APIContext, expectedFormat string) {
|
||||
// if the handler is in the repo's route group, get the repo's signing key
|
||||
// otherwise, get the global signing key
|
||||
path := ""
|
||||
if ctx.Repo != nil && ctx.Repo.Repository != nil {
|
||||
path = ctx.Repo.Repository.RepoPath()
|
||||
}
|
||||
content, format, err := asymkey_service.PublicSigningKey(ctx, path)
|
||||
// get the global signing key
|
||||
content, format, err := asymkey_service.PublicSigningKey(ctx)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
|
||||
@@ -125,8 +125,8 @@ func ListRepoNotifications(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(totalCount, opts.PageSize)
|
||||
ctx.SetTotalCountHeader(totalCount)
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToNotifications(ctx, nl))
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ func ListNotifications(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(totalCount, opts.PageSize)
|
||||
ctx.SetTotalCountHeader(totalCount)
|
||||
ctx.JSON(http.StatusOK, convert.ToNotifications(ctx, nl))
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, opts.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, apiSecrets)
|
||||
}
|
||||
@@ -169,27 +170,6 @@ func (Action) DeleteSecret(ctx *context.APIContext) {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
|
||||
// GetRegistrationToken returns the token to register org runners
|
||||
func (Action) GetRegistrationToken(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/actions/runners/registration-token organization orgGetRunnerRegistrationToken
|
||||
// ---
|
||||
// summary: Get an organization's actions runner registration token
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RegistrationToken"
|
||||
|
||||
shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
|
||||
}
|
||||
|
||||
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
|
||||
// CreateRegistrationToken returns the token to register org runners
|
||||
func (Action) CreateRegistrationToken(ctx *context.APIContext) {
|
||||
@@ -240,9 +220,10 @@ func (Action) ListVariables(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
|
||||
OwnerID: ctx.Org.Organization.ID,
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
ListOptions: listOptions,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -259,7 +240,7 @@ func (Action) ListVariables(ctx *context.APIContext) {
|
||||
Description: v.Description,
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, variables)
|
||||
}
|
||||
@@ -504,9 +485,14 @@ func (Action) ListRunners(ctx *context.APIContext) {
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: disabled
|
||||
// in: query
|
||||
// description: filter by disabled status (true or false)
|
||||
// type: boolean
|
||||
// required: false
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/definitions/ActionRunnersResponse"
|
||||
// "$ref": "#/responses/RunnerList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
@@ -534,7 +520,7 @@ func (Action) GetRunner(ctx *context.APIContext) {
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/definitions/ActionRunner"
|
||||
// "$ref": "#/responses/Runner"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
@@ -570,6 +556,42 @@ func (Action) DeleteRunner(ctx *context.APIContext) {
|
||||
shared.DeleteRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
// UpdateRunner update an org-level runner
|
||||
func (Action) UpdateRunner(ctx *context.APIContext) {
|
||||
// swagger:operation PATCH /orgs/{org}/actions/runners/{runner_id} organization updateOrgRunner
|
||||
// ---
|
||||
// summary: Update an org-level runner
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/EditActionRunnerOption"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Runner"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
shared.UpdateRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
func (Action) ListWorkflowJobs(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/actions/jobs organization getOrgWorkflowJobs
|
||||
// ---
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
webhook_service "code.gitea.io/gitea/services/webhook"
|
||||
)
|
||||
|
||||
// ListHooks list an organziation's webhooks
|
||||
// ListHooks list an organization's webhooks
|
||||
func ListHooks(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/hooks organization orgListHooks
|
||||
// ---
|
||||
|
||||
@@ -20,11 +20,12 @@ import (
|
||||
|
||||
// listMembers list an organization's members
|
||||
func listMembers(ctx *context.APIContext, isMember bool) {
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
opts := &organization.FindOrgMembersOpts{
|
||||
Doer: ctx.Doer,
|
||||
IsDoerMember: isMember,
|
||||
OrgID: ctx.Org.Organization.ID,
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
ListOptions: listOptions,
|
||||
}
|
||||
|
||||
count, err := organization.CountOrgMembers(ctx, opts)
|
||||
@@ -44,6 +45,7 @@ func listMembers(ctx *context.APIContext, isMember bool) {
|
||||
apiMembers[i] = convert.ToUser(ctx, member, ctx.Doer)
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, apiMembers)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package org
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/user"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
@@ -31,6 +33,7 @@ func listUserOrgs(ctx *context.APIContext, u *user_model.User) {
|
||||
UserID: u.ID,
|
||||
IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, u),
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
orgs, maxResults, err := db.FindAndCount[organization.Organization](ctx, opts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -42,7 +45,7 @@ func listUserOrgs(ctx *context.APIContext, u *user_model.User) {
|
||||
apiOrgs[i] = convert.ToOrganization(ctx, orgs[i])
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
|
||||
ctx.SetLinkHeader(maxResults, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(maxResults)
|
||||
ctx.JSON(http.StatusOK, &apiOrgs)
|
||||
}
|
||||
@@ -135,7 +138,7 @@ func GetUserOrgsPermissions(ctx *context.APIContext) {
|
||||
|
||||
op := api.OrganizationPermissions{}
|
||||
|
||||
if !organization.HasOrgOrUserVisible(ctx, o, ctx.ContextUser) {
|
||||
if !organization.HasOrgOrUserVisible(ctx, o, ctx.Doer) {
|
||||
ctx.APIErrorNotFound("HasOrgOrUserVisible", nil)
|
||||
return
|
||||
}
|
||||
@@ -190,7 +193,7 @@ func GetAll(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/OrganizationList"
|
||||
|
||||
vMode := []api.VisibleType{api.VisibleTypePublic}
|
||||
if ctx.IsSigned && !ctx.PublicOnly {
|
||||
if ctx.IsSigned {
|
||||
vMode = append(vMode, api.VisibleTypeLimited)
|
||||
if ctx.Doer.IsAdmin {
|
||||
vMode = append(vMode, api.VisibleTypePrivate)
|
||||
@@ -199,13 +202,16 @@ func GetAll(ctx *context.APIContext) {
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
publicOrgs, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
|
||||
searchOpts := user_model.SearchUserOptions{
|
||||
Actor: ctx.Doer,
|
||||
ListOptions: listOptions,
|
||||
Type: user_model.UserTypeOrganization,
|
||||
Types: []user_model.UserType{user_model.UserTypeOrganization},
|
||||
OrderBy: db.SearchOrderByAlphabetically,
|
||||
Visible: vMode,
|
||||
})
|
||||
}
|
||||
searchOpts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
|
||||
publicOrgs, maxResults, err := user_model.SearchUsers(ctx, searchOpts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -215,7 +221,7 @@ func GetAll(ctx *context.APIContext) {
|
||||
orgs[i] = convert.ToOrganization(ctx, organization.OrgFromUser(publicOrgs[i]))
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
|
||||
ctx.SetLinkHeader(maxResults, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(maxResults)
|
||||
ctx.JSON(http.StatusOK, &orgs)
|
||||
}
|
||||
@@ -340,7 +346,7 @@ func Rename(ctx *context.APIContext) {
|
||||
|
||||
form := web.GetForm(ctx).(*api.RenameOrgOption)
|
||||
orgUser := ctx.Org.Organization.AsUser()
|
||||
if err := user_service.RenameUser(ctx, orgUser, form.NewName); err != nil {
|
||||
if err := user_service.RenameUser(ctx, orgUser, form.NewName, ctx.Doer); err != nil {
|
||||
if user_model.IsErrUserAlreadyExist(err) || db.IsErrNameReserved(err) || db.IsErrNamePatternNotAllowed(err) || db.IsErrNameCharsNotAllowed(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
} else {
|
||||
@@ -379,19 +385,21 @@ func Edit(ctx *context.APIContext) {
|
||||
|
||||
form := web.GetForm(ctx).(*api.EditOrgOption)
|
||||
|
||||
if form.Email != "" {
|
||||
if err := user_service.ReplacePrimaryEmailAddress(ctx, ctx.Org.Organization.AsUser(), form.Email); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
if err := org.UpdateOrgEmailAddress(ctx, ctx.Org.Organization, form.Email); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
opts := &user_service.UpdateOptions{
|
||||
FullName: optional.Some(form.FullName),
|
||||
Description: optional.Some(form.Description),
|
||||
Website: optional.Some(form.Website),
|
||||
Location: optional.Some(form.Location),
|
||||
Visibility: optional.FromMapLookup(api.VisibilityModes, form.Visibility),
|
||||
FullName: optional.FromPtr(form.FullName),
|
||||
Description: optional.FromPtr(form.Description),
|
||||
Website: optional.FromPtr(form.Website),
|
||||
Location: optional.FromPtr(form.Location),
|
||||
Visibility: optional.FromMapLookup(api.VisibilityModes, optional.FromPtr(form.Visibility).Value()),
|
||||
RepoAdminChangeTeamAccess: optional.FromPtr(form.RepoAdminChangeTeamAccess),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, ctx.Org.Organization.AsUser(), opts); err != nil {
|
||||
@@ -483,6 +491,7 @@ func ListOrgActivityFeeds(ctx *context.APIContext) {
|
||||
Date: ctx.FormString("date"),
|
||||
ListOptions: listOptions,
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
|
||||
feeds, count, err := feed_service.GetFeeds(ctx, opts)
|
||||
if err != nil {
|
||||
|
||||
@@ -54,8 +54,9 @@ func ListTeams(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
ListOptions: listOptions,
|
||||
OrgID: ctx.Org.Organization.ID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -69,6 +70,7 @@ func ListTeams(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, apiTeams)
|
||||
}
|
||||
@@ -93,8 +95,9 @@ func ListUserTeams(ctx *context.APIContext) {
|
||||
// "200":
|
||||
// "$ref": "#/responses/TeamList"
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
ListOptions: listOptions,
|
||||
UserID: ctx.Doer.ID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -108,6 +111,7 @@ func ListUserTeams(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, apiTeams)
|
||||
}
|
||||
@@ -392,8 +396,9 @@ func GetTeamMembers(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
teamMembers, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
ListOptions: listOptions,
|
||||
TeamID: ctx.Org.Team.ID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -406,6 +411,7 @@ func GetTeamMembers(ctx *context.APIContext) {
|
||||
members[i] = convert.ToUser(ctx, member, ctx.Doer)
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int64(ctx.Org.Team.NumMembers), listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(int64(ctx.Org.Team.NumMembers))
|
||||
ctx.JSON(http.StatusOK, members)
|
||||
}
|
||||
@@ -559,8 +565,9 @@ func GetTeamRepos(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
team := ctx.Org.Team
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
teamRepos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
ListOptions: listOptions,
|
||||
TeamID: team.ID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -569,13 +576,14 @@ func GetTeamRepos(ctx *context.APIContext) {
|
||||
}
|
||||
repos := make([]*api.Repository, len(teamRepos))
|
||||
for i, repo := range teamRepos {
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
repos[i] = convert.ToRepo(ctx, repo, permission)
|
||||
}
|
||||
ctx.SetLinkHeader(int64(team.NumRepos), listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(int64(team.NumRepos))
|
||||
ctx.JSON(http.StatusOK, repos)
|
||||
}
|
||||
@@ -620,7 +628,7 @@ func GetTeamRepo(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -798,7 +806,7 @@ func SearchTeam(ctx *context.APIContext) {
|
||||
ListOptions: listOptions,
|
||||
}
|
||||
|
||||
// Only admin is allowd to search for all teams
|
||||
// Only admin is allowed to search for all teams
|
||||
if !ctx.Doer.IsAdmin {
|
||||
opts.UserID = ctx.Doer.ID
|
||||
}
|
||||
@@ -819,7 +827,7 @@ func SearchTeam(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
|
||||
ctx.SetLinkHeader(maxResults, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(maxResults)
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"ok": true,
|
||||
@@ -874,7 +882,7 @@ func ListTeamActivityFeeds(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToActivities(ctx, feeds, ctx.Doer))
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func ListPackages(ctx *context.APIContext) {
|
||||
// in: query
|
||||
// description: package type filter
|
||||
// type: string
|
||||
// enum: [alpine, cargo, chef, composer, conan, conda, container, cran, debian, generic, go, helm, maven, npm, nuget, pub, pypi, rpm, rubygems, swift, vagrant]
|
||||
// enum: [alpine, cargo, chef, composer, conan, conda, container, cran, debian, generic, go, helm, maven, npm, nuget, pub, pypi, rpm, rubygems, swift, terraform, vagrant]
|
||||
// - name: q
|
||||
// in: query
|
||||
// description: name filter
|
||||
@@ -68,7 +68,7 @@ func ListPackages(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(count), listOptions.PageSize)
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, apiPackages)
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func GetPackage(ctx *context.APIContext) {
|
||||
|
||||
// DeletePackage deletes a package
|
||||
func DeletePackage(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /packages/{owner}/{type}/{name}/{version} package deletePackage
|
||||
// swagger:operation DELETE /packages/{owner}/{type}/{name} package deletePackage
|
||||
// ---
|
||||
// summary: Delete a package
|
||||
// parameters:
|
||||
@@ -137,6 +137,41 @@ func DeletePackage(ctx *context.APIContext) {
|
||||
// description: name of the package
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
err := packages_service.RemovePackage(ctx, ctx.Doer, ctx.Package.Descriptor.Package)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// DeletePackageVersion deletes a package version
|
||||
func DeletePackageVersion(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /packages/{owner}/{type}/{name}/{version} package deletePackageVersion
|
||||
// ---
|
||||
// summary: Delete a package version
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the package
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: type
|
||||
// in: path
|
||||
// description: type of the package
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: name
|
||||
// in: path
|
||||
// description: name of the package
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: version
|
||||
// in: path
|
||||
// description: version of the package
|
||||
@@ -249,7 +284,7 @@ func ListPackageVersions(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(count), listOptions.PageSize)
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, apiPackages)
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ package repo
|
||||
import (
|
||||
go_context "context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
secret_model "code.gitea.io/gitea/models/secret"
|
||||
"code.gitea.io/gitea/modules/actions"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"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"
|
||||
@@ -69,10 +68,11 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
opts := &secret_model.FindSecretsOptions{
|
||||
RepoID: repo.ID,
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
ListOptions: listOptions,
|
||||
}
|
||||
|
||||
secrets, count, err := db.FindAndCount[secret_model.Secret](ctx, opts)
|
||||
@@ -89,7 +89,7 @@ func (Action) ListActionsSecrets(ctx *context.APIContext) {
|
||||
Created: v.CreatedUnix.AsTime(),
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, apiSecrets)
|
||||
}
|
||||
@@ -482,9 +482,11 @@ func (Action) ListVariables(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
ListOptions: listOptions,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -502,35 +504,11 @@ func (Action) ListVariables(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, variables)
|
||||
}
|
||||
|
||||
// GetRegistrationToken returns the token to register repo runners
|
||||
func (Action) GetRegistrationToken(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/runners/registration-token repository repoGetRunnerRegistrationToken
|
||||
// ---
|
||||
// summary: Get a repository's actions runner registration token
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RegistrationToken"
|
||||
|
||||
shared.GetRegistrationToken(ctx, 0, ctx.Repo.Repository.ID)
|
||||
}
|
||||
|
||||
// CreateRegistrationToken returns the token to register repo runners
|
||||
func (Action) CreateRegistrationToken(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/actions/runners/registration-token repository repoCreateRunnerRegistrationToken
|
||||
@@ -574,9 +552,14 @@ func (Action) ListRunners(ctx *context.APIContext) {
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: disabled
|
||||
// in: query
|
||||
// description: filter by disabled status (true or false)
|
||||
// type: boolean
|
||||
// required: false
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/definitions/ActionRunnersResponse"
|
||||
// "$ref": "#/responses/RunnerList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
@@ -584,11 +567,11 @@ func (Action) ListRunners(ctx *context.APIContext) {
|
||||
shared.ListRunners(ctx, 0, ctx.Repo.Repository.ID)
|
||||
}
|
||||
|
||||
// GetRunner get an repo-level runner
|
||||
// GetRunner get a repo-level runner
|
||||
func (Action) GetRunner(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/runners/{runner_id} repository getRepoRunner
|
||||
// ---
|
||||
// summary: Get an repo-level runner
|
||||
// summary: Get a repo-level runner
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
@@ -609,7 +592,7 @@ func (Action) GetRunner(ctx *context.APIContext) {
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/definitions/ActionRunner"
|
||||
// "$ref": "#/responses/Runner"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
@@ -617,11 +600,11 @@ func (Action) GetRunner(ctx *context.APIContext) {
|
||||
shared.GetRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
// DeleteRunner delete an repo-level runner
|
||||
// DeleteRunner delete a repo-level runner
|
||||
func (Action) DeleteRunner(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /repos/{owner}/{repo}/actions/runners/{runner_id} repository deleteRepoRunner
|
||||
// ---
|
||||
// summary: Delete an repo-level runner
|
||||
// summary: Delete a repo-level runner
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
@@ -650,6 +633,47 @@ func (Action) DeleteRunner(ctx *context.APIContext) {
|
||||
shared.DeleteRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
// UpdateRunner update a repo-level runner
|
||||
func (Action) UpdateRunner(ctx *context.APIContext) {
|
||||
// swagger:operation PATCH /repos/{owner}/{repo}/actions/runners/{runner_id} repository updateRepoRunner
|
||||
// ---
|
||||
// summary: Update a repo-level runner
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/EditActionRunnerOption"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Runner"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
shared.UpdateRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
// GetWorkflowRunJobs Lists all jobs for a workflow run.
|
||||
func (Action) ListWorkflowJobs(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/jobs repository listWorkflowJobs
|
||||
@@ -807,9 +831,10 @@ func ListActionTasks(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/conflict"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
tasks, total, err := db.FindAndCount[actions_model.ActionTask](ctx, &actions_model.FindTaskOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
ListOptions: listOptions,
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -830,6 +855,8 @@ func ListActionTasks(ctx *context.APIContext) {
|
||||
res.Entries[i] = convertedTask
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(total, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(total) // Duplicates api response field but it's better to set it for consistency
|
||||
ctx.JSON(http.StatusOK, &res)
|
||||
}
|
||||
|
||||
@@ -997,9 +1024,15 @@ func ActionsDispatchWorkflow(ctx *context.APIContext) {
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateActionWorkflowDispatch"
|
||||
// - name: return_run_details
|
||||
// description: Whether the response should include the workflow run ID and URLs.
|
||||
// in: query
|
||||
// type: boolean
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RunDetails"
|
||||
// "204":
|
||||
// description: No Content
|
||||
// description: No Content, if return_run_details is missing or false
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
@@ -1016,7 +1049,7 @@ func ActionsDispatchWorkflow(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
err := actions_service.DispatchActionWorkflow(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, workflowID, opt.Ref, func(workflowDispatch *model.WorkflowDispatch, inputs map[string]any) error {
|
||||
runID, err := actions_service.DispatchActionWorkflow(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, workflowID, opt.Ref, func(workflowDispatch *model.WorkflowDispatch, inputs map[string]any) error {
|
||||
if strings.Contains(ctx.Req.Header.Get("Content-Type"), "form-urlencoded") {
|
||||
// The chi framework's "Binding" doesn't support to bind the form map values into a map[string]string
|
||||
// So we have to manually read the `inputs[key]` from the form
|
||||
@@ -1047,7 +1080,16 @@ func ActionsDispatchWorkflow(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
if !ctx.FormBool("return_run_details") {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, &api.RunDetails{
|
||||
WorkflowRunID: runID,
|
||||
HTMLURL: fmt.Sprintf("%s/actions/runs/%d", ctx.Repo.Repository.HTMLURL(ctx), runID),
|
||||
RunURL: fmt.Sprintf("%s/actions/runs/%d", ctx.Repo.Repository.APIURL(), runID),
|
||||
})
|
||||
}
|
||||
|
||||
func ActionsEnableWorkflow(ctx *context.APIContext) {
|
||||
@@ -1100,6 +1142,33 @@ func ActionsEnableWorkflow(ctx *context.APIContext) {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func getCurrentRepoActionRunByID(ctx *context.APIContext) *actions_model.ActionRun {
|
||||
runID := ctx.PathParamInt64("run")
|
||||
run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID)
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
return nil
|
||||
} else if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return nil
|
||||
}
|
||||
return run
|
||||
}
|
||||
|
||||
func getCurrentRepoActionRunJobsByID(ctx *context.APIContext) (*actions_model.ActionRun, actions_model.ActionJobList) {
|
||||
run := getCurrentRepoActionRunByID(ctx)
|
||||
if ctx.Written() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return nil, nil
|
||||
}
|
||||
return run, jobs
|
||||
}
|
||||
|
||||
// GetWorkflowRun Gets a specific workflow run.
|
||||
func GetWorkflowRun(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run} repository GetWorkflowRun
|
||||
@@ -1121,7 +1190,7 @@ func GetWorkflowRun(ctx *context.APIContext) {
|
||||
// - name: run
|
||||
// in: path
|
||||
// description: id of the run
|
||||
// type: string
|
||||
// type: integer
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
@@ -1131,19 +1200,12 @@ func GetWorkflowRun(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
runID := ctx.PathParamInt64("run")
|
||||
job, has, err := db.GetByID[actions_model.ActionRun](ctx, runID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
run := getCurrentRepoActionRunByID(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if !has || job.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.APIErrorNotFound(util.ErrNotExist)
|
||||
return
|
||||
}
|
||||
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, job)
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, run)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -1151,6 +1213,179 @@ func GetWorkflowRun(ctx *context.APIContext) {
|
||||
ctx.JSON(http.StatusOK, convertedRun)
|
||||
}
|
||||
|
||||
// RerunWorkflowRun Reruns an entire workflow run.
|
||||
func RerunWorkflowRun(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/actions/runs/{run}/rerun repository rerunWorkflowRun
|
||||
// ---
|
||||
// summary: Reruns an entire workflow run
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repository
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: run
|
||||
// in: path
|
||||
// description: id of the run
|
||||
// type: integer
|
||||
// required: true
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/WorkflowRun"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
run, jobs := getCurrentRepoActionRunJobsByID(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, jobs); err != nil {
|
||||
handleWorkflowRerunError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, run)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusCreated, convertedRun)
|
||||
}
|
||||
|
||||
// RerunFailedWorkflowRun Reruns all failed jobs in a workflow run.
|
||||
func RerunFailedWorkflowRun(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/actions/runs/{run}/rerun-failed-jobs repository rerunFailedWorkflowRun
|
||||
// ---
|
||||
// summary: Reruns all failed jobs in a workflow run
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repository
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: run
|
||||
// in: path
|
||||
// description: id of the run
|
||||
// type: integer
|
||||
// required: true
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
run, jobs := getCurrentRepoActionRunJobsByID(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, actions_service.GetFailedRerunJobs(jobs)); err != nil {
|
||||
handleWorkflowRerunError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusCreated)
|
||||
}
|
||||
|
||||
// RerunWorkflowJob Reruns a specific workflow job in a run.
|
||||
func RerunWorkflowJob(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/actions/runs/{run}/jobs/{job_id}/rerun repository rerunWorkflowJob
|
||||
// ---
|
||||
// summary: Reruns a specific workflow job in a run
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repository
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: run
|
||||
// in: path
|
||||
// description: id of the run
|
||||
// type: integer
|
||||
// required: true
|
||||
// - name: job_id
|
||||
// in: path
|
||||
// description: id of the job
|
||||
// type: integer
|
||||
// required: true
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/WorkflowJob"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
run, jobs := getCurrentRepoActionRunJobsByID(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
jobID := ctx.PathParamInt64("job_id")
|
||||
jobIdx := slices.IndexFunc(jobs, func(job *actions_model.ActionRunJob) bool { return job.ID == jobID })
|
||||
if jobIdx == -1 {
|
||||
ctx.APIErrorNotFound(util.NewNotExistErrorf("workflow job with id %d", jobID))
|
||||
return
|
||||
}
|
||||
|
||||
targetJob := jobs[jobIdx]
|
||||
if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, actions_service.GetAllRerunJobs(targetJob, jobs)); err != nil {
|
||||
handleWorkflowRerunError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
convertedJob, err := convert.ToActionWorkflowJob(ctx, ctx.Repo.Repository, nil, targetJob)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusCreated, convertedJob)
|
||||
}
|
||||
|
||||
func handleWorkflowRerunError(ctx *context.APIContext, err error) {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
|
||||
// ListWorkflowRunJobs Lists all jobs for a workflow run.
|
||||
func ListWorkflowRunJobs(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run}/jobs repository listWorkflowRunJobs
|
||||
@@ -1195,9 +1430,7 @@ func ListWorkflowRunJobs(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
repoID := ctx.Repo.Repository.ID
|
||||
|
||||
runID := ctx.PathParamInt64("run")
|
||||
repoID, runID := ctx.Repo.Repository.ID, ctx.PathParamInt64("run")
|
||||
|
||||
// Avoid the list all jobs functionality for this api route to be used with a runID == 0.
|
||||
if runID <= 0 {
|
||||
@@ -1297,10 +1530,8 @@ func GetArtifactsOfRun(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
repoID := ctx.Repo.Repository.ID
|
||||
artifactName := ctx.Req.URL.Query().Get("name")
|
||||
|
||||
runID := ctx.PathParamInt64("run")
|
||||
repoID, runID := ctx.Repo.Repository.ID, ctx.PathParamInt64("run")
|
||||
|
||||
artifacts, total, err := db.FindAndCount[actions_model.ActionArtifact](ctx, actions_model.FindArtifactsOptions{
|
||||
RepoID: repoID,
|
||||
@@ -1361,15 +1592,11 @@ func DeleteActionRun(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
runID := ctx.PathParamInt64("run")
|
||||
run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID)
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
return
|
||||
} else if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
run := getCurrentRepoActionRunByID(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if !run.Status.IsDone() {
|
||||
ctx.APIError(http.StatusBadRequest, "this workflow run is not done")
|
||||
return
|
||||
@@ -1541,11 +1768,7 @@ func DeleteArtifact(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
func buildSignature(endp string, expires, artifactID int64) []byte {
|
||||
mac := hmac.New(sha256.New, setting.GetGeneralTokenSigningSecret())
|
||||
mac.Write([]byte(endp))
|
||||
fmt.Fprint(mac, expires)
|
||||
fmt.Fprint(mac, artifactID)
|
||||
return mac.Sum(nil)
|
||||
return actions.BuildSignature("api", endp, strconv.FormatInt(expires, 10), strconv.FormatInt(artifactID, 10))
|
||||
}
|
||||
|
||||
func buildDownloadRawEndpoint(repo *repo_model.Repository, artifactID int64) string {
|
||||
@@ -1555,7 +1778,7 @@ func buildDownloadRawEndpoint(repo *repo_model.Repository, artifactID int64) str
|
||||
func buildSigURL(ctx go_context.Context, endPoint string, artifactID int64) string {
|
||||
// endPoint is a path like "api/v1/repos/owner/repo/actions/artifacts/1/zip/raw"
|
||||
expires := time.Now().Add(60 * time.Minute).Unix()
|
||||
uploadURL := httplib.GuessCurrentAppURL(ctx) + endPoint + "?sig=" + base64.URLEncoding.EncodeToString(buildSignature(endPoint, expires, artifactID)) + "&expires=" + strconv.FormatInt(expires, 10)
|
||||
uploadURL := httplib.GuessCurrentAppURL(ctx) + endPoint + "?sig=" + base64.RawURLEncoding.EncodeToString(buildSignature(endPoint, expires, artifactID)) + "&expires=" + strconv.FormatInt(expires, 10)
|
||||
return uploadURL
|
||||
}
|
||||
|
||||
@@ -1600,18 +1823,16 @@ func DownloadArtifact(ctx *context.APIContext) {
|
||||
ctx.APIError(http.StatusNotFound, "Artifact has expired")
|
||||
return
|
||||
}
|
||||
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.zip; filename*=UTF-8''%s.zip", url.PathEscape(art.ArtifactName), art.ArtifactName))
|
||||
|
||||
if actions.IsArtifactV4(art) {
|
||||
ok, err := actions.DownloadArtifactV4ServeDirectOnly(ctx.Base, art)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
// @actions/toolkit asserts that downloaded artifacts of a different runid return 302
|
||||
// https://github.com/actions/toolkit/blob/44d43b5490b02998bd09b0c4ff369a4cc67876c2/packages/artifact/src/internal/download/download-artifact.ts#L203-L210
|
||||
if actions.DownloadArtifactV4ServeDirect(ctx.Base, art) {
|
||||
return
|
||||
}
|
||||
|
||||
// @actions/toolkit asserts a 302 for the artifact download, so we have to build a signed URL and redirect to it
|
||||
// TODO: a perma link to the code for reference
|
||||
redirectURL := buildSigURL(ctx, buildDownloadRawEndpoint(ctx.Repo.Repository, art.ID), art.ID)
|
||||
ctx.Redirect(redirectURL, http.StatusFound)
|
||||
return
|
||||
@@ -1639,7 +1860,7 @@ func DownloadArtifactRaw(ctx *context.APIContext) {
|
||||
|
||||
sigStr := ctx.Req.URL.Query().Get("sig")
|
||||
expiresStr := ctx.Req.URL.Query().Get("expires")
|
||||
sigBytes, _ := base64.URLEncoding.DecodeString(sigStr)
|
||||
sigBytes, _ := base64.RawURLEncoding.DecodeString(sigStr)
|
||||
expires, _ := strconv.ParseInt(expiresStr, 10, 64)
|
||||
|
||||
expectedSig := buildSignature(buildDownloadRawEndpoint(repo, art.ID), expires, art.ID)
|
||||
@@ -1658,8 +1879,6 @@ func DownloadArtifactRaw(ctx *context.APIContext) {
|
||||
ctx.APIError(http.StatusNotFound, "Artifact has expired")
|
||||
return
|
||||
}
|
||||
ctx.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.zip; filename*=UTF-8''%s.zip", url.PathEscape(art.ArtifactName), art.ArtifactName))
|
||||
|
||||
if actions.IsArtifactV4(art) {
|
||||
err := actions.DownloadArtifactV4(ctx.Base, art)
|
||||
if err != nil {
|
||||
|
||||
@@ -43,9 +43,13 @@ func DownloadActionsRunJobLogs(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
jobID := ctx.PathParamInt64("job_id")
|
||||
curJob, err := actions_model.GetRunJobByID(ctx, jobID)
|
||||
curJob, err := actions_model.GetRunJobByRepoAndID(ctx, ctx.Repo.Repository.ID, jobID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err = curJob.LoadRepo(ctx); err != nil {
|
||||
|
||||
@@ -150,12 +150,12 @@ func DeleteBranch(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName, nil); err != nil {
|
||||
if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil {
|
||||
switch {
|
||||
case git.IsErrBranchNotExist(err):
|
||||
ctx.APIErrorNotFound(err)
|
||||
case errors.Is(err, repo_service.ErrBranchIsDefault):
|
||||
ctx.APIError(http.StatusForbidden, errors.New("can not delete default branch"))
|
||||
ctx.APIError(http.StatusForbidden, errors.New("can not delete default or pull request target branch"))
|
||||
case errors.Is(err, git_model.ErrBranchIsProtected):
|
||||
ctx.APIError(http.StatusForbidden, errors.New("branch protected"))
|
||||
default:
|
||||
@@ -225,7 +225,7 @@ func CreateBranch(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
} else if len(opt.OldBranchName) > 0 { //nolint:staticcheck // deprecated field
|
||||
if gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, opt.OldBranchName) { //nolint:staticcheck // deprecated field
|
||||
if exist, _ := git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, opt.OldBranchName); exist { //nolint:staticcheck // deprecated field
|
||||
oldCommit, err = ctx.Repo.GitRepo.GetBranchCommit(opt.OldBranchName) //nolint:staticcheck // deprecated field
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -243,7 +243,7 @@ func CreateBranch(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, oldCommit.ID.String(), opt.BranchName)
|
||||
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName)
|
||||
if err != nil {
|
||||
if git_model.IsErrBranchNotExist(err) {
|
||||
ctx.APIError(http.StatusNotFound, "The old branch does not exist")
|
||||
@@ -375,11 +375,86 @@ func ListBranches(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(totalNumOfBranches), listOptions.PageSize)
|
||||
ctx.SetLinkHeader(totalNumOfBranches, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(totalNumOfBranches)
|
||||
ctx.JSON(http.StatusOK, apiBranches)
|
||||
}
|
||||
|
||||
// UpdateBranch moves a branch reference to a new commit.
|
||||
func UpdateBranch(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /repos/{owner}/{repo}/branches/{branch} repository repoUpdateBranch
|
||||
// ---
|
||||
// summary: Update a branch reference to a new commit
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: branch
|
||||
// in: path
|
||||
// description: name of the branch
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/UpdateBranchRepoOption"
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "409":
|
||||
// "$ref": "#/responses/conflict"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
opt := web.GetForm(ctx).(*api.UpdateBranchRepoOption)
|
||||
|
||||
branchName := ctx.PathParam("*")
|
||||
repo := ctx.Repo.Repository
|
||||
|
||||
if repo.IsEmpty {
|
||||
ctx.APIError(http.StatusNotFound, "Git Repository is empty.")
|
||||
return
|
||||
}
|
||||
|
||||
if repo.IsMirror {
|
||||
ctx.APIError(http.StatusForbidden, "Git Repository is a mirror.")
|
||||
return
|
||||
}
|
||||
|
||||
// permission check has been done in api.go
|
||||
if err := repo_service.UpdateBranch(ctx, repo, ctx.Repo.GitRepo, ctx.Doer, branchName, opt.NewCommitID, opt.OldCommitID, opt.Force); err != nil {
|
||||
switch {
|
||||
case git_model.IsErrBranchNotExist(err):
|
||||
ctx.APIErrorNotFound(err)
|
||||
case errors.Is(err, util.ErrInvalidArgument):
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
case git.IsErrPushRejected(err):
|
||||
rej := err.(*git.ErrPushRejected)
|
||||
ctx.APIError(http.StatusForbidden, rej.Message)
|
||||
default:
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// RenameBranch renames a repository's branch.
|
||||
func RenameBranch(ctx *context.APIContext) {
|
||||
// swagger:operation PATCH /repos/{owner}/{repo}/branches/{branch} repository repoRenameBranch
|
||||
@@ -434,13 +509,13 @@ func RenameBranch(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := repo_service.RenameBranch(ctx, repo, ctx.Doer, ctx.Repo.GitRepo, oldName, opt.Name)
|
||||
msg, err := repo_service.RenameBranch(ctx, repo, ctx.Doer, oldName, opt.Name)
|
||||
if err != nil {
|
||||
switch {
|
||||
case repo_model.IsErrUserDoesNotHaveAccessToRepo(err):
|
||||
ctx.APIError(http.StatusForbidden, "User must be a repo or site admin to rename default or protected branches.")
|
||||
case errors.Is(err, git_model.ErrBranchIsProtected):
|
||||
ctx.APIError(http.StatusForbidden, "Branch is protected by glob-based protection rules.")
|
||||
ctx.APIError(http.StatusForbidden, "Failed to rename branch due to branch protection rules.")
|
||||
default:
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -488,7 +563,7 @@ func GetBranchProtection(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
bpName := ctx.PathParam("name")
|
||||
bpName := ctx.PathParam("*")
|
||||
bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -770,7 +845,7 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/repoArchivedError"
|
||||
form := web.GetForm(ctx).(*api.EditBranchProtectionOption)
|
||||
repo := ctx.Repo.Repository
|
||||
bpName := ctx.PathParam("name")
|
||||
bpName := ctx.PathParam("*")
|
||||
protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -1011,7 +1086,11 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
isPlainRule := !git_model.IsRuleNameSpecial(bpName)
|
||||
var isBranchExist bool
|
||||
if isPlainRule {
|
||||
isBranchExist = gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, bpName)
|
||||
isBranchExist, err = git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, bpName)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if isBranchExist {
|
||||
@@ -1089,7 +1168,7 @@ func DeleteBranchProtection(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
bpName := ctx.PathParam("name")
|
||||
bpName := ctx.PathParam("*")
|
||||
bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
|
||||
@@ -291,7 +291,7 @@ func GetRepoPermissions(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, ctx.Repo.Repository, collaborator)
|
||||
permission, err := access_model.GetIndividualUserRepoPermission(ctx, ctx.Repo.Repository, collaborator)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
@@ -222,8 +223,7 @@ func GetAllCommits(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
// Total commit count
|
||||
commitsCountTotal, err = git.CommitsCount(ctx.Repo.GitRepo.Ctx, git.CommitsCountOptions{
|
||||
RepoPath: ctx.Repo.GitRepo.Path,
|
||||
commitsCountTotal, err = gitrepo.CommitsCount(ctx, ctx.Repo.Repository, gitrepo.CommitsCountOptions{
|
||||
Not: not,
|
||||
Revision: []string{baseCommit.ID.String()},
|
||||
Since: since,
|
||||
@@ -245,9 +245,8 @@ func GetAllCommits(ctx *context.APIContext) {
|
||||
sha = ctx.Repo.Repository.DefaultBranch
|
||||
}
|
||||
|
||||
commitsCountTotal, err = git.CommitsCount(ctx,
|
||||
git.CommitsCountOptions{
|
||||
RepoPath: ctx.Repo.GitRepo.Path,
|
||||
commitsCountTotal, err = gitrepo.CommitsCount(ctx, ctx.Repo.Repository,
|
||||
gitrepo.CommitsCountOptions{
|
||||
Not: not,
|
||||
Revision: []string{sha},
|
||||
RelPath: []string{path},
|
||||
@@ -291,7 +290,7 @@ func GetAllCommits(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(commitsCountTotal), listOptions.PageSize)
|
||||
ctx.SetLinkHeader(commitsCountTotal, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(commitsCountTotal)
|
||||
|
||||
// kept for backwards compatibility
|
||||
|
||||
@@ -5,7 +5,6 @@ package repo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
@@ -52,18 +51,7 @@ func CompareDiff(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
infoPath := ctx.PathParam("*")
|
||||
infos := []string{ctx.Repo.Repository.DefaultBranch, ctx.Repo.Repository.DefaultBranch}
|
||||
if infoPath != "" {
|
||||
infos = strings.SplitN(infoPath, "...", 2)
|
||||
if len(infos) != 2 {
|
||||
if infos = strings.SplitN(infoPath, "..", 2); len(infos) != 2 {
|
||||
infos = []string{ctx.Repo.Repository.DefaultBranch, infoPath}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compareResult, closer := parseCompareInfo(ctx, api.CreatePullRequestOption{Base: infos[0], Head: infos[1]})
|
||||
compareInfo, closer := parseCompareInfo(ctx, ctx.PathParam("*"))
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
@@ -72,15 +60,22 @@ func CompareDiff(ctx *context.APIContext) {
|
||||
verification := ctx.FormString("verification") == "" || ctx.FormBool("verification")
|
||||
files := ctx.FormString("files") == "" || ctx.FormBool("files")
|
||||
|
||||
apiCommits := make([]*api.Commit, 0, len(compareResult.compareInfo.Commits))
|
||||
apiCommits := make([]*api.Commit, 0, len(compareInfo.Commits))
|
||||
userCache := make(map[string]*user_model.User)
|
||||
for i := 0; i < len(compareResult.compareInfo.Commits); i++ {
|
||||
apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, compareResult.compareInfo.Commits[i], userCache,
|
||||
|
||||
for i := 0; i < len(compareInfo.Commits); i++ {
|
||||
apiCommit, err := convert.ToCommit(
|
||||
ctx,
|
||||
compareInfo.HeadRepo,
|
||||
compareInfo.HeadGitRepo,
|
||||
compareInfo.Commits[i],
|
||||
userCache,
|
||||
convert.ToCommitOptions{
|
||||
Stat: true,
|
||||
Verification: verification,
|
||||
Files: files,
|
||||
})
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -89,7 +84,7 @@ func CompareDiff(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, &api.Compare{
|
||||
TotalCommits: len(compareResult.compareInfo.Commits),
|
||||
TotalCommits: len(compareInfo.Commits),
|
||||
Commits: apiCommits,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,38 +7,48 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
archiver_service "code.gitea.io/gitea/services/repository/archiver"
|
||||
)
|
||||
|
||||
func serveRepoArchive(ctx *context.APIContext, reqFileName string) {
|
||||
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, reqFileName)
|
||||
func serveRepoArchive(ctx *context.APIContext, reqFileName string, paths []string) {
|
||||
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository, ctx.Repo.GitRepo, reqFileName, paths)
|
||||
if err != nil {
|
||||
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else if errors.Is(err, archiver_service.RepoRefNotFoundError{}) {
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
archiver_service.ServeRepoArchive(ctx.Base, ctx.Repo.Repository, ctx.Repo.GitRepo, aReq)
|
||||
err = archiver_service.ServeRepoArchive(ctx.Base, aReq)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DownloadArchive is the GitHub-compatible endpoint to download repository archives
|
||||
// TODO: The API document is missing: Add github compatible tarball download API endpoints (#32572)
|
||||
func DownloadArchive(ctx *context.APIContext) {
|
||||
var tp git.ArchiveType
|
||||
var tp repo_model.ArchiveType
|
||||
switch ballType := ctx.PathParam("ball_type"); ballType {
|
||||
case "tarball":
|
||||
tp = git.ArchiveTarGz
|
||||
tp = repo_model.ArchiveTarGz
|
||||
case "zipball":
|
||||
tp = git.ArchiveZip
|
||||
tp = repo_model.ArchiveZip
|
||||
case "bundle":
|
||||
tp = git.ArchiveBundle
|
||||
tp = repo_model.ArchiveBundle
|
||||
default:
|
||||
ctx.APIError(http.StatusBadRequest, "Unknown archive type: "+ballType)
|
||||
return
|
||||
}
|
||||
serveRepoArchive(ctx, ctx.PathParam("*")+"."+tp.String())
|
||||
serveRepoArchive(ctx, ctx.PathParam("*")+"."+tp.String(), ctx.FormStrings("path"))
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ import (
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/httpcache"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/lfs"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/storage"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
@@ -138,7 +138,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
|
||||
// LFS Pointer files are at most 1024 bytes - so any blob greater than 1024 bytes cannot be an LFS file
|
||||
if blob.Size() > lfs.MetaFileMaxSize {
|
||||
// First handle caching for the blob
|
||||
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
|
||||
if httpcache.HandleGenericETagPrivateCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -151,35 +151,18 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
|
||||
|
||||
// OK, now the blob is known to have at most 1024 (lfs pointer max size) bytes,
|
||||
// we can simply read this in one go (This saves reading it twice)
|
||||
dataRc, err := blob.DataAsync()
|
||||
lfsPointerBuf, err := blob.GetBlobBytes(lfs.MetaFileMaxSize)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
buf, err := io.ReadAll(dataRc)
|
||||
if err != nil {
|
||||
_ = dataRc.Close()
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := dataRc.Close(); err != nil {
|
||||
log.Error("Error whilst closing blob %s reader in %-v. Error: %v", blob.ID, ctx.Repo.Repository, err)
|
||||
}
|
||||
|
||||
// Check if the blob represents a pointer
|
||||
pointer, _ := lfs.ReadPointer(bytes.NewReader(buf))
|
||||
pointer, _ := lfs.ReadPointerFromBuffer(lfsPointerBuf)
|
||||
|
||||
// if it's not a pointer, just serve the data directly
|
||||
if !pointer.IsValid() {
|
||||
// First handle caching for the blob
|
||||
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
|
||||
return
|
||||
}
|
||||
|
||||
// If not cached - serve!
|
||||
common.ServeContentByReader(ctx.Base, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf))
|
||||
_, _ = ctx.Resp.Write(lfsPointerBuf)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -188,12 +171,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
|
||||
|
||||
// If there isn't one, just serve the data directly
|
||||
if errors.Is(err, git_model.ErrLFSObjectNotExist) {
|
||||
// Handle caching for the blob SHA (not the LFS object OID)
|
||||
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
|
||||
return
|
||||
}
|
||||
|
||||
common.ServeContentByReader(ctx.Base, ctx.Repo.TreePath, blob.Size(), bytes.NewReader(buf))
|
||||
_, _ = ctx.Resp.Write(lfsPointerBuf)
|
||||
return
|
||||
} else if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -201,27 +179,26 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
// Handle caching for the LFS object OID
|
||||
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+pointer.Oid+`"`) {
|
||||
if httpcache.HandleGenericETagPrivateCache(ctx.Req, ctx.Resp, `"`+pointer.Oid+`"`, meta.UpdatedUnix.AsTimePtr()) {
|
||||
return
|
||||
}
|
||||
|
||||
if setting.LFS.Storage.ServeDirect() {
|
||||
// If we have a signed url (S3, object storage), redirect to this directly.
|
||||
u, err := storage.LFS.URL(pointer.RelativePath(), blob.Name(), ctx.Req.Method, nil)
|
||||
u, err := storage.LFS.ServeDirectURL(pointer.RelativePath(), blob.Name(), ctx.Req.Method, nil)
|
||||
if u != nil && err == nil {
|
||||
ctx.Redirect(u.String())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
lfsDataRc, err := lfs.ReadMetaObject(meta.Pointer)
|
||||
lfsDataFile, err := lfs.ReadMetaObject(meta.Pointer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
defer lfsDataRc.Close()
|
||||
|
||||
common.ServeContentByReadSeeker(ctx.Base, ctx.Repo.TreePath, lastModified, lfsDataRc)
|
||||
defer lfsDataFile.Close()
|
||||
httplib.ServeUserContentByFile(ctx.Base.Req, ctx.Base.Resp, lfsDataFile, httplib.ServeHeaderOptions{Filename: ctx.Repo.TreePath})
|
||||
}
|
||||
|
||||
func getBlobForEntry(ctx *context.APIContext) (blob *git.Blob, entry *git.TreeEntry, lastModified *time.Time) {
|
||||
@@ -273,13 +250,19 @@ func GetArchive(ctx *context.APIContext) {
|
||||
// description: the git reference for download with attached archive format (e.g. master.zip)
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: path
|
||||
// in: query
|
||||
// type: array
|
||||
// items:
|
||||
// type: string
|
||||
// description: subpath of the repository to download
|
||||
// collectionFormat: multi
|
||||
// responses:
|
||||
// 200:
|
||||
// description: success
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
serveRepoArchive(ctx, ctx.PathParam("*"))
|
||||
serveRepoArchive(ctx, ctx.PathParam("*"), ctx.FormStrings("path"))
|
||||
}
|
||||
|
||||
// GetEditorconfig get editor config of a repository
|
||||
@@ -355,6 +338,7 @@ func ReqChangeRepoFileOptionsAndCheck(ctx *context.APIContext) {
|
||||
Message: commonOpts.Message,
|
||||
OldBranch: commonOpts.BranchName,
|
||||
NewBranch: commonOpts.NewBranchName,
|
||||
ForcePush: commonOpts.ForcePush,
|
||||
Committer: &files_service.IdentityOptions{
|
||||
GitUserName: commonOpts.Committer.Name,
|
||||
GitUserEmail: commonOpts.Committer.Email,
|
||||
@@ -524,7 +508,7 @@ func CreateFile(ctx *context.APIContext) {
|
||||
func UpdateFile(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /repos/{owner}/{repo}/contents/{filepath} repository repoUpdateFile
|
||||
// ---
|
||||
// summary: Update a file in a repository
|
||||
// summary: Update a file in a repository if SHA is set, or create the file if SHA is not set
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
@@ -553,6 +537,8 @@ func UpdateFile(ctx *context.APIContext) {
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/FileResponse"
|
||||
// "201":
|
||||
// "$ref": "#/responses/FileResponse"
|
||||
// "403":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
@@ -571,8 +557,9 @@ func UpdateFile(ctx *context.APIContext) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
return
|
||||
}
|
||||
willCreate := apiOpts.SHA == ""
|
||||
opts.Files = append(opts.Files, &files_service.ChangeRepoFile{
|
||||
Operation: "update",
|
||||
Operation: util.Iif(willCreate, "create", "update"),
|
||||
ContentReader: contentReader,
|
||||
SHA: apiOpts.SHA,
|
||||
FromTreePath: apiOpts.FromPath,
|
||||
@@ -586,11 +573,16 @@ func UpdateFile(ctx *context.APIContext) {
|
||||
handleChangeRepoFilesError(ctx, err)
|
||||
} else {
|
||||
fileResponse := files_service.GetFileResponseFromFilesResponse(filesResponse, 0)
|
||||
ctx.JSON(http.StatusOK, fileResponse)
|
||||
ctx.JSON(util.Iif(willCreate, http.StatusCreated, http.StatusOK), fileResponse)
|
||||
}
|
||||
}
|
||||
|
||||
func handleChangeRepoFilesError(ctx *context.APIContext, err error) {
|
||||
if git.IsErrPushRejected(err) {
|
||||
err := err.(*git.ErrPushRejected)
|
||||
ctx.APIError(http.StatusForbidden, err.Message)
|
||||
return
|
||||
}
|
||||
if files_service.IsErrUserCannotCommit(err) || pull_service.IsErrFilePathProtected(err) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
return
|
||||
@@ -601,10 +593,6 @@ func handleChangeRepoFilesError(ctx *context.APIContext, err error) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
return
|
||||
}
|
||||
if git.IsErrBranchNotExist(err) || files_service.IsErrRepoFileDoesNotExist(err) || git.IsErrNotExist(err) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
return
|
||||
|
||||
@@ -6,7 +6,6 @@ package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
@@ -14,6 +13,7 @@ import (
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
@@ -71,7 +71,7 @@ func ListForks(ctx *context.APIContext) {
|
||||
|
||||
apiForks := make([]*api.Repository, len(forks))
|
||||
for i, fork := range forks {
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, fork, ctx.Doer)
|
||||
permission, err := access_model.GetDoerRepoPermission(ctx, fork, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -83,6 +83,35 @@ func ListForks(ctx *context.APIContext) {
|
||||
ctx.JSON(http.StatusOK, apiForks)
|
||||
}
|
||||
|
||||
func prepareDoerCreateRepoInOrg(ctx *context.APIContext, orgName string) *organization.Organization {
|
||||
org, err := organization.GetOrgByName(ctx, orgName)
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIErrorNotFound()
|
||||
return nil
|
||||
} else if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !organization.HasOrgOrUserVisible(ctx, org.AsUser(), ctx.Doer) {
|
||||
ctx.APIErrorNotFound()
|
||||
return nil
|
||||
}
|
||||
|
||||
if !ctx.Doer.IsAdmin {
|
||||
canCreate, err := org.CanCreateOrgRepo(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return nil
|
||||
}
|
||||
if !canCreate {
|
||||
ctx.APIError(http.StatusForbidden, "User is not allowed to create repositories in this organization.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return org
|
||||
}
|
||||
|
||||
// CreateFork create a fork of a repo
|
||||
func CreateFork(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/forks repository createFork
|
||||
@@ -118,41 +147,18 @@ func CreateFork(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
form := web.GetForm(ctx).(*api.CreateForkOption)
|
||||
repo := ctx.Repo.Repository
|
||||
var forker *user_model.User // user/org that will own the fork
|
||||
if form.Organization == nil {
|
||||
forker = ctx.Doer
|
||||
} else {
|
||||
org, err := organization.GetOrgByName(ctx, *form.Organization)
|
||||
if err != nil {
|
||||
if organization.IsErrOrgNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
forkOwner := ctx.Doer // user/org that will own the fork
|
||||
if form.Organization != nil {
|
||||
org := prepareDoerCreateRepoInOrg(ctx, *form.Organization)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
if !ctx.Doer.IsAdmin {
|
||||
isMember, err := org.IsOrgMember(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
} else if !isMember {
|
||||
ctx.APIError(http.StatusForbidden, fmt.Sprintf("User is no Member of Organisation '%s'", org.Name))
|
||||
return
|
||||
}
|
||||
}
|
||||
forker = org.AsUser()
|
||||
forkOwner = org.AsUser()
|
||||
}
|
||||
|
||||
var name string
|
||||
if form.Name == nil {
|
||||
name = repo.Name
|
||||
} else {
|
||||
name = *form.Name
|
||||
}
|
||||
|
||||
fork, err := repo_service.ForkRepository(ctx, ctx.Doer, forker, repo_service.ForkRepoOptions{
|
||||
repo := ctx.Repo.Repository
|
||||
name := optional.FromPtr(form.Name).ValueOrDefault(repo.Name)
|
||||
fork, err := repo_service.ForkRepository(ctx, ctx.Doer, forkOwner, repo_service.ForkRepoOptions{
|
||||
BaseRepo: repo,
|
||||
Name: name,
|
||||
Description: repo.Description,
|
||||
|
||||
@@ -34,7 +34,7 @@ func GetGitAllRefs(ctx *context.APIContext) {
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// # "$ref": "#/responses/Reference" TODO: swagger doesnt support different output formats by ref
|
||||
// # "$ref": "#/responses/Reference" TODO: swagger doesn't support different output formats by ref
|
||||
// "$ref": "#/responses/ReferenceList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
@@ -67,7 +67,7 @@ func GetGitRefs(ctx *context.APIContext) {
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// # "$ref": "#/responses/Reference" TODO: swagger doesnt support different output formats by ref
|
||||
// # "$ref": "#/responses/Reference" TODO: swagger doesn't support different output formats by ref
|
||||
// "$ref": "#/responses/ReferenceList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
@@ -32,6 +33,61 @@ import (
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
)
|
||||
|
||||
// buildSearchIssuesRepoIDs builds the list of repository IDs for issue search based on query parameters.
|
||||
// It returns repoIDs, allPublic flag, and any error that occurred.
|
||||
func buildSearchIssuesRepoIDs(ctx *context.APIContext) (repoIDs []int64, allPublic bool, err error) {
|
||||
opts := repo_model.SearchRepoOptions{
|
||||
Private: false,
|
||||
AllPublic: true,
|
||||
TopicOnly: false,
|
||||
Collaborate: optional.None[bool](),
|
||||
// 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
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
if ctx.FormString("owner") != "" {
|
||||
owner, err := user_model.GetUserByName(ctx, ctx.FormString("owner"))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
opts.OwnerID = owner.ID
|
||||
opts.AllLimited = false
|
||||
opts.AllPublic = false
|
||||
opts.Collaborate = optional.Some(false)
|
||||
}
|
||||
if ctx.FormString("team") != "" {
|
||||
if ctx.FormString("owner") == "" {
|
||||
return nil, false, util.NewInvalidArgumentErrorf("owner organisation is required for filtering on team")
|
||||
}
|
||||
team, err := organization.GetTeam(ctx, opts.OwnerID, ctx.FormString("team"))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
opts.TeamID = team.ID
|
||||
}
|
||||
|
||||
if opts.AllPublic {
|
||||
allPublic = true
|
||||
opts.AllPublic = false // set it false to avoid returning too many repos, we could filter by indexer
|
||||
}
|
||||
repoIDs, _, err = repo_model.SearchRepositoryIDs(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if len(repoIDs) == 0 {
|
||||
// no repos found, don't let the indexer return all repos
|
||||
repoIDs = []int64{0}
|
||||
}
|
||||
|
||||
return repoIDs, allPublic, nil
|
||||
}
|
||||
|
||||
// SearchIssues searches for issues across the repositories that the user has access to
|
||||
func SearchIssues(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/issues/search issue issueSearchIssues
|
||||
@@ -58,11 +114,6 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
// in: query
|
||||
// description: Search string
|
||||
// type: string
|
||||
// - name: priority_repo_id
|
||||
// in: query
|
||||
// description: Repository ID to prioritize in the results
|
||||
// type: integer
|
||||
// format: int64
|
||||
// - name: type
|
||||
// in: query
|
||||
// description: Filter by issue type
|
||||
@@ -107,6 +158,10 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
// in: query
|
||||
// description: Filter by repository owner
|
||||
// type: string
|
||||
// - name: created_by
|
||||
// in: query
|
||||
// description: Only show items which were created by the given user
|
||||
// type: string
|
||||
// - name: team
|
||||
// in: query
|
||||
// description: Filter by team (requires organization owner parameter)
|
||||
@@ -136,81 +191,16 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
var isClosed optional.Option[bool]
|
||||
switch ctx.FormString("state") {
|
||||
case "closed":
|
||||
isClosed = optional.Some(true)
|
||||
case "all":
|
||||
isClosed = optional.None[bool]()
|
||||
default:
|
||||
isClosed = optional.Some(false)
|
||||
}
|
||||
isClosed := common.ParseIssueFilterStateIsClosed(ctx.FormString("state"))
|
||||
|
||||
var (
|
||||
repoIDs []int64
|
||||
allPublic bool
|
||||
)
|
||||
{
|
||||
// find repos user can access (for issue search)
|
||||
opts := repo_model.SearchRepoOptions{
|
||||
Private: false,
|
||||
AllPublic: true,
|
||||
TopicOnly: false,
|
||||
Collaborate: optional.None[bool](),
|
||||
// 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 = !ctx.PublicOnly
|
||||
opts.AllLimited = true
|
||||
}
|
||||
if ctx.FormString("owner") != "" {
|
||||
owner, err := user_model.GetUserByName(ctx, ctx.FormString("owner"))
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
opts.OwnerID = owner.ID
|
||||
opts.AllLimited = false
|
||||
opts.AllPublic = false
|
||||
opts.Collaborate = optional.Some(false)
|
||||
}
|
||||
if ctx.FormString("team") != "" {
|
||||
if ctx.FormString("owner") == "" {
|
||||
ctx.APIError(http.StatusBadRequest, "Owner organisation is required for filtering on team")
|
||||
return
|
||||
}
|
||||
team, err := organization.GetTeam(ctx, opts.OwnerID, ctx.FormString("team"))
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
opts.TeamID = team.ID
|
||||
}
|
||||
|
||||
if opts.AllPublic {
|
||||
allPublic = true
|
||||
opts.AllPublic = false // set it false to avoid returning too many repos, we could filter by indexer
|
||||
}
|
||||
repoIDs, _, err = repo_model.SearchRepositoryIDs(ctx, opts)
|
||||
if err != nil {
|
||||
repoIDs, allPublic, err := buildSearchIssuesRepoIDs(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if len(repoIDs) == 0 {
|
||||
// no repos found, don't let the indexer return all repos
|
||||
repoIDs = []int64{0}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
keyword := ctx.FormTrim("q")
|
||||
@@ -218,15 +208,7 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
keyword = ""
|
||||
}
|
||||
|
||||
var isPull optional.Option[bool]
|
||||
switch ctx.FormString("type") {
|
||||
case "pulls":
|
||||
isPull = optional.Some(true)
|
||||
case "issues":
|
||||
isPull = optional.Some(false)
|
||||
default:
|
||||
isPull = optional.None[bool]()
|
||||
}
|
||||
isPull := common.ParseIssueFilterTypeIsPull(ctx.FormString("type"))
|
||||
|
||||
var includedAnyLabels []int64
|
||||
{
|
||||
@@ -256,14 +238,7 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
limit := util.IfZero(ctx.FormInt("limit"), setting.API.DefaultPagingNum)
|
||||
|
||||
searchOpt := &issue_indexer.SearchOptions{
|
||||
Paginator: &db.ListOptions{
|
||||
@@ -287,6 +262,14 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
searchOpt.UpdatedBeforeUnix = optional.Some(before)
|
||||
}
|
||||
|
||||
createdByID := getUserIDForFilter(ctx, "created_by")
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
if createdByID > 0 {
|
||||
searchOpt.PosterID = strconv.FormatInt(createdByID, 10)
|
||||
}
|
||||
|
||||
if ctx.IsSigned {
|
||||
ctxUserID := ctx.Doer.ID
|
||||
if ctx.FormBool("created") {
|
||||
@@ -306,10 +289,6 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: It's unsupported to sort by priority repo when searching by indexer,
|
||||
// it's indeed an regression, but I think it is worth to support filtering by indexer first.
|
||||
_ = ctx.FormInt64("priority_repo_id")
|
||||
|
||||
ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -321,7 +300,7 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(total), limit)
|
||||
ctx.SetLinkHeader(total, limit)
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, issues))
|
||||
}
|
||||
@@ -351,7 +330,7 @@ func ListIssues(ctx *context.APIContext) {
|
||||
// enum: [closed, open, all]
|
||||
// - name: labels
|
||||
// in: query
|
||||
// description: comma separated list of labels. Fetch only issues that have any of this labels. Non existent labels are discarded
|
||||
// description: comma separated list of label names. Fetch only issues that have any of this label names. Non existent labels are discarded.
|
||||
// type: string
|
||||
// - name: q
|
||||
// in: query
|
||||
@@ -409,16 +388,7 @@ func ListIssues(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
var isClosed optional.Option[bool]
|
||||
switch ctx.FormString("state") {
|
||||
case "closed":
|
||||
isClosed = optional.Some(true)
|
||||
case "all":
|
||||
isClosed = optional.None[bool]()
|
||||
default:
|
||||
isClosed = optional.Some(false)
|
||||
}
|
||||
|
||||
isClosed := common.ParseIssueFilterStateIsClosed(ctx.FormString("state"))
|
||||
keyword := ctx.FormTrim("q")
|
||||
if strings.IndexByte(keyword, 0) >= 0 {
|
||||
keyword = ""
|
||||
@@ -558,7 +528,7 @@ func ListIssues(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(total), listOptions.PageSize)
|
||||
ctx.SetLinkHeader(total, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, issues))
|
||||
}
|
||||
@@ -757,6 +727,9 @@ func EditIssue(ctx *context.APIContext) {
|
||||
// swagger:operation PATCH /repos/{owner}/{repo}/issues/{index} issue issueEditIssue
|
||||
// ---
|
||||
// summary: Edit an issue. If using deadline only the date will be taken into account, and time of day ignored.
|
||||
// description: |
|
||||
// Pass `content_version` to enable optimistic locking on body edits.
|
||||
// If the version doesn't match the current value, the request fails with 409 Conflict.
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
@@ -816,6 +789,15 @@ func EditIssue(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
// Fail fast: if content_version is provided and already stale, reject
|
||||
// before any mutations. The DB-level check in ChangeContent still
|
||||
// handles concurrent requests.
|
||||
// TODO: wrap all mutations in a transaction to fully prevent partial writes.
|
||||
if form.ContentVersion != nil && *form.ContentVersion != issue.ContentVersion {
|
||||
ctx.APIError(http.StatusConflict, issues_model.ErrIssueAlreadyChanged)
|
||||
return
|
||||
}
|
||||
|
||||
if len(form.Title) > 0 {
|
||||
err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title)
|
||||
if err != nil {
|
||||
@@ -824,10 +806,14 @@ func EditIssue(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
if form.Body != nil {
|
||||
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, issue.ContentVersion)
|
||||
contentVersion := issue.ContentVersion
|
||||
if form.ContentVersion != nil {
|
||||
contentVersion = *form.ContentVersion
|
||||
}
|
||||
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, contentVersion)
|
||||
if err != nil {
|
||||
if errors.Is(err, issues_model.ErrIssueAlreadyChanged) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusConflict, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ func CreateIssueAttachment(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
uploaderFile := attachment_service.NewLimitedUploaderKnownSize(file, header.Size)
|
||||
attachment, err := attachment_service.UploadAttachmentGeneralSizeLimit(ctx, uploaderFile, setting.Attachment.AllowedTypes, &repo_model.Attachment{
|
||||
attachment, err := attachment_service.UploadAttachmentForIssue(ctx, uploaderFile, &repo_model.Attachment{
|
||||
Name: filename,
|
||||
UploaderID: ctx.Doer.ID,
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
|
||||
@@ -219,7 +219,7 @@ func isXRefCommentAccessible(ctx stdCtx.Context, user *user_model.User, c *issue
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, c.RefRepo, user)
|
||||
perm, err := access_model.GetDoerRepoPermission(ctx, c.RefRepo, user)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -445,7 +445,7 @@ func GetIssueComment(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
|
||||
comment, err := issues_model.GetCommentWithRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrCommentNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
@@ -455,15 +455,6 @@ func GetIssueComment(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = comment.LoadIssue(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
@@ -579,7 +570,7 @@ func EditIssueCommentDeprecated(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) {
|
||||
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
|
||||
comment, err := issues_model.GetCommentWithRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrCommentNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
@@ -589,16 +580,6 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
|
||||
return
|
||||
}
|
||||
|
||||
if err := comment.LoadIssue(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return
|
||||
@@ -698,7 +679,7 @@ func DeleteIssueCommentDeprecated(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
func deleteIssueComment(ctx *context.APIContext) {
|
||||
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
|
||||
comment, err := issues_model.GetCommentWithRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrCommentNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
@@ -708,16 +689,6 @@ func deleteIssueComment(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := comment.LoadIssue(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return
|
||||
|
||||
@@ -193,7 +193,7 @@ func CreateIssueCommentAttachment(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
uploaderFile := attachment_service.NewLimitedUploaderKnownSize(file, header.Size)
|
||||
attachment, err := attachment_service.UploadAttachmentGeneralSizeLimit(ctx, uploaderFile, setting.Attachment.AllowedTypes, &repo_model.Attachment{
|
||||
attachment, err := attachment_service.UploadAttachmentForIssue(ctx, uploaderFile, &repo_model.Attachment{
|
||||
Name: filename,
|
||||
UploaderID: ctx.Doer.ID,
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
|
||||
@@ -7,13 +7,13 @@ package repo
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
)
|
||||
@@ -77,23 +77,14 @@ func GetIssueDependencies(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
page := max(ctx.FormInt("page"), 1)
|
||||
limit := ctx.FormInt("limit")
|
||||
if limit == 0 {
|
||||
limit = setting.API.DefaultPagingNum
|
||||
} else if limit > setting.API.MaxResponseItems {
|
||||
limit = setting.API.MaxResponseItems
|
||||
}
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
canWrite := ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull)
|
||||
|
||||
blockerIssues := make([]*issues_model.Issue, 0, limit)
|
||||
blockerIssues := make([]*issues_model.Issue, 0, min(listOptions.PageSize, setting.API.MaxResponseItems))
|
||||
|
||||
// 2. Get the issues this issue depends on, i.e. the `<#b>`: `<issue> <- <#b>`
|
||||
blockersInfo, err := issue.BlockedByDependencies(ctx, db.ListOptions{
|
||||
Page: page,
|
||||
PageSize: limit,
|
||||
})
|
||||
blockersInfo, total, err := issue.BlockedByDependencies(ctx, listOptions)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -111,7 +102,7 @@ func GetIssueDependencies(ctx *context.APIContext) {
|
||||
perm = existPerm
|
||||
} else {
|
||||
var err error
|
||||
perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
|
||||
perm, err = access_model.GetDoerRepoPermission(ctx, &blocker.Repository, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -149,7 +140,8 @@ func GetIssueDependencies(ctx *context.APIContext) {
|
||||
}
|
||||
blockerIssues = append(blockerIssues, &blocker.Issue)
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(total, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, blockerIssues))
|
||||
}
|
||||
|
||||
@@ -359,7 +351,7 @@ func GetIssueBlocks(ctx *context.APIContext) {
|
||||
perm = existPerm
|
||||
} else {
|
||||
var err error
|
||||
perm, err = access_model.GetUserRepoPermission(ctx, &depMeta.Repository, ctx.Doer)
|
||||
perm, err = access_model.GetDoerRepoPermission(ctx, &depMeta.Repository, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -545,7 +537,7 @@ func getPermissionForRepo(ctx *context.APIContext, repo *repo_model.Repository)
|
||||
return &ctx.Repo.Permission
|
||||
}
|
||||
|
||||
perm, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||
perm, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return nil
|
||||
|
||||
@@ -169,7 +169,7 @@ func MoveIssuePin(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
err = issues_model.MovePin(ctx, issue, int(ctx.PathParamInt64("position")))
|
||||
err = issues_model.MovePin(ctx, issue, ctx.PathParamInt("position"))
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
|
||||
@@ -175,7 +175,7 @@ func DeleteIssueCommentReaction(ctx *context.APIContext) {
|
||||
// schema:
|
||||
// "$ref": "#/definitions/EditReactionOption"
|
||||
// responses:
|
||||
// "200":
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
@@ -248,8 +248,7 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
// ToDo respond 204
|
||||
ctx.Status(http.StatusOK)
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,7 +407,7 @@ func DeleteIssueReaction(ctx *context.APIContext) {
|
||||
// schema:
|
||||
// "$ref": "#/definitions/EditReactionOption"
|
||||
// responses:
|
||||
// "200":
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
@@ -464,7 +463,6 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
// ToDo respond 204
|
||||
ctx.Status(http.StatusOK)
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ func setIssueSubscription(ctx *context.APIContext, watch bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// If watch state wont change
|
||||
// If watch state won't change
|
||||
if current == watch {
|
||||
ctx.Status(http.StatusOK)
|
||||
return
|
||||
|
||||
@@ -356,7 +356,7 @@ func DeleteTime(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
time, err := issues_model.GetTrackedTimeByID(ctx, ctx.PathParamInt64("id"))
|
||||
time, err := issues_model.GetTrackedTimeByID(ctx, issue.ID, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if db.IsErrNotExist(err) {
|
||||
ctx.APIErrorNotFound(err)
|
||||
|
||||
@@ -140,6 +140,7 @@ func Migrate(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
opts := migrations.MigrateOptions{
|
||||
OriginalURL: form.CloneAddr,
|
||||
CloneAddr: remoteAddr,
|
||||
RepoName: form.RepoName,
|
||||
Description: form.Description,
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
@@ -60,12 +59,7 @@ func ListMilestones(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
state := api.StateType(ctx.FormString("state"))
|
||||
var isClosed optional.Option[bool]
|
||||
switch state {
|
||||
case api.StateClosed, api.StateOpen:
|
||||
isClosed = optional.Some(state == api.StateClosed)
|
||||
}
|
||||
isClosed := common.ParseIssueFilterStateIsClosed(ctx.FormString("state"))
|
||||
|
||||
milestones, total, err := db.FindAndCount[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
|
||||
@@ -179,7 +179,7 @@ func ListPushMirrors(ctx *context.APIContext) {
|
||||
responsePushMirrors = append(responsePushMirrors, m)
|
||||
}
|
||||
}
|
||||
ctx.SetLinkHeader(len(responsePushMirrors), utils.GetListOptions(ctx).PageSize)
|
||||
ctx.SetLinkHeader(int64(len(responsePushMirrors)), utils.GetListOptions(ctx).PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, responsePushMirrors)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
pull_model "code.gitea.io/gitea/models/pull"
|
||||
@@ -21,12 +22,15 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/git/gitcmd"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
@@ -35,6 +39,7 @@ import (
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
git_service "code.gitea.io/gitea/services/git"
|
||||
"code.gitea.io/gitea/services/gitdiff"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
@@ -150,7 +155,7 @@ func ListPullRequests(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
|
||||
ctx.SetLinkHeader(maxResults, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(maxResults)
|
||||
ctx.JSON(http.StatusOK, &apiPrs)
|
||||
}
|
||||
@@ -413,20 +418,20 @@ func CreatePullRequest(ctx *context.APIContext) {
|
||||
)
|
||||
|
||||
// Get repo/branch information
|
||||
compareResult, closer := parseCompareInfo(ctx, form)
|
||||
compareResult, closer := parseCompareInfo(ctx, form.Base+".."+form.Head)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
defer closer()
|
||||
|
||||
if !compareResult.baseRef.IsBranch() || !compareResult.headRef.IsBranch() {
|
||||
if !compareResult.BaseRef.IsBranch() || !compareResult.HeadRef.IsBranch() {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "Invalid PullRequest: base and head must be branches")
|
||||
return
|
||||
}
|
||||
|
||||
// Check if another PR exists with the same targets
|
||||
existingPr, err := issues_model.GetUnmergedPullRequest(ctx, compareResult.headRepo.ID, ctx.Repo.Repository.ID,
|
||||
compareResult.headRef.ShortName(), compareResult.baseRef.ShortName(),
|
||||
existingPr, err := issues_model.GetUnmergedPullRequest(ctx, compareResult.HeadRepo.ID, ctx.Repo.Repository.ID,
|
||||
compareResult.HeadRef.ShortName(), compareResult.BaseRef.ShortName(),
|
||||
issues_model.PullRequestFlowGithub,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -493,6 +498,12 @@ func CreatePullRequest(ctx *context.APIContext) {
|
||||
deadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
|
||||
}
|
||||
|
||||
unitPullRequest, err := ctx.Repo.Repository.GetUnit(ctx, unit.TypePullRequests)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
prIssue := &issues_model.Issue{
|
||||
RepoID: repo.ID,
|
||||
Title: form.Title,
|
||||
@@ -504,16 +515,18 @@ func CreatePullRequest(ctx *context.APIContext) {
|
||||
DeadlineUnix: deadlineUnix,
|
||||
}
|
||||
pr := &issues_model.PullRequest{
|
||||
HeadRepoID: compareResult.headRepo.ID,
|
||||
HeadRepoID: compareResult.HeadRepo.ID,
|
||||
BaseRepoID: repo.ID,
|
||||
HeadBranch: compareResult.headRef.ShortName(),
|
||||
BaseBranch: compareResult.baseRef.ShortName(),
|
||||
HeadRepo: compareResult.headRepo,
|
||||
HeadBranch: compareResult.HeadRef.ShortName(),
|
||||
BaseBranch: compareResult.BaseRef.ShortName(),
|
||||
HeadRepo: compareResult.HeadRepo,
|
||||
BaseRepo: repo,
|
||||
MergeBase: compareResult.compareInfo.MergeBase,
|
||||
MergeBase: compareResult.MergeBase,
|
||||
Type: issues_model.PullRequestGitea,
|
||||
}
|
||||
|
||||
pr.AllowMaintainerEdit = optional.FromPtr(form.AllowMaintainerEdit).ValueOrDefault(unitPullRequest.PullRequestsConfig().DefaultAllowMaintainerEdit)
|
||||
|
||||
// Get all assignee IDs
|
||||
assigneeIDs, err := issues_model.MakeIDsFromAPIAssigneesToAdd(ctx, form.Assignee, form.Assignees)
|
||||
if err != nil {
|
||||
@@ -645,6 +658,15 @@ func EditPullRequest(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
// Fail fast: if content_version is provided and already stale, reject
|
||||
// before any mutations. The DB-level check in ChangeContent still
|
||||
// handles concurrent requests.
|
||||
// TODO: wrap all mutations in a transaction to fully prevent partial writes.
|
||||
if form.ContentVersion != nil && *form.ContentVersion != issue.ContentVersion {
|
||||
ctx.APIError(http.StatusConflict, issues_model.ErrIssueAlreadyChanged)
|
||||
return
|
||||
}
|
||||
|
||||
if len(form.Title) > 0 {
|
||||
err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title)
|
||||
if err != nil {
|
||||
@@ -653,10 +675,14 @@ func EditPullRequest(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
if form.Body != nil {
|
||||
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, issue.ContentVersion)
|
||||
contentVersion := issue.ContentVersion
|
||||
if form.ContentVersion != nil {
|
||||
contentVersion = *form.ContentVersion
|
||||
}
|
||||
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, contentVersion)
|
||||
if err != nil {
|
||||
if errors.Is(err, issues_model.ErrIssueAlreadyChanged) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusConflict, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -756,7 +782,12 @@ func EditPullRequest(ctx *context.APIContext) {
|
||||
|
||||
// change pull target branch
|
||||
if !pr.HasMerged && len(form.Base) != 0 && form.Base != pr.BaseBranch {
|
||||
if !gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, form.Base) {
|
||||
branchExist, err := git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, form.Base)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if !branchExist {
|
||||
ctx.APIError(http.StatusNotFound, fmt.Errorf("new base '%s' not exist", form.Base))
|
||||
return
|
||||
}
|
||||
@@ -881,6 +912,8 @@ func MergePullRequest(ctx *context.APIContext) {
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "405":
|
||||
@@ -961,7 +994,7 @@ func MergePullRequest(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
if strings.Contains(err.Error(), "Wrong commit ID") {
|
||||
ctx.JSON(http.StatusConflict, err)
|
||||
ctx.APIError(http.StatusConflict, err)
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -1011,7 +1044,7 @@ func MergePullRequest(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil {
|
||||
if err := pull_service.Merge(ctx, pr, ctx.Doer, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil {
|
||||
if pull_service.IsErrInvalidMergeStyle(err) {
|
||||
ctx.APIError(http.StatusMethodNotAllowed, fmt.Errorf("%s is not allowed an allowed merge style for this repository", repo_model.MergeStyle(form.Do)))
|
||||
} else if pull_service.IsErrMergeConflicts(err) {
|
||||
@@ -1051,63 +1084,32 @@ func MergePullRequest(ctx *context.APIContext) {
|
||||
ctx.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
type parseCompareInfoResult struct {
|
||||
headRepo *repo_model.Repository
|
||||
headGitRepo *git.Repository
|
||||
compareInfo *pull_service.CompareInfo
|
||||
baseRef git.RefName
|
||||
headRef git.RefName
|
||||
}
|
||||
|
||||
// parseCompareInfo returns non-nil if it succeeds, it always writes to the context and returns nil if it fails
|
||||
func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) (result *parseCompareInfoResult, closer func()) {
|
||||
var err error
|
||||
// Get compared branches information
|
||||
// format: <base branch>...[<head repo>:]<head branch>
|
||||
// base<-head: master...head:feature
|
||||
// same repo: master...feature
|
||||
func parseCompareInfo(ctx *context.APIContext, compareParam string) (result *git_service.CompareInfo, closer func()) {
|
||||
baseRepo := ctx.Repo.Repository
|
||||
baseRefToGuess := form.Base
|
||||
compareReq := common.ParseCompareRouterParam(compareParam)
|
||||
|
||||
headUser := ctx.Repo.Owner
|
||||
headRefToGuess := form.Head
|
||||
if headInfos := strings.Split(form.Head, ":"); len(headInfos) == 1 {
|
||||
// If there is no head repository, it means pull request between same repository.
|
||||
// Do nothing here because the head variables have been assigned above.
|
||||
} else if len(headInfos) == 2 {
|
||||
// There is a head repository (the head repository could also be the same base repo)
|
||||
headRefToGuess = headInfos[1]
|
||||
headUser, err = user_model.GetUserOrOrgByName(ctx, headInfos[0])
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIErrorNotFound("GetUserByName")
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
} else {
|
||||
ctx.APIErrorNotFound()
|
||||
// remove the check when we support compare with carets
|
||||
if compareReq.BaseOriRefSuffix != "" {
|
||||
ctx.APIError(http.StatusBadRequest, "Unsupported comparison syntax: ref with suffix")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
isSameRepo := ctx.Repo.Owner.ID == headUser.ID
|
||||
|
||||
var headRepo *repo_model.Repository
|
||||
if isSameRepo {
|
||||
headRepo = baseRepo
|
||||
} else {
|
||||
headRepo, err = common.FindHeadRepo(ctx, baseRepo, headUser.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return nil, nil
|
||||
}
|
||||
if headRepo == nil {
|
||||
ctx.APIErrorNotFound("head repository not found")
|
||||
return nil, nil
|
||||
}
|
||||
_, headRepo, err := common.GetHeadOwnerAndRepo(ctx, baseRepo, compareReq)
|
||||
switch {
|
||||
case errors.Is(err, util.ErrInvalidArgument):
|
||||
ctx.APIError(http.StatusBadRequest, err.Error())
|
||||
return nil, nil
|
||||
case errors.Is(err, util.ErrNotExist):
|
||||
ctx.APIErrorNotFound()
|
||||
return nil, nil
|
||||
case err != nil:
|
||||
ctx.APIErrorInternal(err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
isSameRepo := baseRepo.ID == headRepo.ID
|
||||
|
||||
var headGitRepo *git.Repository
|
||||
if isSameRepo {
|
||||
headGitRepo = ctx.Repo.GitRepo
|
||||
@@ -1127,21 +1129,21 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
||||
}()
|
||||
|
||||
// user should have permission to read baseRepo's codes and pulls, NOT headRepo's
|
||||
permBase, err := access_model.GetUserRepoPermission(ctx, baseRepo, ctx.Doer)
|
||||
permBase, err := access_model.GetDoerRepoPermission(ctx, baseRepo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !permBase.CanRead(unit.TypeCode) {
|
||||
log.Trace("Permission Denied: User %-v cannot create/read pull requests or cannot read code in Repo %-v\nUser in baseRepo has Permissions: %-+v", ctx.Doer, baseRepo, permBase)
|
||||
ctx.APIErrorNotFound("Can't read pulls or can't read UnitTypeCode")
|
||||
log.Trace("Permission Denied: User %-v cannot read code in Repo %-v\nUser in baseRepo has Permissions: %-+v", ctx.Doer, baseRepo, permBase)
|
||||
ctx.APIErrorNotFound("can't read baseRepo UnitTypeCode")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// user should have permission to read headRepo's codes
|
||||
// TODO: could the logic be simplified if the headRepo is the same as the baseRepo? Need to think more about it.
|
||||
permHead, err := access_model.GetUserRepoPermission(ctx, headRepo, ctx.Doer)
|
||||
permHead, err := access_model.GetDoerRepoPermission(ctx, headRepo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return nil, nil
|
||||
@@ -1152,10 +1154,10 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
baseRef := ctx.Repo.GitRepo.UnstableGuessRefByShortName(baseRefToGuess)
|
||||
headRef := headGitRepo.UnstableGuessRefByShortName(headRefToGuess)
|
||||
baseRef := ctx.Repo.GitRepo.UnstableGuessRefByShortName(util.IfZero(compareReq.BaseOriRef, baseRepo.GetPullRequestTargetBranch(ctx)))
|
||||
headRef := headGitRepo.UnstableGuessRefByShortName(util.IfZero(compareReq.HeadOriRef, headRepo.DefaultBranch))
|
||||
|
||||
log.Trace("Repo path: %q, base ref: %q->%q, head ref: %q->%q", ctx.Repo.GitRepo.Path, baseRefToGuess, baseRef, headRefToGuess, headRef)
|
||||
log.Trace("Repo path: %q, base ref: %q->%q, head ref: %q->%q", ctx.Repo.Repository.RelativePath(), compareReq.BaseOriRef, baseRef, compareReq.HeadOriRef, headRef)
|
||||
|
||||
baseRefValid := baseRef.IsBranch() || baseRef.IsTag() || git.IsStringLikelyCommitID(git.ObjectFormatFromName(ctx.Repo.Repository.ObjectFormatName), baseRef.ShortName())
|
||||
headRefValid := headRef.IsBranch() || headRef.IsTag() || git.IsStringLikelyCommitID(git.ObjectFormatFromName(headRepo.ObjectFormatName), headRef.ShortName())
|
||||
@@ -1165,14 +1167,13 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
compareInfo, err := pull_service.GetCompareInfo(ctx, baseRepo, headRepo, headGitRepo, baseRef.ShortName(), headRef.ShortName(), false, false)
|
||||
compareInfo, err := git_service.GetCompareInfo(ctx, baseRepo, headRepo, headGitRepo, baseRef, headRef, compareReq.DirectComparison(), false)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
result = &parseCompareInfoResult{headRepo: headRepo, headGitRepo: headGitRepo, compareInfo: compareInfo, baseRef: baseRef, headRef: headRef}
|
||||
return result, closer
|
||||
return compareInfo, closer
|
||||
}
|
||||
|
||||
// UpdatePullRequest merge PR's baseBranch into headBranch
|
||||
@@ -1416,7 +1417,7 @@ func GetPullRequestCommits(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
var prInfo *pull_service.CompareInfo
|
||||
var compareInfo *git_service.CompareInfo
|
||||
baseGitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, pr.BaseRepo)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -1425,19 +1426,22 @@ func GetPullRequestCommits(ctx *context.APIContext) {
|
||||
defer closer.Close()
|
||||
|
||||
if pr.HasMerged {
|
||||
prInfo, err = pull_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, pr.MergeBase, pr.GetGitHeadRefName(), false, false)
|
||||
compareInfo, err = git_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, git.RefName(pr.MergeBase), git.RefName(pr.GetGitHeadRefName()), false, false)
|
||||
} else {
|
||||
prInfo, err = pull_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, pr.BaseBranch, pr.GetGitHeadRefName(), false, false)
|
||||
compareInfo, err = git_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, git.RefNameFromBranch(pr.BaseBranch), git.RefName(pr.GetGitHeadRefName()), false, false)
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
if gitcmd.StderrHasPrefix(err, "fatal: bad revision") {
|
||||
ctx.APIError(http.StatusNotFound, "invalid base branch or revision")
|
||||
return
|
||||
} else if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
commits := prInfo.Commits
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
totalNumberOfCommits := len(commits)
|
||||
totalNumberOfCommits := len(compareInfo.Commits)
|
||||
totalNumberOfPages := int(math.Ceil(float64(totalNumberOfCommits) / float64(listOptions.PageSize)))
|
||||
|
||||
userCache := make(map[string]*user_model.User)
|
||||
@@ -1452,7 +1456,7 @@ func GetPullRequestCommits(ctx *context.APIContext) {
|
||||
|
||||
apiCommits := make([]*api.Commit, 0, limit)
|
||||
for i := start; i < start+limit; i++ {
|
||||
apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, baseGitRepo, commits[i], userCache,
|
||||
apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, baseGitRepo, compareInfo.Commits[i], userCache,
|
||||
convert.ToCommitOptions{
|
||||
Stat: true,
|
||||
Verification: verification,
|
||||
@@ -1465,7 +1469,7 @@ func GetPullRequestCommits(ctx *context.APIContext) {
|
||||
apiCommits = append(apiCommits, apiCommit)
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(totalNumberOfCommits, listOptions.PageSize)
|
||||
ctx.SetLinkHeader(int64(totalNumberOfCommits), listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(int64(totalNumberOfCommits))
|
||||
|
||||
ctx.RespHeader().Set("X-Page", strconv.Itoa(listOptions.Page))
|
||||
@@ -1546,11 +1550,11 @@ func GetPullRequestFiles(ctx *context.APIContext) {
|
||||
|
||||
baseGitRepo := ctx.Repo.GitRepo
|
||||
|
||||
var prInfo *pull_service.CompareInfo
|
||||
var compareInfo *git_service.CompareInfo
|
||||
if pr.HasMerged {
|
||||
prInfo, err = pull_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, pr.MergeBase, pr.GetGitHeadRefName(), false, false)
|
||||
compareInfo, err = git_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, git.RefName(pr.MergeBase), git.RefName(pr.GetGitHeadRefName()), false, false)
|
||||
} else {
|
||||
prInfo, err = pull_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, pr.BaseBranch, pr.GetGitHeadRefName(), false, false)
|
||||
compareInfo, err = git_service.GetCompareInfo(ctx, pr.BaseRepo, pr.BaseRepo, baseGitRepo, git.RefNameFromBranch(pr.BaseBranch), git.RefName(pr.GetGitHeadRefName()), false, false)
|
||||
}
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -1563,7 +1567,7 @@ func GetPullRequestFiles(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
startCommitID := prInfo.MergeBase
|
||||
startCommitID := compareInfo.MergeBase
|
||||
endCommitID := headCommitID
|
||||
|
||||
maxLines := setting.Git.MaxGitDiffLines
|
||||
@@ -1584,7 +1588,7 @@ func GetPullRequestFiles(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(baseGitRepo, startCommitID, endCommitID)
|
||||
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, ctx.Repo.Repository, baseGitRepo, startCommitID, endCommitID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -1607,7 +1611,7 @@ func GetPullRequestFiles(ctx *context.APIContext) {
|
||||
apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.BaseRepo, endCommitID))
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(totalNumberOfFiles, listOptions.PageSize)
|
||||
ctx.SetLinkHeader(int64(totalNumberOfFiles), listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(int64(totalNumberOfFiles))
|
||||
|
||||
ctx.RespHeader().Set("X-Page", strconv.Itoa(listOptions.Page))
|
||||
|
||||
@@ -208,6 +208,126 @@ func GetPullReviewComments(ctx *context.APIContext) {
|
||||
ctx.JSON(http.StatusOK, apiComments)
|
||||
}
|
||||
|
||||
// ResolvePullReviewComment resolves a review comment in a pull request
|
||||
func ResolvePullReviewComment(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/pulls/comments/{id}/resolve repository repoResolvePullReviewComment
|
||||
// ---
|
||||
// summary: Resolve a pull request review comment
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: id of the review comment
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "400":
|
||||
// "$ref": "#/responses/validationError"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
updatePullReviewCommentResolve(ctx, true)
|
||||
}
|
||||
|
||||
// UnresolvePullReviewComment unresolves a review comment in a pull request
|
||||
func UnresolvePullReviewComment(ctx *context.APIContext) {
|
||||
// swagger:operation POST /repos/{owner}/{repo}/pulls/comments/{id}/unresolve repository repoUnresolvePullReviewComment
|
||||
// ---
|
||||
// summary: Unresolve a pull request review comment
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: id of the review comment
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "400":
|
||||
// "$ref": "#/responses/validationError"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
updatePullReviewCommentResolve(ctx, false)
|
||||
}
|
||||
|
||||
func updatePullReviewCommentResolve(ctx *context.APIContext, isResolve bool) {
|
||||
comment := getPullReviewCommentToResolve(ctx)
|
||||
if comment == nil {
|
||||
return
|
||||
}
|
||||
|
||||
canMarkConv, err := issues_model.CanMarkConversation(ctx, comment.Issue, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if !canMarkConv {
|
||||
ctx.APIError(http.StatusForbidden, "user should have permission to resolve comment")
|
||||
return
|
||||
}
|
||||
|
||||
if err = issues_model.MarkConversation(ctx, comment, ctx.Doer, isResolve); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func getPullReviewCommentToResolve(ctx *context.APIContext) *issues_model.Comment {
|
||||
comment, err := issues_model.GetCommentWithRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if issues_model.IsErrCommentNotExist(err) {
|
||||
ctx.APIErrorNotFound("GetCommentByID", err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if !comment.Issue.IsPull {
|
||||
ctx.APIError(http.StatusBadRequest, "comment does not belong to a pull request")
|
||||
return nil
|
||||
}
|
||||
|
||||
if comment.Type != issues_model.CommentTypeCode {
|
||||
ctx.APIError(http.StatusBadRequest, "comment is not a review comment")
|
||||
return nil
|
||||
}
|
||||
|
||||
return comment
|
||||
}
|
||||
|
||||
// DeletePullReview delete a specific review from a pull request
|
||||
func DeletePullReview(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /repos/{owner}/{repo}/pulls/{index}/reviews/{id} repository repoDeletePullReview
|
||||
@@ -713,7 +833,7 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions
|
||||
return
|
||||
}
|
||||
|
||||
permDoer, err := access_model.GetUserRepoPermission(ctx, pr.Issue.Repo, ctx.Doer)
|
||||
permDoer, err := access_model.GetDoerRepoPermission(ctx, pr.Issue.Repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
@@ -21,6 +21,21 @@ import (
|
||||
release_service "code.gitea.io/gitea/services/release"
|
||||
)
|
||||
|
||||
func canAccessReleaseDraft(ctx *context.APIContext) bool {
|
||||
if !ctx.IsSigned || !ctx.Repo.CanWrite(unit.TypeReleases) {
|
||||
return false
|
||||
}
|
||||
if ctx.Data["IsApiToken"] != true {
|
||||
// not API token request, the request is from a user session with write access
|
||||
return true
|
||||
}
|
||||
// the request is from an access token with scope
|
||||
scope := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
|
||||
requiredScopes := auth_model.GetRequiredScopes(auth_model.Write, auth_model.AccessTokenScopeCategoryRepository)
|
||||
allow, _ := scope.HasScope(requiredScopes...) // err (invalid token) can be safely ignored
|
||||
return allow
|
||||
}
|
||||
|
||||
// GetRelease get a single release of a repository
|
||||
func GetRelease(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/releases/{id} repository repoGetRelease
|
||||
@@ -62,6 +77,11 @@ func GetRelease(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if release.IsDraft && !canAccessReleaseDraft(ctx) { // only the users with write access can see draft releases
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if err := release.LoadAttributes(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -130,7 +150,7 @@ func ListReleases(ctx *context.APIContext) {
|
||||
// required: true
|
||||
// - name: draft
|
||||
// in: query
|
||||
// description: filter (exclude / include) drafts, if you dont have repo write access none will show
|
||||
// description: filter (exclude / include) drafts, if you don't have repo write access none will show
|
||||
// type: boolean
|
||||
// - name: pre-release
|
||||
// in: query
|
||||
@@ -150,10 +170,12 @@ func ListReleases(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
opts := repo_model.FindReleasesOptions{
|
||||
ListOptions: listOptions,
|
||||
IncludeDrafts: ctx.Repo.AccessMode >= perm.AccessModeWrite || ctx.Repo.UnitAccessMode(unit.TypeReleases) >= perm.AccessModeWrite,
|
||||
IncludeDrafts: canAccessReleaseDraft(ctx),
|
||||
IncludeTags: false,
|
||||
IsDraft: ctx.FormOptionalBool("draft"),
|
||||
IsPreRelease: ctx.FormOptionalBool("pre-release"),
|
||||
@@ -180,7 +202,7 @@ func ListReleases(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(filteredCount), listOptions.PageSize)
|
||||
ctx.SetLinkHeader(filteredCount, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(filteredCount)
|
||||
ctx.JSON(http.StatusOK, rels)
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ func checkReleaseMatchRepo(ctx *context.APIContext, releaseID int64) bool {
|
||||
ctx.APIErrorNotFound()
|
||||
return false
|
||||
}
|
||||
if release.IsDraft && !canAccessReleaseDraft(ctx) {
|
||||
ctx.APIErrorNotFound()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -141,6 +145,10 @@ func ListReleaseAttachments(ctx *context.APIContext) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
if release.IsDraft && !canAccessReleaseDraft(ctx) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
if err := release.LoadAttributes(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -234,7 +242,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
// Create a new attachment and save the file
|
||||
attach, err := attachment_service.UploadAttachmentGeneralSizeLimit(ctx, uploaderFile, setting.Repository.Release.AllowedTypes, &repo_model.Attachment{
|
||||
attach, err := attachment_service.UploadAttachmentForRelease(ctx, uploaderFile, &repo_model.Attachment{
|
||||
Name: filename,
|
||||
UploaderID: ctx.Doer.ID,
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/validation"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
@@ -130,9 +131,6 @@ func Search(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
private := ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private"))
|
||||
if ctx.PublicOnly {
|
||||
private = false
|
||||
}
|
||||
|
||||
opts := repo_model.SearchRepoOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
@@ -148,6 +146,7 @@ func Search(ctx *context.APIContext) {
|
||||
StarredByID: ctx.FormInt64("starredBy"),
|
||||
IncludeDescription: ctx.FormBool("includeDesc"),
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
|
||||
if ctx.FormString("template") != "" {
|
||||
opts.Template = optional.Some(ctx.FormBool("template"))
|
||||
@@ -220,7 +219,7 @@ func Search(ctx *context.APIContext) {
|
||||
})
|
||||
return
|
||||
}
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, api.SearchError{
|
||||
OK: false,
|
||||
@@ -229,7 +228,7 @@ func Search(ctx *context.APIContext) {
|
||||
}
|
||||
results[i] = convert.ToRepo(ctx, repo, permission)
|
||||
}
|
||||
ctx.SetLinkHeader(int(count), opts.PageSize)
|
||||
ctx.SetLinkHeader(count, opts.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, api.SearchResults{
|
||||
OK: true,
|
||||
@@ -270,6 +269,8 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre
|
||||
db.IsErrNamePatternNotAllowed(err) ||
|
||||
label.IsErrTemplateLoad(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
} else if errors.Is(err, util.ErrPermissionDenied) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
@@ -495,31 +496,11 @@ func CreateOrgRepo(ctx *context.APIContext) {
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
opt := web.GetForm(ctx).(*api.CreateRepoOption)
|
||||
org, err := organization.GetOrgByName(ctx, ctx.PathParam("org"))
|
||||
if err != nil {
|
||||
if organization.IsErrOrgNotExist(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
orgName := ctx.PathParam("org")
|
||||
org := prepareDoerCreateRepoInOrg(ctx, orgName)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
if !organization.HasOrgOrUserVisible(ctx, org.AsUser(), ctx.Doer) {
|
||||
ctx.APIErrorNotFound("HasOrgOrUserVisible", nil)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Doer.IsAdmin {
|
||||
canCreate, err := org.CanCreateOrgRepo(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
} else if !canCreate {
|
||||
ctx.APIError(http.StatusForbidden, "Given user is not allowed to create repository in organization.")
|
||||
return
|
||||
}
|
||||
}
|
||||
CreateUserRepo(ctx, org.AsUser(), *opt)
|
||||
}
|
||||
|
||||
@@ -584,8 +565,12 @@ func GetByID(ctx *context.APIContext) {
|
||||
}
|
||||
return
|
||||
}
|
||||
if !ctx.TokenCanAccessRepo(repo) {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -881,77 +866,44 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
|
||||
}
|
||||
}
|
||||
|
||||
if opts.HasPullRequests != nil && !unit_model.TypePullRequests.UnitGlobalDisabled() {
|
||||
if *opts.HasPullRequests {
|
||||
// We do allow setting individual PR settings through the API, so
|
||||
// we get the config settings and then set them
|
||||
// if those settings were provided in the opts.
|
||||
unit, err := repo.GetUnit(ctx, unit_model.TypePullRequests)
|
||||
var config *repo_model.PullRequestsConfig
|
||||
if err != nil {
|
||||
// Unit type doesn't exist so we make a new config file with default values
|
||||
config = &repo_model.PullRequestsConfig{
|
||||
IgnoreWhitespaceConflicts: false,
|
||||
AllowMerge: true,
|
||||
AllowRebase: true,
|
||||
AllowRebaseMerge: true,
|
||||
AllowSquash: true,
|
||||
AllowFastForwardOnly: true,
|
||||
AllowManualMerge: true,
|
||||
AutodetectManualMerge: false,
|
||||
AllowRebaseUpdate: true,
|
||||
DefaultDeleteBranchAfterMerge: false,
|
||||
DefaultMergeStyle: repo_model.MergeStyleMerge,
|
||||
DefaultAllowMaintainerEdit: false,
|
||||
}
|
||||
} else {
|
||||
config = unit.PullRequestsConfig()
|
||||
}
|
||||
|
||||
if opts.IgnoreWhitespaceConflicts != nil {
|
||||
config.IgnoreWhitespaceConflicts = *opts.IgnoreWhitespaceConflicts
|
||||
}
|
||||
if opts.AllowMerge != nil {
|
||||
config.AllowMerge = *opts.AllowMerge
|
||||
}
|
||||
if opts.AllowRebase != nil {
|
||||
config.AllowRebase = *opts.AllowRebase
|
||||
}
|
||||
if opts.AllowRebaseMerge != nil {
|
||||
config.AllowRebaseMerge = *opts.AllowRebaseMerge
|
||||
}
|
||||
if opts.AllowSquash != nil {
|
||||
config.AllowSquash = *opts.AllowSquash
|
||||
}
|
||||
if opts.AllowFastForwardOnly != nil {
|
||||
config.AllowFastForwardOnly = *opts.AllowFastForwardOnly
|
||||
}
|
||||
if opts.AllowManualMerge != nil {
|
||||
config.AllowManualMerge = *opts.AllowManualMerge
|
||||
}
|
||||
if opts.AutodetectManualMerge != nil {
|
||||
config.AutodetectManualMerge = *opts.AutodetectManualMerge
|
||||
}
|
||||
if opts.AllowRebaseUpdate != nil {
|
||||
config.AllowRebaseUpdate = *opts.AllowRebaseUpdate
|
||||
}
|
||||
if opts.DefaultDeleteBranchAfterMerge != nil {
|
||||
config.DefaultDeleteBranchAfterMerge = *opts.DefaultDeleteBranchAfterMerge
|
||||
}
|
||||
if opts.DefaultMergeStyle != nil {
|
||||
config.DefaultMergeStyle = repo_model.MergeStyle(*opts.DefaultMergeStyle)
|
||||
}
|
||||
if opts.DefaultAllowMaintainerEdit != nil {
|
||||
config.DefaultAllowMaintainerEdit = *opts.DefaultAllowMaintainerEdit
|
||||
}
|
||||
|
||||
units = append(units, repo_model.RepoUnit{
|
||||
RepoID: repo.ID,
|
||||
Type: unit_model.TypePullRequests,
|
||||
Config: config,
|
||||
})
|
||||
} else {
|
||||
if !unit_model.TypePullRequests.UnitGlobalDisabled() {
|
||||
mustDeletePullRequestUnit := opts.HasPullRequests != nil && !*opts.HasPullRequests
|
||||
mustInsertPullRequestUnit := opts.HasPullRequests != nil && *opts.HasPullRequests
|
||||
if mustDeletePullRequestUnit {
|
||||
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests)
|
||||
} else {
|
||||
// We do allow setting individual PR settings through the API,
|
||||
// so we get the config settings and then set them if those settings were provided in the opts.
|
||||
unit, err := repo.GetUnit(ctx, unit_model.TypePullRequests)
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
if unit == nil {
|
||||
// Unit doesn't exist yet but is being enabled, create with defaults
|
||||
unit = new(repo_model.DefaultPullRequestsUnit(repo.ID))
|
||||
}
|
||||
|
||||
changed := new(false)
|
||||
config := unit.PullRequestsConfig()
|
||||
optional.AssignPtrValue(changed, &config.IgnoreWhitespaceConflicts, opts.IgnoreWhitespaceConflicts)
|
||||
optional.AssignPtrValue(changed, &config.AllowMerge, opts.AllowMerge)
|
||||
optional.AssignPtrValue(changed, &config.AllowRebase, opts.AllowRebase)
|
||||
optional.AssignPtrValue(changed, &config.AllowRebaseMerge, opts.AllowRebaseMerge)
|
||||
optional.AssignPtrValue(changed, &config.AllowSquash, opts.AllowSquash)
|
||||
optional.AssignPtrValue(changed, &config.AllowFastForwardOnly, opts.AllowFastForwardOnly)
|
||||
optional.AssignPtrValue(changed, &config.AllowManualMerge, opts.AllowManualMerge)
|
||||
optional.AssignPtrValue(changed, &config.AutodetectManualMerge, opts.AutodetectManualMerge)
|
||||
optional.AssignPtrValue(changed, &config.AllowRebaseUpdate, opts.AllowRebaseUpdate)
|
||||
optional.AssignPtrValue(changed, &config.DefaultDeleteBranchAfterMerge, opts.DefaultDeleteBranchAfterMerge)
|
||||
optional.AssignPtrValue(changed, &config.DefaultAllowMaintainerEdit, opts.DefaultAllowMaintainerEdit)
|
||||
optional.AssignPtrString(changed, &config.DefaultMergeStyle, opts.DefaultMergeStyle)
|
||||
if *changed || mustInsertPullRequestUnit {
|
||||
units = append(units, repo_model.RepoUnit{
|
||||
RepoID: repo.ID,
|
||||
Type: unit_model.TypePullRequests,
|
||||
Config: config,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1304,6 +1256,7 @@ func ListRepoActivityFeeds(ctx *context.APIContext) {
|
||||
Date: ctx.FormString("date"),
|
||||
ListOptions: listOptions,
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
|
||||
feeds, count, err := feed_service.GetFeeds(ctx, opts)
|
||||
if err != nil {
|
||||
|
||||
@@ -206,7 +206,7 @@ func getCommitStatuses(ctx *context.APIContext, commitID string) {
|
||||
apiStatuses = append(apiStatuses, convert.ToCommitStatus(ctx, status))
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
|
||||
ctx.SetLinkHeader(maxResults, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(maxResults)
|
||||
|
||||
ctx.JSON(http.StatusOK, apiStatuses)
|
||||
@@ -257,8 +257,8 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
repo := ctx.Repo.Repository
|
||||
|
||||
statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, refCommit.Commit.ID.String(), utils.GetListOptions(ctx))
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
statuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, refCommit.Commit.ID.String(), listOptions)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(fmt.Errorf("GetLatestCommitStatus[%s, %s]: %w", repo.FullName(), refCommit.CommitID, err))
|
||||
return
|
||||
@@ -269,6 +269,7 @@ func GetCombinedCommitStatusByRef(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(fmt.Errorf("CountLatestCommitStatus[%s, %s]: %w", repo.FullName(), refCommit.CommitID, err))
|
||||
return
|
||||
}
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
|
||||
combiStatus := convert.ToCombinedStatus(ctx, refCommit.Commit.ID.String(), statuses,
|
||||
|
||||
@@ -107,15 +107,18 @@ func GetAnnotatedTag(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if tag, err := ctx.Repo.GitRepo.GetAnnotatedTag(sha); err != nil {
|
||||
tag, err := ctx.Repo.GitRepo.GetAnnotatedTag(sha)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else {
|
||||
commit, err := ctx.Repo.GitRepo.GetTagCommit(tag.Name)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
}
|
||||
ctx.JSON(http.StatusOK, convert.ToAnnotatedTag(ctx, ctx.Repo.Repository, tag, commit))
|
||||
return
|
||||
}
|
||||
|
||||
commit, err := ctx.Repo.GitRepo.GetTagCommit(tag.Name)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, convert.ToAnnotatedTag(ctx, ctx.Repo.Repository, tag, commit))
|
||||
}
|
||||
|
||||
// GetTag get the tag of a repository
|
||||
|
||||
@@ -193,7 +193,7 @@ func getWikiPage(ctx *context.APIContext, wikiName wiki_service.WebPath) *api.Wi
|
||||
}
|
||||
|
||||
// get commit count - wiki revisions
|
||||
commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
|
||||
commitsCount, _ := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository.WikiStorageRepo(), ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
|
||||
|
||||
// Get last change information.
|
||||
lastCommit, err := wikiRepo.GetCommitByPath(pageFilename)
|
||||
@@ -333,6 +333,7 @@ func ListWikiPages(ctx *context.APIContext) {
|
||||
pages = append(pages, wiki_service.ToWikiPageMetaData(wikiName, c, ctx.Repo.Repository))
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int64(len(entries)), limit)
|
||||
ctx.SetTotalCountHeader(int64(len(entries)))
|
||||
ctx.JSON(http.StatusOK, pages)
|
||||
}
|
||||
@@ -429,7 +430,7 @@ func ListPageRevisions(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
// get commit count - wiki revisions
|
||||
commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
|
||||
commitsCount, _ := gitrepo.FileCommitsCount(ctx, ctx.Repo.Repository.WikiStorageRepo(), ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
|
||||
|
||||
page := max(ctx.FormInt("page"), 1)
|
||||
|
||||
@@ -445,6 +446,7 @@ func ListPageRevisions(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
// FIXME: SetLinkHeader missing
|
||||
ctx.SetTotalCountHeader(commitsCount)
|
||||
ctx.JSON(http.StatusOK, convert.ToWikiCommitList(commitsHistory, commitsCount))
|
||||
}
|
||||
|
||||
@@ -32,11 +32,12 @@ func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64) {
|
||||
if ownerID != 0 && repoID != 0 {
|
||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||
}
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
opts := actions_model.FindRunJobOptions{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
RunID: runID,
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
ListOptions: listOptions,
|
||||
}
|
||||
for _, status := range ctx.FormStrings("status") {
|
||||
values, err := convertToInternal(status)
|
||||
@@ -78,7 +79,8 @@ func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64) {
|
||||
}
|
||||
res.Entries[i] = convertedWorkflowJob
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(total, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, &res)
|
||||
}
|
||||
|
||||
@@ -120,10 +122,11 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
if ownerID != 0 && repoID != 0 {
|
||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||
}
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
opts := actions_model.FindRunOptions{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
ListOptions: listOptions,
|
||||
}
|
||||
|
||||
if event := ctx.FormString("event"); event != "" {
|
||||
@@ -182,6 +185,7 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
}
|
||||
res.Entries[i] = convertedRun
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(total, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, &res)
|
||||
}
|
||||
|
||||
@@ -16,8 +16,9 @@ import (
|
||||
)
|
||||
|
||||
func ListBlocks(ctx *context.APIContext, blocker *user_model.User) {
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
blocks, total, err := user_model.FindBlockings(ctx, &user_model.FindBlockingOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
ListOptions: listOptions,
|
||||
BlockerID: blocker.ID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -35,6 +36,7 @@ func ListBlocks(ctx *context.APIContext, blocker *user_model.User) {
|
||||
users = append(users, convert.ToUser(ctx, b.Blockee, blocker))
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(total, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, &users)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"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"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
@@ -46,11 +47,13 @@ func ListRunners(ctx *context.APIContext, ownerID, repoID int64) {
|
||||
if ownerID != 0 && repoID != 0 {
|
||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||
}
|
||||
runners, total, err := db.FindAndCount[actions_model.ActionRunner](ctx, &actions_model.FindRunnerOptions{
|
||||
opts := &actions_model.FindRunnerOptions{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
})
|
||||
}
|
||||
opts.IsDisabled = ctx.FormOptionalBool("disabled")
|
||||
runners, total, err := db.FindAndCount[actions_model.ActionRunner](ctx, opts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -125,3 +128,23 @@ func DeleteRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func UpdateRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
||||
runner, ok := getRunnerByID(ctx, ownerID, repoID, runnerID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
form := web.GetForm(ctx).(*api.EditActionRunnerOption)
|
||||
if form.Disabled == nil {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "[Disabled]: Required")
|
||||
return
|
||||
}
|
||||
|
||||
if err := actions_model.SetRunnerDisabled(ctx, runner, *form.Disabled); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
GetRunner(ctx, ownerID, repoID, runnerID)
|
||||
}
|
||||
|
||||
@@ -46,3 +46,10 @@ type swaggerResponseActionWorkflowList struct {
|
||||
// in:body
|
||||
Body api.ActionWorkflowResponse `json:"body"`
|
||||
}
|
||||
|
||||
// RunDetails
|
||||
// swagger:response RunDetails
|
||||
type swaggerResponseRunDetails struct {
|
||||
// in:body
|
||||
Body api.RunDetails `json:"body"`
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package swagger
|
||||
|
||||
import (
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
)
|
||||
|
||||
// ActivityPub
|
||||
// swagger:response ActivityPub
|
||||
type swaggerResponseActivityPub struct {
|
||||
// in:body
|
||||
Body api.ActivityPub `json:"body"`
|
||||
}
|
||||
@@ -147,6 +147,8 @@ type swaggerParameterBodies struct {
|
||||
|
||||
// in:body
|
||||
CreateBranchRepoOption api.CreateBranchRepoOption
|
||||
// in:body
|
||||
UpdateBranchRepoOption api.UpdateBranchRepoOption
|
||||
|
||||
// in:body
|
||||
CreateBranchProtectionOption api.CreateBranchProtectionOption
|
||||
@@ -223,6 +225,9 @@ type swaggerParameterBodies struct {
|
||||
// in:body
|
||||
UpdateVariableOption api.UpdateVariableOption
|
||||
|
||||
// in:body
|
||||
EditActionRunnerOption api.EditActionRunnerOption
|
||||
|
||||
// in:body
|
||||
LockIssueOption api.LockIssueOption
|
||||
}
|
||||
|
||||
@@ -333,10 +333,10 @@ func ListVariables(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
|
||||
OwnerID: ctx.Doer.ID,
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
ListOptions: listOptions,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -354,6 +354,7 @@ func ListVariables(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, variables)
|
||||
}
|
||||
|
||||
@@ -24,12 +24,14 @@ func responseAPIUsers(ctx *context.APIContext, users []*user_model.User) {
|
||||
}
|
||||
|
||||
func listUserFollowers(ctx *context.APIContext, u *user_model.User) {
|
||||
users, count, err := user_model.GetUserFollowers(ctx, u, ctx.Doer, utils.GetListOptions(ctx))
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
users, count, err := user_model.GetUserFollowers(ctx, u, ctx.Doer, listOptions)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
responseAPIUsers(ctx, users)
|
||||
}
|
||||
@@ -88,12 +90,14 @@ func ListFollowers(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
func listUserFollowing(ctx *context.APIContext, u *user_model.User) {
|
||||
users, count, err := user_model.GetUserFollowing(ctx, u, ctx.Doer, utils.GetListOptions(ctx))
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
users, count, err := user_model.GetUserFollowing(ctx, u, ctx.Doer, listOptions)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
responseAPIUsers(ctx, users)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
@@ -53,11 +54,11 @@ func composePublicKeysAPILink() string {
|
||||
func listPublicKeys(ctx *context.APIContext, user *user_model.User) {
|
||||
var keys []*asymkey_model.PublicKey
|
||||
var err error
|
||||
var count int
|
||||
var count int64
|
||||
|
||||
fingerprint := ctx.FormString("fingerprint")
|
||||
username := ctx.PathParam("username")
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
if fingerprint != "" {
|
||||
var userID int64 // Unrestricted
|
||||
// Querying not just listing
|
||||
@@ -65,20 +66,18 @@ func listPublicKeys(ctx *context.APIContext, user *user_model.User) {
|
||||
// Restrict to provided uid
|
||||
userID = user.ID
|
||||
}
|
||||
keys, err = db.Find[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
|
||||
keys, count, err = db.FindAndCount[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
|
||||
ListOptions: listOptions,
|
||||
OwnerID: userID,
|
||||
Fingerprint: fingerprint,
|
||||
})
|
||||
count = len(keys)
|
||||
} else {
|
||||
var total int64
|
||||
// Use ListPublicKeys
|
||||
keys, total, err = db.FindAndCount[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
keys, count, err = db.FindAndCount[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
|
||||
ListOptions: listOptions,
|
||||
OwnerID: user.ID,
|
||||
NotKeytype: asymkey_model.KeyTypePrincipal,
|
||||
})
|
||||
count = int(total)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -95,7 +94,8 @@ func listPublicKeys(ctx *context.APIContext, user *user_model.User) {
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(int64(count))
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, &apiKeys)
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid
|
||||
return
|
||||
}
|
||||
|
||||
key, err := asymkey_model.AddPublicKey(ctx, uid, form.Title, content, 0)
|
||||
key, err := asymkey_model.AddPublicKey(ctx, uid, form.Title, content, 0, false)
|
||||
if err != nil {
|
||||
repo.HandleAddKeyError(ctx, err)
|
||||
return
|
||||
|
||||
@@ -19,12 +19,15 @@ import (
|
||||
func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) {
|
||||
opts := utils.GetListOptions(ctx)
|
||||
|
||||
repos, count, err := repo_model.GetUserRepositories(ctx, repo_model.SearchRepoOptions{
|
||||
searchOpts := repo_model.SearchRepoOptions{
|
||||
Actor: u,
|
||||
Private: private,
|
||||
ListOptions: opts,
|
||||
OrderBy: "id ASC",
|
||||
})
|
||||
}
|
||||
searchOpts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
|
||||
repos, count, err := repo_model.GetUserRepositories(ctx, searchOpts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -37,7 +40,7 @@ func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) {
|
||||
|
||||
apiRepos := make([]*api.Repository, 0, len(repos))
|
||||
for i := range repos {
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, repos[i], ctx.Doer)
|
||||
permission, err := access_model.GetDoerRepoPermission(ctx, repos[i], ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
@@ -47,7 +50,7 @@ func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) {
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(count), opts.PageSize)
|
||||
ctx.SetLinkHeader(count, opts.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, &apiRepos)
|
||||
}
|
||||
@@ -79,8 +82,7 @@ func ListUserRepos(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
private := ctx.IsSigned
|
||||
listUserRepos(ctx, ctx.ContextUser, private)
|
||||
listUserRepos(ctx, ctx.ContextUser, ctx.IsSigned)
|
||||
}
|
||||
|
||||
// ListMyRepos - list the repositories you own or have access to.
|
||||
@@ -110,6 +112,7 @@ func ListMyRepos(ctx *context.APIContext) {
|
||||
Private: ctx.IsSigned,
|
||||
IncludeDescription: true,
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
|
||||
repos, count, err := repo_model.SearchRepository(ctx, opts)
|
||||
if err != nil {
|
||||
@@ -123,14 +126,14 @@ func ListMyRepos(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
results[i] = convert.ToRepo(ctx, repo, permission)
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(count), opts.ListOptions.PageSize)
|
||||
ctx.SetLinkHeader(count, opts.ListOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, &results)
|
||||
}
|
||||
|
||||
@@ -10,21 +10,6 @@ import (
|
||||
|
||||
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
|
||||
|
||||
// GetRegistrationToken returns the token to register user runners
|
||||
func GetRegistrationToken(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/actions/runners/registration-token user userGetRunnerRegistrationToken
|
||||
// ---
|
||||
// summary: Get an user's actions runner registration token
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RegistrationToken"
|
||||
|
||||
shared.GetRegistrationToken(ctx, ctx.Doer.ID, 0)
|
||||
}
|
||||
|
||||
// CreateRegistrationToken returns the token to register user runners
|
||||
func CreateRegistrationToken(ctx *context.APIContext) {
|
||||
// swagger:operation POST /user/actions/runners/registration-token user userCreateRunnerRegistrationToken
|
||||
@@ -47,9 +32,15 @@ func ListRunners(ctx *context.APIContext) {
|
||||
// summary: Get user-level runners
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: disabled
|
||||
// in: query
|
||||
// description: filter by disabled status (true or false)
|
||||
// type: boolean
|
||||
// required: false
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/definitions/ActionRunnersResponse"
|
||||
// "$ref": "#/responses/RunnerList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
@@ -57,11 +48,11 @@ func ListRunners(ctx *context.APIContext) {
|
||||
shared.ListRunners(ctx, ctx.Doer.ID, 0)
|
||||
}
|
||||
|
||||
// GetRunner get an user-level runner
|
||||
// GetRunner get a user-level runner
|
||||
func GetRunner(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/actions/runners/{runner_id} user getUserRunner
|
||||
// ---
|
||||
// summary: Get an user-level runner
|
||||
// summary: Get a user-level runner
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
@@ -72,7 +63,7 @@ func GetRunner(ctx *context.APIContext) {
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/definitions/ActionRunner"
|
||||
// "$ref": "#/responses/Runner"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
@@ -80,11 +71,11 @@ func GetRunner(ctx *context.APIContext) {
|
||||
shared.GetRunner(ctx, ctx.Doer.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
// DeleteRunner delete an user-level runner
|
||||
// DeleteRunner delete a user-level runner
|
||||
func DeleteRunner(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /user/actions/runners/{runner_id} user deleteUserRunner
|
||||
// ---
|
||||
// summary: Delete an user-level runner
|
||||
// summary: Delete a user-level runner
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
@@ -102,3 +93,34 @@ func DeleteRunner(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.DeleteRunner(ctx, ctx.Doer.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
// UpdateRunner update a user-level runner
|
||||
func UpdateRunner(ctx *context.APIContext) {
|
||||
// swagger:operation PATCH /user/actions/runners/{runner_id} user updateUserRunner
|
||||
// ---
|
||||
// summary: Update a user-level runner
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/EditActionRunnerOption"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Runner"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
shared.UpdateRunner(ctx, ctx.Doer.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
@@ -20,18 +20,21 @@ import (
|
||||
// getStarredRepos returns the repos that the user with the specified userID has
|
||||
// starred
|
||||
func getStarredRepos(ctx *context.APIContext, user *user_model.User, private bool) ([]*api.Repository, error) {
|
||||
starredRepos, err := repo_model.GetStarredRepos(ctx, &repo_model.StarredReposOptions{
|
||||
opts := &repo_model.StarredReposOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
StarrerID: user.ID,
|
||||
IncludePrivate: private,
|
||||
})
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
|
||||
starredRepos, err := repo_model.GetStarredRepos(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repos := make([]*api.Repository, len(starredRepos))
|
||||
for i, starred := range starredRepos {
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, starred, user)
|
||||
permission, err := access_model.GetIndividualUserRepoPermission(ctx, starred, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -76,6 +79,7 @@ func GetStarredRepos(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int64(ctx.ContextUser.NumStars), utils.GetListOptions(ctx).PageSize)
|
||||
ctx.SetTotalCountHeader(int64(ctx.ContextUser.NumStars))
|
||||
ctx.JSON(http.StatusOK, &repos)
|
||||
}
|
||||
@@ -107,6 +111,7 @@ func GetMyStarredRepos(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int64(ctx.Doer.NumStars), utils.GetListOptions(ctx).PageSize)
|
||||
ctx.SetTotalCountHeader(int64(ctx.Doer.NumStars))
|
||||
ctx.JSON(http.StatusOK, &repos)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
@@ -69,19 +68,16 @@ func Search(ctx *context.APIContext) {
|
||||
maxResults = 1
|
||||
users = []*user_model.User{user_model.NewActionsUser()}
|
||||
default:
|
||||
var visible []structs.VisibleType
|
||||
if ctx.PublicOnly {
|
||||
visible = []structs.VisibleType{structs.VisibleTypePublic}
|
||||
}
|
||||
users, maxResults, err = user_model.SearchUsers(ctx, user_model.SearchUserOptions{
|
||||
opts := user_model.SearchUserOptions{
|
||||
Actor: ctx.Doer,
|
||||
Keyword: ctx.FormTrim("q"),
|
||||
UID: uid,
|
||||
Type: user_model.UserTypeIndividual,
|
||||
Types: []user_model.UserType{user_model.UserTypeIndividual},
|
||||
SearchByEmail: true,
|
||||
Visible: visible,
|
||||
ListOptions: listOptions,
|
||||
})
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
users, maxResults, err = user_model.SearchUsers(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, map[string]any{
|
||||
"ok": false,
|
||||
@@ -91,7 +87,7 @@ func Search(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
|
||||
ctx.SetLinkHeader(maxResults, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(maxResults)
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
@@ -214,6 +210,7 @@ func ListUserActivityFeeds(ctx *context.APIContext) {
|
||||
Date: ctx.FormString("date"),
|
||||
ListOptions: listOptions,
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
|
||||
feeds, count, err := feed_service.GetFeeds(ctx, opts)
|
||||
if err != nil {
|
||||
|
||||
@@ -18,18 +18,21 @@ import (
|
||||
|
||||
// getWatchedRepos returns the repos that the user with the specified userID is watching
|
||||
func getWatchedRepos(ctx *context.APIContext, user *user_model.User, private bool) ([]*api.Repository, int64, error) {
|
||||
watchedRepos, total, err := repo_model.GetWatchedRepos(ctx, &repo_model.WatchedReposOptions{
|
||||
opts := &repo_model.WatchedReposOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
WatcherID: user.ID,
|
||||
IncludePrivate: private,
|
||||
})
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
|
||||
watchedRepos, total, err := repo_model.GetWatchedRepos(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
repos := make([]*api.Repository, len(watchedRepos))
|
||||
for i, watched := range watchedRepos {
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, watched, user)
|
||||
permission, err := access_model.GetIndividualUserRepoPermission(ctx, watched, user)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -71,6 +74,7 @@ func GetWatchedRepos(ctx *context.APIContext) {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(total, utils.GetListOptions(ctx).PageSize)
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, &repos)
|
||||
}
|
||||
@@ -99,7 +103,7 @@ func GetMyWatchedRepos(ctx *context.APIContext) {
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(total, utils.GetListOptions(ctx).PageSize)
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, &repos)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package utils
|
||||
import (
|
||||
"errors"
|
||||
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
@@ -27,7 +28,7 @@ func ResolveRefCommit(ctx reqctx.RequestContext, repo *repo_model.Repository, in
|
||||
return nil, err
|
||||
}
|
||||
refCommit := RefCommit{InputRef: inputRef}
|
||||
if gitrepo.IsBranchExist(ctx, repo, inputRef) {
|
||||
if exist, _ := git_model.IsBranchExist(ctx, repo.ID, inputRef); exist {
|
||||
refCommit.RefName = git.RefNameFromBranch(inputRef)
|
||||
} else if gitrepo.IsTagExist(ctx, repo, inputRef) {
|
||||
refCommit.RefName = git.RefNameFromTag(inputRef)
|
||||
|
||||
@@ -23,8 +23,9 @@ import (
|
||||
|
||||
// ListOwnerHooks lists the webhooks of the provided owner
|
||||
func ListOwnerHooks(ctx *context.APIContext, owner *user_model.User) {
|
||||
listOptions := GetListOptions(ctx)
|
||||
opts := &webhook.ListWebhookOptions{
|
||||
ListOptions: GetListOptions(ctx),
|
||||
ListOptions: listOptions,
|
||||
OwnerID: owner.ID,
|
||||
}
|
||||
|
||||
@@ -42,7 +43,7 @@ func ListOwnerHooks(ctx *context.APIContext, owner *user_model.User) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, apiHooks)
|
||||
}
|
||||
@@ -214,6 +215,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
|
||||
w := &webhook.Webhook{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
Name: strings.TrimSpace(form.Name),
|
||||
URL: form.Config["url"],
|
||||
ContentType: webhook.ToHookContentType(form.Config["content_type"]),
|
||||
Secret: form.Config["secret"],
|
||||
@@ -391,6 +393,10 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
|
||||
w.IsActive = *form.Active
|
||||
}
|
||||
|
||||
if form.Name != nil {
|
||||
w.Name = strings.TrimSpace(*form.Name)
|
||||
}
|
||||
|
||||
if err := webhook.UpdateWebhook(ctx, w); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return false
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
// GetListOptions returns list options using the page and limit parameters
|
||||
func GetListOptions(ctx *context.APIContext) db.ListOptions {
|
||||
return db.ListOptions{
|
||||
Page: ctx.FormInt("page"),
|
||||
Page: max(ctx.FormInt("page"), 1),
|
||||
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user