mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 17:24:22 +00:00 
			
		
		
		
	Add API management for issue/pull and comment attachments (#21783)
Close #14601 Fix #3690 Revive of #14601. Updated to current code, cleanup and added more read/write checks. Signed-off-by: Andrew Thornton <art27@cantab.net> Signed-off-by: Andre Bruch <ab@andrebruch.com> Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Norwin <git@nroo.de> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
		@@ -875,6 +875,8 @@ func updateCommentInfos(ctx context.Context, opts *CreateCommentOptions, comment
 | 
			
		||||
				return fmt.Errorf("update attachment [%d]: %w", attachments[i].ID, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		comment.Attachments = attachments
 | 
			
		||||
	case CommentTypeReopen, CommentTypeClose:
 | 
			
		||||
		if err = repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.Issue.IsPull, true); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
 
 | 
			
		||||
@@ -30,19 +30,19 @@ func DeleteOrphanedAttachments(x *xorm.Engine) error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
		attachements := make([]Attachment, 0, limit)
 | 
			
		||||
		attachments := make([]Attachment, 0, limit)
 | 
			
		||||
		if err := sess.Where("`issue_id` = 0 and (`release_id` = 0 or `release_id` not in (select `id` from `release`))").
 | 
			
		||||
			Cols("id, uuid").Limit(limit).
 | 
			
		||||
			Asc("id").
 | 
			
		||||
			Find(&attachements); err != nil {
 | 
			
		||||
			Find(&attachments); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if len(attachements) == 0 {
 | 
			
		||||
		if len(attachments) == 0 {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ids := make([]int64, 0, limit)
 | 
			
		||||
		for _, attachment := range attachements {
 | 
			
		||||
		for _, attachment := range attachments {
 | 
			
		||||
			ids = append(ids, attachment.ID)
 | 
			
		||||
		}
 | 
			
		||||
		if len(ids) > 0 {
 | 
			
		||||
@@ -51,13 +51,13 @@ func DeleteOrphanedAttachments(x *xorm.Engine) error {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, attachment := range attachements {
 | 
			
		||||
		for _, attachment := range attachments {
 | 
			
		||||
			uuid := attachment.UUID
 | 
			
		||||
			if err := util.RemoveAll(filepath.Join(setting.Attachment.Path, uuid[0:1], uuid[1:2], uuid)); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if len(attachements) < limit {
 | 
			
		||||
		if len(attachments) < limit {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								modules/convert/attachment.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								modules/convert/attachment.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package convert
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ToAttachment converts models.Attachment to api.Attachment
 | 
			
		||||
func ToAttachment(a *repo_model.Attachment) *api.Attachment {
 | 
			
		||||
	return &api.Attachment{
 | 
			
		||||
		ID:            a.ID,
 | 
			
		||||
		Name:          a.Name,
 | 
			
		||||
		Created:       a.CreatedUnix.AsTime(),
 | 
			
		||||
		DownloadCount: a.DownloadCount,
 | 
			
		||||
		Size:          a.Size,
 | 
			
		||||
		UUID:          a.UUID,
 | 
			
		||||
		DownloadURL:   a.DownloadURL(),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ToAttachments(attachments []*repo_model.Attachment) []*api.Attachment {
 | 
			
		||||
	converted := make([]*api.Attachment, 0, len(attachments))
 | 
			
		||||
	for _, attachment := range attachments {
 | 
			
		||||
		converted = append(converted, ToAttachment(attachment))
 | 
			
		||||
	}
 | 
			
		||||
	return converted
 | 
			
		||||
}
 | 
			
		||||
@@ -37,20 +37,21 @@ func ToAPIIssue(ctx context.Context, issue *issues_model.Issue) *api.Issue {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	apiIssue := &api.Issue{
 | 
			
		||||
		ID:       issue.ID,
 | 
			
		||||
		URL:      issue.APIURL(),
 | 
			
		||||
		HTMLURL:  issue.HTMLURL(),
 | 
			
		||||
		Index:    issue.Index,
 | 
			
		||||
		Poster:   ToUser(issue.Poster, nil),
 | 
			
		||||
		Title:    issue.Title,
 | 
			
		||||
		Body:     issue.Content,
 | 
			
		||||
		Ref:      issue.Ref,
 | 
			
		||||
		Labels:   ToLabelList(issue.Labels, issue.Repo, issue.Repo.Owner),
 | 
			
		||||
		State:    issue.State(),
 | 
			
		||||
		IsLocked: issue.IsLocked,
 | 
			
		||||
		Comments: issue.NumComments,
 | 
			
		||||
		Created:  issue.CreatedUnix.AsTime(),
 | 
			
		||||
		Updated:  issue.UpdatedUnix.AsTime(),
 | 
			
		||||
		ID:          issue.ID,
 | 
			
		||||
		URL:         issue.APIURL(),
 | 
			
		||||
		HTMLURL:     issue.HTMLURL(),
 | 
			
		||||
		Index:       issue.Index,
 | 
			
		||||
		Poster:      ToUser(issue.Poster, nil),
 | 
			
		||||
		Title:       issue.Title,
 | 
			
		||||
		Body:        issue.Content,
 | 
			
		||||
		Attachments: ToAttachments(issue.Attachments),
 | 
			
		||||
		Ref:         issue.Ref,
 | 
			
		||||
		Labels:      ToLabelList(issue.Labels, issue.Repo, issue.Repo.Owner),
 | 
			
		||||
		State:       issue.State(),
 | 
			
		||||
		IsLocked:    issue.IsLocked,
 | 
			
		||||
		Comments:    issue.NumComments,
 | 
			
		||||
		Created:     issue.CreatedUnix.AsTime(),
 | 
			
		||||
		Updated:     issue.UpdatedUnix.AsTime(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	apiIssue.Repo = &api.RepositoryMeta{
 | 
			
		||||
 
 | 
			
		||||
@@ -16,14 +16,15 @@ import (
 | 
			
		||||
// ToComment converts a issues_model.Comment to the api.Comment format
 | 
			
		||||
func ToComment(c *issues_model.Comment) *api.Comment {
 | 
			
		||||
	return &api.Comment{
 | 
			
		||||
		ID:       c.ID,
 | 
			
		||||
		Poster:   ToUser(c.Poster, nil),
 | 
			
		||||
		HTMLURL:  c.HTMLURL(),
 | 
			
		||||
		IssueURL: c.IssueURL(),
 | 
			
		||||
		PRURL:    c.PRURL(),
 | 
			
		||||
		Body:     c.Content,
 | 
			
		||||
		Created:  c.CreatedUnix.AsTime(),
 | 
			
		||||
		Updated:  c.UpdatedUnix.AsTime(),
 | 
			
		||||
		ID:          c.ID,
 | 
			
		||||
		Poster:      ToUser(c.Poster, nil),
 | 
			
		||||
		HTMLURL:     c.HTMLURL(),
 | 
			
		||||
		IssueURL:    c.IssueURL(),
 | 
			
		||||
		PRURL:       c.PRURL(),
 | 
			
		||||
		Body:        c.Content,
 | 
			
		||||
		Attachments: ToAttachments(c.Attachments),
 | 
			
		||||
		Created:     c.CreatedUnix.AsTime(),
 | 
			
		||||
		Updated:     c.UpdatedUnix.AsTime(),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,10 +10,6 @@ import (
 | 
			
		||||
 | 
			
		||||
// ToRelease convert a repo_model.Release to api.Release
 | 
			
		||||
func ToRelease(r *repo_model.Release) *api.Release {
 | 
			
		||||
	assets := make([]*api.Attachment, 0)
 | 
			
		||||
	for _, att := range r.Attachments {
 | 
			
		||||
		assets = append(assets, ToReleaseAttachment(att))
 | 
			
		||||
	}
 | 
			
		||||
	return &api.Release{
 | 
			
		||||
		ID:           r.ID,
 | 
			
		||||
		TagName:      r.TagName,
 | 
			
		||||
@@ -29,19 +25,6 @@ func ToRelease(r *repo_model.Release) *api.Release {
 | 
			
		||||
		CreatedAt:    r.CreatedUnix.AsTime(),
 | 
			
		||||
		PublishedAt:  r.CreatedUnix.AsTime(),
 | 
			
		||||
		Publisher:    ToUser(r.Publisher, nil),
 | 
			
		||||
		Attachments:  assets,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToReleaseAttachment converts models.Attachment to api.Attachment
 | 
			
		||||
func ToReleaseAttachment(a *repo_model.Attachment) *api.Attachment {
 | 
			
		||||
	return &api.Attachment{
 | 
			
		||||
		ID:            a.ID,
 | 
			
		||||
		Name:          a.Name,
 | 
			
		||||
		Created:       a.CreatedUnix.AsTime(),
 | 
			
		||||
		DownloadCount: a.DownloadCount,
 | 
			
		||||
		Size:          a.Size,
 | 
			
		||||
		UUID:          a.UUID,
 | 
			
		||||
		DownloadURL:   a.DownloadURL(),
 | 
			
		||||
		Attachments:  ToAttachments(r.Attachments),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -314,6 +314,11 @@ func (m *webhookNotifier) NotifyNewPullRequest(ctx context.Context, pull *issues
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (m *webhookNotifier) NotifyIssueChangeContent(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldContent string) {
 | 
			
		||||
	if err := issue.LoadRepo(ctx); err != nil {
 | 
			
		||||
		log.Error("LoadRepo: %v", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mode, _ := access_model.AccessLevel(ctx, issue.Poster, issue.Repo)
 | 
			
		||||
	var err error
 | 
			
		||||
	if issue.IsPull {
 | 
			
		||||
 
 | 
			
		||||
@@ -41,18 +41,19 @@ type RepositoryMeta struct {
 | 
			
		||||
// Issue represents an issue in a repository
 | 
			
		||||
// swagger:model
 | 
			
		||||
type Issue struct {
 | 
			
		||||
	ID               int64      `json:"id"`
 | 
			
		||||
	URL              string     `json:"url"`
 | 
			
		||||
	HTMLURL          string     `json:"html_url"`
 | 
			
		||||
	Index            int64      `json:"number"`
 | 
			
		||||
	Poster           *User      `json:"user"`
 | 
			
		||||
	OriginalAuthor   string     `json:"original_author"`
 | 
			
		||||
	OriginalAuthorID int64      `json:"original_author_id"`
 | 
			
		||||
	Title            string     `json:"title"`
 | 
			
		||||
	Body             string     `json:"body"`
 | 
			
		||||
	Ref              string     `json:"ref"`
 | 
			
		||||
	Labels           []*Label   `json:"labels"`
 | 
			
		||||
	Milestone        *Milestone `json:"milestone"`
 | 
			
		||||
	ID               int64         `json:"id"`
 | 
			
		||||
	URL              string        `json:"url"`
 | 
			
		||||
	HTMLURL          string        `json:"html_url"`
 | 
			
		||||
	Index            int64         `json:"number"`
 | 
			
		||||
	Poster           *User         `json:"user"`
 | 
			
		||||
	OriginalAuthor   string        `json:"original_author"`
 | 
			
		||||
	OriginalAuthorID int64         `json:"original_author_id"`
 | 
			
		||||
	Title            string        `json:"title"`
 | 
			
		||||
	Body             string        `json:"body"`
 | 
			
		||||
	Ref              string        `json:"ref"`
 | 
			
		||||
	Attachments      []*Attachment `json:"assets"`
 | 
			
		||||
	Labels           []*Label      `json:"labels"`
 | 
			
		||||
	Milestone        *Milestone    `json:"milestone"`
 | 
			
		||||
	// deprecated
 | 
			
		||||
	Assignee  *User   `json:"assignee"`
 | 
			
		||||
	Assignees []*User `json:"assignees"`
 | 
			
		||||
 
 | 
			
		||||
@@ -9,14 +9,15 @@ import (
 | 
			
		||||
 | 
			
		||||
// Comment represents a comment on a commit or issue
 | 
			
		||||
type Comment struct {
 | 
			
		||||
	ID               int64  `json:"id"`
 | 
			
		||||
	HTMLURL          string `json:"html_url"`
 | 
			
		||||
	PRURL            string `json:"pull_request_url"`
 | 
			
		||||
	IssueURL         string `json:"issue_url"`
 | 
			
		||||
	Poster           *User  `json:"user"`
 | 
			
		||||
	OriginalAuthor   string `json:"original_author"`
 | 
			
		||||
	OriginalAuthorID int64  `json:"original_author_id"`
 | 
			
		||||
	Body             string `json:"body"`
 | 
			
		||||
	ID               int64         `json:"id"`
 | 
			
		||||
	HTMLURL          string        `json:"html_url"`
 | 
			
		||||
	PRURL            string        `json:"pull_request_url"`
 | 
			
		||||
	IssueURL         string        `json:"issue_url"`
 | 
			
		||||
	Poster           *User         `json:"user"`
 | 
			
		||||
	OriginalAuthor   string        `json:"original_author"`
 | 
			
		||||
	OriginalAuthorID int64         `json:"original_author_id"`
 | 
			
		||||
	Body             string        `json:"body"`
 | 
			
		||||
	Attachments      []*Attachment `json:"assets"`
 | 
			
		||||
	// swagger:strfmt date-time
 | 
			
		||||
	Created time.Time `json:"created_at"`
 | 
			
		||||
	// swagger:strfmt date-time
 | 
			
		||||
 
 | 
			
		||||
@@ -567,6 +567,13 @@ func mustNotBeArchived(ctx *context.APIContext) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mustEnableAttachments(ctx *context.APIContext) {
 | 
			
		||||
	if !setting.Attachment.Enabled {
 | 
			
		||||
		ctx.NotFound()
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// bind binding an obj to a func(ctx *context.APIContext)
 | 
			
		||||
func bind(obj interface{}) http.HandlerFunc {
 | 
			
		||||
	tp := reflect.TypeOf(obj)
 | 
			
		||||
@@ -892,6 +899,15 @@ func Routes(ctx gocontext.Context) *web.Route {
 | 
			
		||||
								Get(repo.GetIssueCommentReactions).
 | 
			
		||||
								Post(reqToken(), bind(api.EditReactionOption{}), repo.PostIssueCommentReaction).
 | 
			
		||||
								Delete(reqToken(), bind(api.EditReactionOption{}), repo.DeleteIssueCommentReaction)
 | 
			
		||||
							m.Group("/assets", func() {
 | 
			
		||||
								m.Combo("").
 | 
			
		||||
									Get(repo.ListIssueCommentAttachments).
 | 
			
		||||
									Post(reqToken(), mustNotBeArchived, repo.CreateIssueCommentAttachment)
 | 
			
		||||
								m.Combo("/{asset}").
 | 
			
		||||
									Get(repo.GetIssueCommentAttachment).
 | 
			
		||||
									Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueCommentAttachment).
 | 
			
		||||
									Delete(reqToken(), mustNotBeArchived, repo.DeleteIssueCommentAttachment)
 | 
			
		||||
							}, mustEnableAttachments)
 | 
			
		||||
						})
 | 
			
		||||
					})
 | 
			
		||||
					m.Group("/{index}", func() {
 | 
			
		||||
@@ -935,6 +951,15 @@ func Routes(ctx gocontext.Context) *web.Route {
 | 
			
		||||
							Get(repo.GetIssueReactions).
 | 
			
		||||
							Post(reqToken(), bind(api.EditReactionOption{}), repo.PostIssueReaction).
 | 
			
		||||
							Delete(reqToken(), bind(api.EditReactionOption{}), repo.DeleteIssueReaction)
 | 
			
		||||
						m.Group("/assets", func() {
 | 
			
		||||
							m.Combo("").
 | 
			
		||||
								Get(repo.ListIssueAttachments).
 | 
			
		||||
								Post(reqToken(), mustNotBeArchived, repo.CreateIssueAttachment)
 | 
			
		||||
							m.Combo("/{asset}").
 | 
			
		||||
								Get(repo.GetIssueAttachment).
 | 
			
		||||
								Patch(reqToken(), mustNotBeArchived, bind(api.EditAttachmentOptions{}), repo.EditIssueAttachment).
 | 
			
		||||
								Delete(reqToken(), mustNotBeArchived, repo.DeleteIssueAttachment)
 | 
			
		||||
						}, mustEnableAttachments)
 | 
			
		||||
					})
 | 
			
		||||
				}, mustEnableIssuesOrPulls)
 | 
			
		||||
				m.Group("/labels", func() {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										372
									
								
								routers/api/v1/repo/issue_attachment.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										372
									
								
								routers/api/v1/repo/issue_attachment.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,372 @@
 | 
			
		||||
// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/convert"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
	"code.gitea.io/gitea/services/attachment"
 | 
			
		||||
	issue_service "code.gitea.io/gitea/services/issue"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetIssueAttachment gets a single attachment of the issue
 | 
			
		||||
func GetIssueAttachment(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/assets/{attachment_id} issue issueGetIssueAttachment
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Get an issue attachment
 | 
			
		||||
	// 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: index
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: index of the issue
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: attachment_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: id of the attachment to get
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/Attachment"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
 | 
			
		||||
	issue := getIssueFromContext(ctx)
 | 
			
		||||
	if issue == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	attach := getIssueAttachmentSafeRead(ctx, issue)
 | 
			
		||||
	if attach == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToAttachment(attach))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListIssueAttachments lists all attachments of the issue
 | 
			
		||||
func ListIssueAttachments(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/assets issue issueListIssueAttachments
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: List issue's attachments
 | 
			
		||||
	// 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: index
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: index of the issue
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/AttachmentList"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
 | 
			
		||||
	issue := getIssueFromContext(ctx)
 | 
			
		||||
	if issue == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := issue.LoadAttributes(ctx); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, issue).Attachments)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateIssueAttachment creates an attachment and saves the given file
 | 
			
		||||
func CreateIssueAttachment(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation POST /repos/{owner}/{repo}/issues/{index}/assets issue issueCreateIssueAttachment
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Create an issue attachment
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - multipart/form-data
 | 
			
		||||
	// 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: index
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: index of the issue
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: name
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: name of the attachment
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: false
 | 
			
		||||
	// - name: attachment
 | 
			
		||||
	//   in: formData
 | 
			
		||||
	//   description: attachment to upload
 | 
			
		||||
	//   type: file
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "201":
 | 
			
		||||
	//     "$ref": "#/responses/Attachment"
 | 
			
		||||
	//   "400":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
 | 
			
		||||
	issue := getIssueFromContext(ctx)
 | 
			
		||||
	if issue == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !canUserWriteIssueAttachment(ctx, issue) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get uploaded file from request
 | 
			
		||||
	file, header, err := ctx.Req.FormFile("attachment")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "FormFile", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
 | 
			
		||||
	filename := header.Filename
 | 
			
		||||
	if query := ctx.FormString("name"); query != "" {
 | 
			
		||||
		filename = query
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	attachment, err := attachment.UploadAttachment(file, setting.Attachment.AllowedTypes, &repo_model.Attachment{
 | 
			
		||||
		Name:       filename,
 | 
			
		||||
		UploaderID: ctx.Doer.ID,
 | 
			
		||||
		RepoID:     ctx.Repo.Repository.ID,
 | 
			
		||||
		IssueID:    issue.ID,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	issue.Attachments = append(issue.Attachments, attachment)
 | 
			
		||||
 | 
			
		||||
	if err := issue_service.ChangeContent(issue, ctx.Doer, issue.Content); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "ChangeContent", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusCreated, convert.ToAttachment(attachment))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EditIssueAttachment updates the given attachment
 | 
			
		||||
func EditIssueAttachment(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation PATCH /repos/{owner}/{repo}/issues/{index}/assets/{attachment_id} issue issueEditIssueAttachment
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Edit an issue attachment
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - 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: index
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: index of the issue
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: attachment_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: id of the attachment to edit
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: body
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//     "$ref": "#/definitions/EditAttachmentOptions"
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "201":
 | 
			
		||||
	//     "$ref": "#/responses/Attachment"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
 | 
			
		||||
	attachment := getIssueAttachmentSafeWrite(ctx)
 | 
			
		||||
	if attachment == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// do changes to attachment. only meaningful change is name.
 | 
			
		||||
	form := web.GetForm(ctx).(*api.EditAttachmentOptions)
 | 
			
		||||
	if form.Name != "" {
 | 
			
		||||
		attachment.Name = form.Name
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := repo_model.UpdateAttachment(ctx, attachment); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "UpdateAttachment", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusCreated, convert.ToAttachment(attachment))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteIssueAttachment delete a given attachment
 | 
			
		||||
func DeleteIssueAttachment(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/assets/{attachment_id} issue issueDeleteIssueAttachment
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Delete an issue attachment
 | 
			
		||||
	// 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: index
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: index of the issue
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: attachment_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: id of the attachment to delete
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "204":
 | 
			
		||||
	//     "$ref": "#/responses/empty"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
 | 
			
		||||
	attachment := getIssueAttachmentSafeWrite(ctx)
 | 
			
		||||
	if attachment == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := repo_model.DeleteAttachment(attachment, true); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "DeleteAttachment", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Status(http.StatusNoContent)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getIssueFromContext(ctx *context.APIContext) *issues_model.Issue {
 | 
			
		||||
	issue, err := issues_model.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64("index"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetIssueByIndex", issues_model.IsErrIssueNotExist, err)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	issue.Repo = ctx.Repo.Repository
 | 
			
		||||
 | 
			
		||||
	return issue
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getIssueAttachmentSafeWrite(ctx *context.APIContext) *repo_model.Attachment {
 | 
			
		||||
	issue := getIssueFromContext(ctx)
 | 
			
		||||
	if issue == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !canUserWriteIssueAttachment(ctx, issue) {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return getIssueAttachmentSafeRead(ctx, issue)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getIssueAttachmentSafeRead(ctx *context.APIContext, issue *issues_model.Issue) *repo_model.Attachment {
 | 
			
		||||
	attachment, err := repo_model.GetAttachmentByID(ctx, ctx.ParamsInt64("asset"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetAttachmentByID", repo_model.IsErrAttachmentNotExist, err)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if !attachmentBelongsToRepoOrIssue(ctx, attachment, issue) {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return attachment
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func canUserWriteIssueAttachment(ctx *context.APIContext, issue *issues_model.Issue) bool {
 | 
			
		||||
	canEditIssue := ctx.IsSigned && (ctx.Doer.ID == issue.PosterID || ctx.IsUserRepoAdmin() || ctx.IsUserSiteAdmin()) && ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
 | 
			
		||||
	if !canEditIssue {
 | 
			
		||||
		ctx.Error(http.StatusForbidden, "", "user should have permission to write issue")
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func attachmentBelongsToRepoOrIssue(ctx *context.APIContext, attachment *repo_model.Attachment, issue *issues_model.Issue) bool {
 | 
			
		||||
	if attachment.RepoID != ctx.Repo.Repository.ID {
 | 
			
		||||
		log.Debug("Requested attachment[%d] does not belong to repo[%-v].", attachment.ID, ctx.Repo.Repository)
 | 
			
		||||
		ctx.NotFound("no such attachment in repo")
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if attachment.IssueID == 0 {
 | 
			
		||||
		log.Debug("Requested attachment[%d] is not in an issue.", attachment.ID)
 | 
			
		||||
		ctx.NotFound("no such attachment in issue")
 | 
			
		||||
		return false
 | 
			
		||||
	} else if issue != nil && attachment.IssueID != issue.ID {
 | 
			
		||||
		log.Debug("Requested attachment[%d] does not belong to issue[%d, #%d].", attachment.ID, issue.ID, issue.Index)
 | 
			
		||||
		ctx.NotFound("no such attachment in issue")
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
@@ -95,6 +95,11 @@ func ListIssueComments(ctx *context.APIContext) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := issues_model.CommentList(comments).LoadAttachments(ctx); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	apiComments := make([]*api.Comment, len(comments))
 | 
			
		||||
	for i, comment := range comments {
 | 
			
		||||
		comment.Issue = issue
 | 
			
		||||
@@ -294,6 +299,10 @@ func ListRepoIssueComments(ctx *context.APIContext) {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if err := issues_model.CommentList(comments).LoadAttachments(ctx); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := issues_model.CommentList(comments).Issues().LoadRepositories(ctx); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "LoadRepositories", err)
 | 
			
		||||
		return
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										383
									
								
								routers/api/v1/repo/issue_comment_attachment.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										383
									
								
								routers/api/v1/repo/issue_comment_attachment.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,383 @@
 | 
			
		||||
// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/convert"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
	"code.gitea.io/gitea/services/attachment"
 | 
			
		||||
	comment_service "code.gitea.io/gitea/services/comments"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetIssueCommentAttachment gets a single attachment of the comment
 | 
			
		||||
func GetIssueCommentAttachment(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation GET /repos/{owner}/{repo}/issues/comments/{id}/assets/{attachment_id} issue issueGetIssueCommentAttachment
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Get a comment attachment
 | 
			
		||||
	// 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 comment
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: attachment_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: id of the attachment to get
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/Attachment"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
 | 
			
		||||
	comment := getIssueCommentSafe(ctx)
 | 
			
		||||
	if comment == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	attachment := getIssueCommentAttachmentSafeRead(ctx, comment)
 | 
			
		||||
	if attachment == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if attachment.CommentID != comment.ID {
 | 
			
		||||
		log.Debug("User requested attachment[%d] is not in comment[%d].", attachment.ID, comment.ID)
 | 
			
		||||
		ctx.NotFound("attachment not in comment")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToAttachment(attachment))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListIssueCommentAttachments lists all attachments of the comment
 | 
			
		||||
func ListIssueCommentAttachments(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation GET /repos/{owner}/{repo}/issues/comments/{id}/assets issue issueListIssueCommentAttachments
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: List comment's attachments
 | 
			
		||||
	// 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 comment
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/AttachmentList"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
	comment := getIssueCommentSafe(ctx)
 | 
			
		||||
	if comment == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := comment.LoadAttachments(ctx); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToAttachments(comment.Attachments))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateIssueCommentAttachment creates an attachment and saves the given file
 | 
			
		||||
func CreateIssueCommentAttachment(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation POST /repos/{owner}/{repo}/issues/comments/{id}/assets issue issueCreateIssueCommentAttachment
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Create a comment attachment
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - multipart/form-data
 | 
			
		||||
	// 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 comment
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: name
 | 
			
		||||
	//   in: query
 | 
			
		||||
	//   description: name of the attachment
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   required: false
 | 
			
		||||
	// - name: attachment
 | 
			
		||||
	//   in: formData
 | 
			
		||||
	//   description: attachment to upload
 | 
			
		||||
	//   type: file
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "201":
 | 
			
		||||
	//     "$ref": "#/responses/Attachment"
 | 
			
		||||
	//   "400":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
 | 
			
		||||
	// Check if comment exists and load comment
 | 
			
		||||
	comment := getIssueCommentSafe(ctx)
 | 
			
		||||
	if comment == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !canUserWriteIssueCommentAttachment(ctx, comment) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get uploaded file from request
 | 
			
		||||
	file, header, err := ctx.Req.FormFile("attachment")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "FormFile", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
 | 
			
		||||
	filename := header.Filename
 | 
			
		||||
	if query := ctx.FormString("name"); query != "" {
 | 
			
		||||
		filename = query
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	attachment, err := attachment.UploadAttachment(file, setting.Attachment.AllowedTypes, &repo_model.Attachment{
 | 
			
		||||
		Name:       filename,
 | 
			
		||||
		UploaderID: ctx.Doer.ID,
 | 
			
		||||
		RepoID:     ctx.Repo.Repository.ID,
 | 
			
		||||
		IssueID:    comment.IssueID,
 | 
			
		||||
		CommentID:  comment.ID,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "UploadAttachment", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if err := comment.LoadAttachments(ctx); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = comment_service.UpdateComment(ctx, comment, ctx.Doer, comment.Content); err != nil {
 | 
			
		||||
		ctx.ServerError("UpdateComment", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusCreated, convert.ToAttachment(attachment))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EditIssueCommentAttachment updates the given attachment
 | 
			
		||||
func EditIssueCommentAttachment(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation PATCH /repos/{owner}/{repo}/issues/comments/{id}/assets/{attachment_id} issue issueEditIssueCommentAttachment
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Edit a comment attachment
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - 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 comment
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: attachment_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: id of the attachment to edit
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: body
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//     "$ref": "#/definitions/EditAttachmentOptions"
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "201":
 | 
			
		||||
	//     "$ref": "#/responses/Attachment"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
 | 
			
		||||
	attach := getIssueCommentAttachmentSafeWrite(ctx)
 | 
			
		||||
	if attach == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	form := web.GetForm(ctx).(*api.EditAttachmentOptions)
 | 
			
		||||
	if form.Name != "" {
 | 
			
		||||
		attach.Name = form.Name
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := repo_model.UpdateAttachment(ctx, attach); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach)
 | 
			
		||||
	}
 | 
			
		||||
	ctx.JSON(http.StatusCreated, convert.ToAttachment(attach))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteIssueCommentAttachment delete a given attachment
 | 
			
		||||
func DeleteIssueCommentAttachment(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation DELETE /repos/{owner}/{repo}/issues/comments/{id}/assets/{attachment_id} issue issueDeleteIssueCommentAttachment
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Delete a comment attachment
 | 
			
		||||
	// 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 comment
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// - name: attachment_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: id of the attachment to delete
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	//   format: int64
 | 
			
		||||
	//   required: true
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "204":
 | 
			
		||||
	//     "$ref": "#/responses/empty"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
 | 
			
		||||
	attach := getIssueCommentAttachmentSafeWrite(ctx)
 | 
			
		||||
	if attach == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := repo_model.DeleteAttachment(attach, true); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "DeleteAttachment", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Status(http.StatusNoContent)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getIssueCommentSafe(ctx *context.APIContext) *issues_model.Comment {
 | 
			
		||||
	comment, err := issues_model.GetCommentByID(ctx, ctx.ParamsInt64("id"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetCommentByID", issues_model.IsErrCommentNotExist, err)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if err := comment.LoadIssue(ctx); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "comment.LoadIssue", err)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if comment.Issue == nil || comment.Issue.RepoID != ctx.Repo.Repository.ID {
 | 
			
		||||
		ctx.Error(http.StatusNotFound, "", "no matching issue comment found")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	comment.Issue.Repo = ctx.Repo.Repository
 | 
			
		||||
 | 
			
		||||
	return comment
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getIssueCommentAttachmentSafeWrite(ctx *context.APIContext) *repo_model.Attachment {
 | 
			
		||||
	comment := getIssueCommentSafe(ctx)
 | 
			
		||||
	if comment == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if !canUserWriteIssueCommentAttachment(ctx, comment) {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return getIssueCommentAttachmentSafeRead(ctx, comment)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func canUserWriteIssueCommentAttachment(ctx *context.APIContext, comment *issues_model.Comment) bool {
 | 
			
		||||
	canEditComment := ctx.IsSigned && (ctx.Doer.ID == comment.PosterID || ctx.IsUserRepoAdmin() || ctx.IsUserSiteAdmin()) && ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)
 | 
			
		||||
	if !canEditComment {
 | 
			
		||||
		ctx.Error(http.StatusForbidden, "", "user should have permission to edit comment")
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getIssueCommentAttachmentSafeRead(ctx *context.APIContext, comment *issues_model.Comment) *repo_model.Attachment {
 | 
			
		||||
	attachment, err := repo_model.GetAttachmentByID(ctx, ctx.ParamsInt64("asset"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetAttachmentByID", repo_model.IsErrAttachmentNotExist, err)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if !attachmentBelongsToRepoOrComment(ctx, attachment, comment) {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return attachment
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func attachmentBelongsToRepoOrComment(ctx *context.APIContext, attachment *repo_model.Attachment, comment *issues_model.Comment) bool {
 | 
			
		||||
	if attachment.RepoID != ctx.Repo.Repository.ID {
 | 
			
		||||
		log.Debug("Requested attachment[%d] does not belong to repo[%-v].", attachment.ID, ctx.Repo.Repository)
 | 
			
		||||
		ctx.NotFound("no such attachment in repo")
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if attachment.IssueID == 0 || attachment.CommentID == 0 {
 | 
			
		||||
		log.Debug("Requested attachment[%d] is not in a comment.", attachment.ID)
 | 
			
		||||
		ctx.NotFound("no such attachment in comment")
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if comment != nil && attachment.CommentID != comment.ID {
 | 
			
		||||
		log.Debug("Requested attachment[%d] does not belong to comment[%d].", attachment.ID, comment.ID)
 | 
			
		||||
		ctx.NotFound("no such attachment in comment")
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
@@ -68,7 +68,7 @@ func GetReleaseAttachment(ctx *context.APIContext) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// FIXME Should prove the existence of the given repo, but results in unnecessary database requests
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToReleaseAttachment(attach))
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToAttachment(attach))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListReleaseAttachments lists all attachments of the release
 | 
			
		||||
@@ -194,7 +194,12 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Create a new attachment and save the file
 | 
			
		||||
	attach, err := attachment.UploadAttachment(file, ctx.Doer.ID, release.RepoID, releaseID, filename, setting.Repository.Release.AllowedTypes)
 | 
			
		||||
	attach, err := attachment.UploadAttachment(file, setting.Repository.Release.AllowedTypes, &repo_model.Attachment{
 | 
			
		||||
		Name:       filename,
 | 
			
		||||
		UploaderID: ctx.Doer.ID,
 | 
			
		||||
		RepoID:     release.RepoID,
 | 
			
		||||
		ReleaseID:  releaseID,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if upload.IsErrFileTypeForbidden(err) {
 | 
			
		||||
			ctx.Error(http.StatusBadRequest, "DetectContentType", err)
 | 
			
		||||
@@ -204,7 +209,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusCreated, convert.ToReleaseAttachment(attach))
 | 
			
		||||
	ctx.JSON(http.StatusCreated, convert.ToAttachment(attach))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EditReleaseAttachment updates the given attachment
 | 
			
		||||
@@ -274,7 +279,7 @@ func EditReleaseAttachment(ctx *context.APIContext) {
 | 
			
		||||
	if err := repo_model.UpdateAttachment(ctx, attach); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach)
 | 
			
		||||
	}
 | 
			
		||||
	ctx.JSON(http.StatusCreated, convert.ToReleaseAttachment(attach))
 | 
			
		||||
	ctx.JSON(http.StatusCreated, convert.ToAttachment(attach))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteReleaseAttachment delete a given attachment
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,11 @@ func uploadAttachment(ctx *context.Context, repoID int64, allowedTypes string) {
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
 | 
			
		||||
	attach, err := attachment.UploadAttachment(file, ctx.Doer.ID, repoID, 0, header.Filename, allowedTypes)
 | 
			
		||||
	attach, err := attachment.UploadAttachment(file, allowedTypes, &repo_model.Attachment{
 | 
			
		||||
		Name:       header.Filename,
 | 
			
		||||
		UploaderID: ctx.Doer.ID,
 | 
			
		||||
		RepoID:     repoID,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if upload.IsErrFileTypeForbidden(err) {
 | 
			
		||||
			ctx.Error(http.StatusBadRequest, err.Error())
 | 
			
		||||
@@ -82,7 +86,7 @@ func DeleteAttachment(ctx *context.Context) {
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetAttachment serve attachements
 | 
			
		||||
// GetAttachment serve attachments
 | 
			
		||||
func GetAttachment(ctx *context.Context) {
 | 
			
		||||
	attach, err := repo_model.GetAttachmentByUUID(ctx, ctx.Params(":uuid"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -2749,6 +2749,7 @@ func UpdateCommentContent(ctx *context.Context) {
 | 
			
		||||
		})
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = comment_service.UpdateComment(ctx, comment, ctx.Doer, oldContent); err != nil {
 | 
			
		||||
		ctx.ServerError("UpdateComment", err)
 | 
			
		||||
		return
 | 
			
		||||
@@ -3050,7 +3051,7 @@ func GetIssueAttachments(ctx *context.Context) {
 | 
			
		||||
	issue := GetActionIssue(ctx)
 | 
			
		||||
	attachments := make([]*api.Attachment, len(issue.Attachments))
 | 
			
		||||
	for i := 0; i < len(issue.Attachments); i++ {
 | 
			
		||||
		attachments[i] = convert.ToReleaseAttachment(issue.Attachments[i])
 | 
			
		||||
		attachments[i] = convert.ToAttachment(issue.Attachments[i])
 | 
			
		||||
	}
 | 
			
		||||
	ctx.JSON(http.StatusOK, attachments)
 | 
			
		||||
}
 | 
			
		||||
@@ -3069,7 +3070,7 @@ func GetCommentAttachments(ctx *context.Context) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		for i := 0; i < len(comment.Attachments); i++ {
 | 
			
		||||
			attachments = append(attachments, convert.ToReleaseAttachment(comment.Attachments[i]))
 | 
			
		||||
			attachments = append(attachments, convert.ToAttachment(comment.Attachments[i]))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ctx.JSON(http.StatusOK, attachments)
 | 
			
		||||
 
 | 
			
		||||
@@ -39,19 +39,14 @@ func NewAttachment(attach *repo_model.Attachment, file io.Reader) (*repo_model.A
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UploadAttachment upload new attachment into storage and update database
 | 
			
		||||
func UploadAttachment(file io.Reader, actorID, repoID, releaseID int64, fileName, allowedTypes string) (*repo_model.Attachment, error) {
 | 
			
		||||
func UploadAttachment(file io.Reader, allowedTypes string, opts *repo_model.Attachment) (*repo_model.Attachment, error) {
 | 
			
		||||
	buf := make([]byte, 1024)
 | 
			
		||||
	n, _ := util.ReadAtMost(file, buf)
 | 
			
		||||
	buf = buf[:n]
 | 
			
		||||
 | 
			
		||||
	if err := upload.Verify(buf, fileName, allowedTypes); err != nil {
 | 
			
		||||
	if err := upload.Verify(buf, opts.Name, allowedTypes); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return NewAttachment(&repo_model.Attachment{
 | 
			
		||||
		RepoID:     repoID,
 | 
			
		||||
		UploaderID: actorID,
 | 
			
		||||
		ReleaseID:  releaseID,
 | 
			
		||||
		Name:       fileName,
 | 
			
		||||
	}, io.MultiReader(bytes.NewReader(buf), file))
 | 
			
		||||
	return NewAttachment(opts, io.MultiReader(bytes.NewReader(buf), file))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/repository"
 | 
			
		||||
	"code.gitea.io/gitea/modules/storage"
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func createTag(ctx context.Context, gitRepo *git.Repository, rel *repo_model.Release, msg string) (bool, error) {
 | 
			
		||||
@@ -218,7 +219,10 @@ func UpdateRelease(doer *user_model.User, gitRepo *git.Repository, rel *repo_mod
 | 
			
		||||
		}
 | 
			
		||||
		for _, attach := range attachments {
 | 
			
		||||
			if attach.ReleaseID != rel.ID {
 | 
			
		||||
				return errors.New("delete attachement of release permission denied")
 | 
			
		||||
				return util.SilentWrap{
 | 
			
		||||
					Message: "delete attachment of release permission denied",
 | 
			
		||||
					Err:     util.ErrPermissionDenied,
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			deletedUUIDs.Add(attach.UUID)
 | 
			
		||||
		}
 | 
			
		||||
@@ -240,7 +244,10 @@ func UpdateRelease(doer *user_model.User, gitRepo *git.Repository, rel *repo_mod
 | 
			
		||||
		}
 | 
			
		||||
		for _, attach := range attachments {
 | 
			
		||||
			if attach.ReleaseID != rel.ID {
 | 
			
		||||
				return errors.New("update attachement of release permission denied")
 | 
			
		||||
				return util.SilentWrap{
 | 
			
		||||
					Message: "update attachment of release permission denied",
 | 
			
		||||
					Err:     util.ErrPermissionDenied,
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5095,6 +5095,273 @@
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/repos/{owner}/{repo}/issues/comments/{id}/assets": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "issue"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "List comment's attachments",
 | 
			
		||||
        "operationId": "issueListIssueCommentAttachments",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "owner of the repo",
 | 
			
		||||
            "name": "owner",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the repo",
 | 
			
		||||
            "name": "repo",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "id of the comment",
 | 
			
		||||
            "name": "id",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "$ref": "#/responses/AttachmentList"
 | 
			
		||||
          },
 | 
			
		||||
          "404": {
 | 
			
		||||
            "$ref": "#/responses/error"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "post": {
 | 
			
		||||
        "consumes": [
 | 
			
		||||
          "multipart/form-data"
 | 
			
		||||
        ],
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "issue"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "Create a comment attachment",
 | 
			
		||||
        "operationId": "issueCreateIssueCommentAttachment",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "owner of the repo",
 | 
			
		||||
            "name": "owner",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the repo",
 | 
			
		||||
            "name": "repo",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "id of the comment",
 | 
			
		||||
            "name": "id",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the attachment",
 | 
			
		||||
            "name": "name",
 | 
			
		||||
            "in": "query"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "file",
 | 
			
		||||
            "description": "attachment to upload",
 | 
			
		||||
            "name": "attachment",
 | 
			
		||||
            "in": "formData",
 | 
			
		||||
            "required": true
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "201": {
 | 
			
		||||
            "$ref": "#/responses/Attachment"
 | 
			
		||||
          },
 | 
			
		||||
          "400": {
 | 
			
		||||
            "$ref": "#/responses/error"
 | 
			
		||||
          },
 | 
			
		||||
          "404": {
 | 
			
		||||
            "$ref": "#/responses/error"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/repos/{owner}/{repo}/issues/comments/{id}/assets/{attachment_id}": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "issue"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "Get a comment attachment",
 | 
			
		||||
        "operationId": "issueGetIssueCommentAttachment",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "owner of the repo",
 | 
			
		||||
            "name": "owner",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the repo",
 | 
			
		||||
            "name": "repo",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "id of the comment",
 | 
			
		||||
            "name": "id",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "id of the attachment to get",
 | 
			
		||||
            "name": "attachment_id",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "$ref": "#/responses/Attachment"
 | 
			
		||||
          },
 | 
			
		||||
          "404": {
 | 
			
		||||
            "$ref": "#/responses/error"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "delete": {
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "issue"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "Delete a comment attachment",
 | 
			
		||||
        "operationId": "issueDeleteIssueCommentAttachment",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "owner of the repo",
 | 
			
		||||
            "name": "owner",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the repo",
 | 
			
		||||
            "name": "repo",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "id of the comment",
 | 
			
		||||
            "name": "id",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "id of the attachment to delete",
 | 
			
		||||
            "name": "attachment_id",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "204": {
 | 
			
		||||
            "$ref": "#/responses/empty"
 | 
			
		||||
          },
 | 
			
		||||
          "404": {
 | 
			
		||||
            "$ref": "#/responses/error"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "patch": {
 | 
			
		||||
        "consumes": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "issue"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "Edit a comment attachment",
 | 
			
		||||
        "operationId": "issueEditIssueCommentAttachment",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "owner of the repo",
 | 
			
		||||
            "name": "owner",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the repo",
 | 
			
		||||
            "name": "repo",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "id of the comment",
 | 
			
		||||
            "name": "id",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "id of the attachment to edit",
 | 
			
		||||
            "name": "attachment_id",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "name": "body",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/EditAttachmentOptions"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "201": {
 | 
			
		||||
            "$ref": "#/responses/Attachment"
 | 
			
		||||
          },
 | 
			
		||||
          "404": {
 | 
			
		||||
            "$ref": "#/responses/error"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/repos/{owner}/{repo}/issues/comments/{id}/reactions": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "consumes": [
 | 
			
		||||
@@ -5393,6 +5660,273 @@
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/repos/{owner}/{repo}/issues/{index}/assets": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "issue"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "List issue's attachments",
 | 
			
		||||
        "operationId": "issueListIssueAttachments",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "owner of the repo",
 | 
			
		||||
            "name": "owner",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the repo",
 | 
			
		||||
            "name": "repo",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "index of the issue",
 | 
			
		||||
            "name": "index",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "$ref": "#/responses/AttachmentList"
 | 
			
		||||
          },
 | 
			
		||||
          "404": {
 | 
			
		||||
            "$ref": "#/responses/error"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "post": {
 | 
			
		||||
        "consumes": [
 | 
			
		||||
          "multipart/form-data"
 | 
			
		||||
        ],
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "issue"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "Create an issue attachment",
 | 
			
		||||
        "operationId": "issueCreateIssueAttachment",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "owner of the repo",
 | 
			
		||||
            "name": "owner",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the repo",
 | 
			
		||||
            "name": "repo",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "index of the issue",
 | 
			
		||||
            "name": "index",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the attachment",
 | 
			
		||||
            "name": "name",
 | 
			
		||||
            "in": "query"
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "file",
 | 
			
		||||
            "description": "attachment to upload",
 | 
			
		||||
            "name": "attachment",
 | 
			
		||||
            "in": "formData",
 | 
			
		||||
            "required": true
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "201": {
 | 
			
		||||
            "$ref": "#/responses/Attachment"
 | 
			
		||||
          },
 | 
			
		||||
          "400": {
 | 
			
		||||
            "$ref": "#/responses/error"
 | 
			
		||||
          },
 | 
			
		||||
          "404": {
 | 
			
		||||
            "$ref": "#/responses/error"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/repos/{owner}/{repo}/issues/{index}/assets/{attachment_id}": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "issue"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "Get an issue attachment",
 | 
			
		||||
        "operationId": "issueGetIssueAttachment",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "owner of the repo",
 | 
			
		||||
            "name": "owner",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the repo",
 | 
			
		||||
            "name": "repo",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "index of the issue",
 | 
			
		||||
            "name": "index",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "id of the attachment to get",
 | 
			
		||||
            "name": "attachment_id",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "200": {
 | 
			
		||||
            "$ref": "#/responses/Attachment"
 | 
			
		||||
          },
 | 
			
		||||
          "404": {
 | 
			
		||||
            "$ref": "#/responses/error"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "delete": {
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "issue"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "Delete an issue attachment",
 | 
			
		||||
        "operationId": "issueDeleteIssueAttachment",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "owner of the repo",
 | 
			
		||||
            "name": "owner",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the repo",
 | 
			
		||||
            "name": "repo",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "index of the issue",
 | 
			
		||||
            "name": "index",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "id of the attachment to delete",
 | 
			
		||||
            "name": "attachment_id",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "204": {
 | 
			
		||||
            "$ref": "#/responses/empty"
 | 
			
		||||
          },
 | 
			
		||||
          "404": {
 | 
			
		||||
            "$ref": "#/responses/error"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      "patch": {
 | 
			
		||||
        "consumes": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "produces": [
 | 
			
		||||
          "application/json"
 | 
			
		||||
        ],
 | 
			
		||||
        "tags": [
 | 
			
		||||
          "issue"
 | 
			
		||||
        ],
 | 
			
		||||
        "summary": "Edit an issue attachment",
 | 
			
		||||
        "operationId": "issueEditIssueAttachment",
 | 
			
		||||
        "parameters": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "owner of the repo",
 | 
			
		||||
            "name": "owner",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "string",
 | 
			
		||||
            "description": "name of the repo",
 | 
			
		||||
            "name": "repo",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "index of the issue",
 | 
			
		||||
            "name": "index",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "type": "integer",
 | 
			
		||||
            "format": "int64",
 | 
			
		||||
            "description": "id of the attachment to edit",
 | 
			
		||||
            "name": "attachment_id",
 | 
			
		||||
            "in": "path",
 | 
			
		||||
            "required": true
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "name": "body",
 | 
			
		||||
            "in": "body",
 | 
			
		||||
            "schema": {
 | 
			
		||||
              "$ref": "#/definitions/EditAttachmentOptions"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "responses": {
 | 
			
		||||
          "201": {
 | 
			
		||||
            "$ref": "#/responses/Attachment"
 | 
			
		||||
          },
 | 
			
		||||
          "404": {
 | 
			
		||||
            "$ref": "#/responses/error"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "/repos/{owner}/{repo}/issues/{index}/comments": {
 | 
			
		||||
      "get": {
 | 
			
		||||
        "produces": [
 | 
			
		||||
@@ -13882,6 +14416,13 @@
 | 
			
		||||
      "description": "Comment represents a comment on a commit or issue",
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "assets": {
 | 
			
		||||
          "type": "array",
 | 
			
		||||
          "items": {
 | 
			
		||||
            "$ref": "#/definitions/Attachment"
 | 
			
		||||
          },
 | 
			
		||||
          "x-go-name": "Attachments"
 | 
			
		||||
        },
 | 
			
		||||
        "body": {
 | 
			
		||||
          "type": "string",
 | 
			
		||||
          "x-go-name": "Body"
 | 
			
		||||
@@ -16634,6 +17175,13 @@
 | 
			
		||||
      "description": "Issue represents an issue in a repository",
 | 
			
		||||
      "type": "object",
 | 
			
		||||
      "properties": {
 | 
			
		||||
        "assets": {
 | 
			
		||||
          "type": "array",
 | 
			
		||||
          "items": {
 | 
			
		||||
            "$ref": "#/definitions/Attachment"
 | 
			
		||||
          },
 | 
			
		||||
          "x-go-name": "Attachments"
 | 
			
		||||
        },
 | 
			
		||||
        "assignee": {
 | 
			
		||||
          "$ref": "#/definitions/User"
 | 
			
		||||
        },
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										154
									
								
								tests/integration/api_comment_attachment_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								tests/integration/api_comment_attachment_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,154 @@
 | 
			
		||||
// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package integration
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"mime/multipart"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	"code.gitea.io/gitea/models/unittest"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	"code.gitea.io/gitea/modules/convert"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/tests"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestAPIGetCommentAttachment(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2})
 | 
			
		||||
	assert.NoError(t, comment.LoadIssue(db.DefaultContext))
 | 
			
		||||
	assert.NoError(t, comment.LoadAttachments(db.DefaultContext))
 | 
			
		||||
	attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: comment.Attachments[0].ID})
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: comment.Issue.RepoID})
 | 
			
		||||
	repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, repoOwner.Name)
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session)
 | 
			
		||||
	req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d", repoOwner.Name, repo.Name, comment.ID, attachment.ID)
 | 
			
		||||
	session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d?token=%s", repoOwner.Name, repo.Name, comment.ID, attachment.ID, token)
 | 
			
		||||
	resp := session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
	var apiAttachment api.Attachment
 | 
			
		||||
	DecodeJSON(t, resp, &apiAttachment)
 | 
			
		||||
 | 
			
		||||
	expect := convert.ToAttachment(attachment)
 | 
			
		||||
	assert.Equal(t, expect.ID, apiAttachment.ID)
 | 
			
		||||
	assert.Equal(t, expect.Name, apiAttachment.Name)
 | 
			
		||||
	assert.Equal(t, expect.UUID, apiAttachment.UUID)
 | 
			
		||||
	assert.Equal(t, expect.Created.Unix(), apiAttachment.Created.Unix())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPIListCommentAttachments(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2})
 | 
			
		||||
	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
 | 
			
		||||
	repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, repoOwner.Name)
 | 
			
		||||
	req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets",
 | 
			
		||||
		repoOwner.Name, repo.Name, comment.ID)
 | 
			
		||||
	resp := session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
	var apiAttachments []*api.Attachment
 | 
			
		||||
	DecodeJSON(t, resp, &apiAttachments)
 | 
			
		||||
	expectedCount := unittest.GetCount(t, &repo_model.Attachment{CommentID: comment.ID})
 | 
			
		||||
	assert.EqualValues(t, expectedCount, len(apiAttachments))
 | 
			
		||||
 | 
			
		||||
	unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachments[0].ID, CommentID: comment.ID})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPICreateCommentAttachment(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 2})
 | 
			
		||||
	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
 | 
			
		||||
	repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, repoOwner.Name)
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session)
 | 
			
		||||
	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets?token=%s",
 | 
			
		||||
		repoOwner.Name, repo.Name, comment.ID, token)
 | 
			
		||||
 | 
			
		||||
	filename := "image.png"
 | 
			
		||||
	buff := generateImg()
 | 
			
		||||
	body := &bytes.Buffer{}
 | 
			
		||||
 | 
			
		||||
	// Setup multi-part
 | 
			
		||||
	writer := multipart.NewWriter(body)
 | 
			
		||||
	part, err := writer.CreateFormFile("attachment", filename)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	_, err = io.Copy(part, &buff)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	err = writer.Close()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	req := NewRequestWithBody(t, "POST", urlStr, body)
 | 
			
		||||
	req.Header.Add("Content-Type", writer.FormDataContentType())
 | 
			
		||||
	resp := session.MakeRequest(t, req, http.StatusCreated)
 | 
			
		||||
 | 
			
		||||
	apiAttachment := new(api.Attachment)
 | 
			
		||||
	DecodeJSON(t, resp, &apiAttachment)
 | 
			
		||||
 | 
			
		||||
	unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, CommentID: comment.ID})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPIEditCommentAttachment(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	const newAttachmentName = "newAttachmentName"
 | 
			
		||||
 | 
			
		||||
	attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 6})
 | 
			
		||||
	comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: attachment.CommentID})
 | 
			
		||||
	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
 | 
			
		||||
	repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, repoOwner.Name)
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session)
 | 
			
		||||
	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets/%d?token=%s",
 | 
			
		||||
		repoOwner.Name, repo.Name, comment.ID, attachment.ID, token)
 | 
			
		||||
	req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{
 | 
			
		||||
		"name": newAttachmentName,
 | 
			
		||||
	})
 | 
			
		||||
	resp := session.MakeRequest(t, req, http.StatusCreated)
 | 
			
		||||
	apiAttachment := new(api.Attachment)
 | 
			
		||||
	DecodeJSON(t, resp, &apiAttachment)
 | 
			
		||||
 | 
			
		||||
	unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, CommentID: comment.ID, Name: apiAttachment.Name})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPIDeleteCommentAttachment(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 6})
 | 
			
		||||
	comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: attachment.CommentID})
 | 
			
		||||
	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
 | 
			
		||||
	repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, repoOwner.Name)
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session)
 | 
			
		||||
	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/assets/%d?token=%s",
 | 
			
		||||
		repoOwner.Name, repo.Name, comment.ID, attachment.ID, token)
 | 
			
		||||
 | 
			
		||||
	req := NewRequestf(t, "DELETE", urlStr)
 | 
			
		||||
	session.MakeRequest(t, req, http.StatusNoContent)
 | 
			
		||||
 | 
			
		||||
	unittest.AssertNotExistsBean(t, &repo_model.Attachment{ID: attachment.ID, CommentID: comment.ID})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										143
									
								
								tests/integration/api_issue_attachment_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								tests/integration/api_issue_attachment_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
			
		||||
// Copyright 2021 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package integration
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"mime/multipart"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	"code.gitea.io/gitea/models/unittest"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/tests"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestAPIGetIssueAttachment(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 1})
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: attachment.RepoID})
 | 
			
		||||
	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: attachment.IssueID})
 | 
			
		||||
	repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, repoOwner.Name)
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session)
 | 
			
		||||
	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d?token=%s",
 | 
			
		||||
		repoOwner.Name, repo.Name, issue.Index, attachment.ID, token)
 | 
			
		||||
 | 
			
		||||
	req := NewRequest(t, "GET", urlStr)
 | 
			
		||||
	resp := session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	apiAttachment := new(api.Attachment)
 | 
			
		||||
	DecodeJSON(t, resp, &apiAttachment)
 | 
			
		||||
 | 
			
		||||
	unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPIListIssueAttachments(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 1})
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: attachment.RepoID})
 | 
			
		||||
	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: attachment.IssueID})
 | 
			
		||||
	repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, repoOwner.Name)
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session)
 | 
			
		||||
	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets?token=%s",
 | 
			
		||||
		repoOwner.Name, repo.Name, issue.Index, token)
 | 
			
		||||
 | 
			
		||||
	req := NewRequest(t, "GET", urlStr)
 | 
			
		||||
	resp := session.MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
	apiAttachment := new([]api.Attachment)
 | 
			
		||||
	DecodeJSON(t, resp, &apiAttachment)
 | 
			
		||||
 | 
			
		||||
	unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: (*apiAttachment)[0].ID, IssueID: issue.ID})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPICreateIssueAttachment(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
 | 
			
		||||
	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
 | 
			
		||||
	repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, repoOwner.Name)
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session)
 | 
			
		||||
	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets?token=%s",
 | 
			
		||||
		repoOwner.Name, repo.Name, issue.Index, token)
 | 
			
		||||
 | 
			
		||||
	filename := "image.png"
 | 
			
		||||
	buff := generateImg()
 | 
			
		||||
	body := &bytes.Buffer{}
 | 
			
		||||
 | 
			
		||||
	// Setup multi-part
 | 
			
		||||
	writer := multipart.NewWriter(body)
 | 
			
		||||
	part, err := writer.CreateFormFile("attachment", filename)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	_, err = io.Copy(part, &buff)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	err = writer.Close()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	req := NewRequestWithBody(t, "POST", urlStr, body)
 | 
			
		||||
	req.Header.Add("Content-Type", writer.FormDataContentType())
 | 
			
		||||
	resp := session.MakeRequest(t, req, http.StatusCreated)
 | 
			
		||||
 | 
			
		||||
	apiAttachment := new(api.Attachment)
 | 
			
		||||
	DecodeJSON(t, resp, &apiAttachment)
 | 
			
		||||
 | 
			
		||||
	unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPIEditIssueAttachment(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	const newAttachmentName = "newAttachmentName"
 | 
			
		||||
 | 
			
		||||
	attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 1})
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: attachment.RepoID})
 | 
			
		||||
	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: attachment.IssueID})
 | 
			
		||||
	repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, repoOwner.Name)
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session)
 | 
			
		||||
	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d?token=%s",
 | 
			
		||||
		repoOwner.Name, repo.Name, issue.Index, attachment.ID, token)
 | 
			
		||||
	req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{
 | 
			
		||||
		"name": newAttachmentName,
 | 
			
		||||
	})
 | 
			
		||||
	resp := session.MakeRequest(t, req, http.StatusCreated)
 | 
			
		||||
	apiAttachment := new(api.Attachment)
 | 
			
		||||
	DecodeJSON(t, resp, &apiAttachment)
 | 
			
		||||
 | 
			
		||||
	unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: apiAttachment.ID, IssueID: issue.ID, Name: apiAttachment.Name})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestAPIDeleteIssueAttachment(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	attachment := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: 1})
 | 
			
		||||
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: attachment.RepoID})
 | 
			
		||||
	issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: attachment.IssueID})
 | 
			
		||||
	repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
 | 
			
		||||
 | 
			
		||||
	session := loginUser(t, repoOwner.Name)
 | 
			
		||||
	token := getTokenForLoggedInUser(t, session)
 | 
			
		||||
	urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/assets/%d?token=%s",
 | 
			
		||||
		repoOwner.Name, repo.Name, issue.Index, attachment.ID, token)
 | 
			
		||||
 | 
			
		||||
	req := NewRequest(t, "DELETE", urlStr)
 | 
			
		||||
	session.MakeRequest(t, req, http.StatusNoContent)
 | 
			
		||||
 | 
			
		||||
	unittest.AssertNotExistsBean(t, &repo_model.Attachment{ID: attachment.ID, IssueID: issue.ID})
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user