mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08: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)
 | 
									return fmt.Errorf("update attachment [%d]: %w", attachments[i].ID, err)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							comment.Attachments = attachments
 | 
				
			||||||
	case CommentTypeReopen, CommentTypeClose:
 | 
						case CommentTypeReopen, CommentTypeClose:
 | 
				
			||||||
		if err = repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.Issue.IsPull, true); err != nil {
 | 
							if err = repo_model.UpdateRepoIssueNumbers(ctx, opts.Issue.RepoID, opts.Issue.IsPull, true); err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,19 +30,19 @@ func DeleteOrphanedAttachments(x *xorm.Engine) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for {
 | 
						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`))").
 | 
							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).
 | 
								Cols("id, uuid").Limit(limit).
 | 
				
			||||||
			Asc("id").
 | 
								Asc("id").
 | 
				
			||||||
			Find(&attachements); err != nil {
 | 
								Find(&attachments); err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if len(attachements) == 0 {
 | 
							if len(attachments) == 0 {
 | 
				
			||||||
			return nil
 | 
								return nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ids := make([]int64, 0, limit)
 | 
							ids := make([]int64, 0, limit)
 | 
				
			||||||
		for _, attachment := range attachements {
 | 
							for _, attachment := range attachments {
 | 
				
			||||||
			ids = append(ids, attachment.ID)
 | 
								ids = append(ids, attachment.ID)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if len(ids) > 0 {
 | 
							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
 | 
								uuid := attachment.UUID
 | 
				
			||||||
			if err := util.RemoveAll(filepath.Join(setting.Attachment.Path, uuid[0:1], uuid[1:2], uuid)); err != nil {
 | 
								if err := util.RemoveAll(filepath.Join(setting.Attachment.Path, uuid[0:1], uuid[1:2], uuid)); err != nil {
 | 
				
			||||||
				return err
 | 
									return err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if len(attachements) < limit {
 | 
							if len(attachments) < limit {
 | 
				
			||||||
			return nil
 | 
								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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -44,6 +44,7 @@ func ToAPIIssue(ctx context.Context, issue *issues_model.Issue) *api.Issue {
 | 
				
			|||||||
		Poster:      ToUser(issue.Poster, nil),
 | 
							Poster:      ToUser(issue.Poster, nil),
 | 
				
			||||||
		Title:       issue.Title,
 | 
							Title:       issue.Title,
 | 
				
			||||||
		Body:        issue.Content,
 | 
							Body:        issue.Content,
 | 
				
			||||||
 | 
							Attachments: ToAttachments(issue.Attachments),
 | 
				
			||||||
		Ref:         issue.Ref,
 | 
							Ref:         issue.Ref,
 | 
				
			||||||
		Labels:      ToLabelList(issue.Labels, issue.Repo, issue.Repo.Owner),
 | 
							Labels:      ToLabelList(issue.Labels, issue.Repo, issue.Repo.Owner),
 | 
				
			||||||
		State:       issue.State(),
 | 
							State:       issue.State(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,7 @@ func ToComment(c *issues_model.Comment) *api.Comment {
 | 
				
			|||||||
		IssueURL:    c.IssueURL(),
 | 
							IssueURL:    c.IssueURL(),
 | 
				
			||||||
		PRURL:       c.PRURL(),
 | 
							PRURL:       c.PRURL(),
 | 
				
			||||||
		Body:        c.Content,
 | 
							Body:        c.Content,
 | 
				
			||||||
 | 
							Attachments: ToAttachments(c.Attachments),
 | 
				
			||||||
		Created:     c.CreatedUnix.AsTime(),
 | 
							Created:     c.CreatedUnix.AsTime(),
 | 
				
			||||||
		Updated:     c.UpdatedUnix.AsTime(),
 | 
							Updated:     c.UpdatedUnix.AsTime(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,10 +10,6 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// ToRelease convert a repo_model.Release to api.Release
 | 
					// ToRelease convert a repo_model.Release to api.Release
 | 
				
			||||||
func ToRelease(r *repo_model.Release) *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{
 | 
						return &api.Release{
 | 
				
			||||||
		ID:           r.ID,
 | 
							ID:           r.ID,
 | 
				
			||||||
		TagName:      r.TagName,
 | 
							TagName:      r.TagName,
 | 
				
			||||||
@@ -29,19 +25,6 @@ func ToRelease(r *repo_model.Release) *api.Release {
 | 
				
			|||||||
		CreatedAt:    r.CreatedUnix.AsTime(),
 | 
							CreatedAt:    r.CreatedUnix.AsTime(),
 | 
				
			||||||
		PublishedAt:  r.CreatedUnix.AsTime(),
 | 
							PublishedAt:  r.CreatedUnix.AsTime(),
 | 
				
			||||||
		Publisher:    ToUser(r.Publisher, nil),
 | 
							Publisher:    ToUser(r.Publisher, nil),
 | 
				
			||||||
		Attachments:  assets,
 | 
							Attachments:  ToAttachments(r.Attachments),
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 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(),
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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) {
 | 
					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)
 | 
						mode, _ := access_model.AccessLevel(ctx, issue.Poster, issue.Repo)
 | 
				
			||||||
	var err error
 | 
						var err error
 | 
				
			||||||
	if issue.IsPull {
 | 
						if issue.IsPull {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,6 +51,7 @@ type Issue struct {
 | 
				
			|||||||
	Title            string        `json:"title"`
 | 
						Title            string        `json:"title"`
 | 
				
			||||||
	Body             string        `json:"body"`
 | 
						Body             string        `json:"body"`
 | 
				
			||||||
	Ref              string        `json:"ref"`
 | 
						Ref              string        `json:"ref"`
 | 
				
			||||||
 | 
						Attachments      []*Attachment `json:"assets"`
 | 
				
			||||||
	Labels           []*Label      `json:"labels"`
 | 
						Labels           []*Label      `json:"labels"`
 | 
				
			||||||
	Milestone        *Milestone    `json:"milestone"`
 | 
						Milestone        *Milestone    `json:"milestone"`
 | 
				
			||||||
	// deprecated
 | 
						// deprecated
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,7 @@ type Comment struct {
 | 
				
			|||||||
	OriginalAuthor   string        `json:"original_author"`
 | 
						OriginalAuthor   string        `json:"original_author"`
 | 
				
			||||||
	OriginalAuthorID int64         `json:"original_author_id"`
 | 
						OriginalAuthorID int64         `json:"original_author_id"`
 | 
				
			||||||
	Body             string        `json:"body"`
 | 
						Body             string        `json:"body"`
 | 
				
			||||||
 | 
						Attachments      []*Attachment `json:"assets"`
 | 
				
			||||||
	// swagger:strfmt date-time
 | 
						// swagger:strfmt date-time
 | 
				
			||||||
	Created time.Time `json:"created_at"`
 | 
						Created time.Time `json:"created_at"`
 | 
				
			||||||
	// swagger:strfmt date-time
 | 
						// 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)
 | 
					// bind binding an obj to a func(ctx *context.APIContext)
 | 
				
			||||||
func bind(obj interface{}) http.HandlerFunc {
 | 
					func bind(obj interface{}) http.HandlerFunc {
 | 
				
			||||||
	tp := reflect.TypeOf(obj)
 | 
						tp := reflect.TypeOf(obj)
 | 
				
			||||||
@@ -892,6 +899,15 @@ func Routes(ctx gocontext.Context) *web.Route {
 | 
				
			|||||||
								Get(repo.GetIssueCommentReactions).
 | 
													Get(repo.GetIssueCommentReactions).
 | 
				
			||||||
								Post(reqToken(), bind(api.EditReactionOption{}), repo.PostIssueCommentReaction).
 | 
													Post(reqToken(), bind(api.EditReactionOption{}), repo.PostIssueCommentReaction).
 | 
				
			||||||
								Delete(reqToken(), bind(api.EditReactionOption{}), repo.DeleteIssueCommentReaction)
 | 
													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() {
 | 
										m.Group("/{index}", func() {
 | 
				
			||||||
@@ -935,6 +951,15 @@ func Routes(ctx gocontext.Context) *web.Route {
 | 
				
			|||||||
							Get(repo.GetIssueReactions).
 | 
												Get(repo.GetIssueReactions).
 | 
				
			||||||
							Post(reqToken(), bind(api.EditReactionOption{}), repo.PostIssueReaction).
 | 
												Post(reqToken(), bind(api.EditReactionOption{}), repo.PostIssueReaction).
 | 
				
			||||||
							Delete(reqToken(), bind(api.EditReactionOption{}), repo.DeleteIssueReaction)
 | 
												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)
 | 
									}, mustEnableIssuesOrPulls)
 | 
				
			||||||
				m.Group("/labels", func() {
 | 
									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
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := issues_model.CommentList(comments).LoadAttachments(ctx); err != nil {
 | 
				
			||||||
 | 
							ctx.Error(http.StatusInternalServerError, "LoadAttachments", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	apiComments := make([]*api.Comment, len(comments))
 | 
						apiComments := make([]*api.Comment, len(comments))
 | 
				
			||||||
	for i, comment := range comments {
 | 
						for i, comment := range comments {
 | 
				
			||||||
		comment.Issue = issue
 | 
							comment.Issue = issue
 | 
				
			||||||
@@ -294,6 +299,10 @@ func ListRepoIssueComments(ctx *context.APIContext) {
 | 
				
			|||||||
		ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
 | 
							ctx.Error(http.StatusInternalServerError, "LoadPosters", err)
 | 
				
			||||||
		return
 | 
							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 {
 | 
						if _, err := issues_model.CommentList(comments).Issues().LoadRepositories(ctx); err != nil {
 | 
				
			||||||
		ctx.Error(http.StatusInternalServerError, "LoadRepositories", err)
 | 
							ctx.Error(http.StatusInternalServerError, "LoadRepositories", err)
 | 
				
			||||||
		return
 | 
							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
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// FIXME Should prove the existence of the given repo, but results in unnecessary database requests
 | 
						// 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
 | 
					// ListReleaseAttachments lists all attachments of the release
 | 
				
			||||||
@@ -194,7 +194,12 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Create a new attachment and save the file
 | 
						// 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 err != nil {
 | 
				
			||||||
		if upload.IsErrFileTypeForbidden(err) {
 | 
							if upload.IsErrFileTypeForbidden(err) {
 | 
				
			||||||
			ctx.Error(http.StatusBadRequest, "DetectContentType", err)
 | 
								ctx.Error(http.StatusBadRequest, "DetectContentType", err)
 | 
				
			||||||
@@ -204,7 +209,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.JSON(http.StatusCreated, convert.ToReleaseAttachment(attach))
 | 
						ctx.JSON(http.StatusCreated, convert.ToAttachment(attach))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// EditReleaseAttachment updates the given attachment
 | 
					// EditReleaseAttachment updates the given attachment
 | 
				
			||||||
@@ -274,7 +279,7 @@ func EditReleaseAttachment(ctx *context.APIContext) {
 | 
				
			|||||||
	if err := repo_model.UpdateAttachment(ctx, attach); err != nil {
 | 
						if err := repo_model.UpdateAttachment(ctx, attach); err != nil {
 | 
				
			||||||
		ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach)
 | 
							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
 | 
					// DeleteReleaseAttachment delete a given attachment
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,7 +44,11 @@ func uploadAttachment(ctx *context.Context, repoID int64, allowedTypes string) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	defer file.Close()
 | 
						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 err != nil {
 | 
				
			||||||
		if upload.IsErrFileTypeForbidden(err) {
 | 
							if upload.IsErrFileTypeForbidden(err) {
 | 
				
			||||||
			ctx.Error(http.StatusBadRequest, err.Error())
 | 
								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) {
 | 
					func GetAttachment(ctx *context.Context) {
 | 
				
			||||||
	attach, err := repo_model.GetAttachmentByUUID(ctx, ctx.Params(":uuid"))
 | 
						attach, err := repo_model.GetAttachmentByUUID(ctx, ctx.Params(":uuid"))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2749,6 +2749,7 @@ func UpdateCommentContent(ctx *context.Context) {
 | 
				
			|||||||
		})
 | 
							})
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err = comment_service.UpdateComment(ctx, comment, ctx.Doer, oldContent); err != nil {
 | 
						if err = comment_service.UpdateComment(ctx, comment, ctx.Doer, oldContent); err != nil {
 | 
				
			||||||
		ctx.ServerError("UpdateComment", err)
 | 
							ctx.ServerError("UpdateComment", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@@ -3050,7 +3051,7 @@ func GetIssueAttachments(ctx *context.Context) {
 | 
				
			|||||||
	issue := GetActionIssue(ctx)
 | 
						issue := GetActionIssue(ctx)
 | 
				
			||||||
	attachments := make([]*api.Attachment, len(issue.Attachments))
 | 
						attachments := make([]*api.Attachment, len(issue.Attachments))
 | 
				
			||||||
	for i := 0; i < len(issue.Attachments); i++ {
 | 
						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)
 | 
						ctx.JSON(http.StatusOK, attachments)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -3069,7 +3070,7 @@ func GetCommentAttachments(ctx *context.Context) {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		for i := 0; i < len(comment.Attachments); i++ {
 | 
							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)
 | 
						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
 | 
					// 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)
 | 
						buf := make([]byte, 1024)
 | 
				
			||||||
	n, _ := util.ReadAtMost(file, buf)
 | 
						n, _ := util.ReadAtMost(file, buf)
 | 
				
			||||||
	buf = buf[:n]
 | 
						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 nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return NewAttachment(&repo_model.Attachment{
 | 
						return NewAttachment(opts, io.MultiReader(bytes.NewReader(buf), file))
 | 
				
			||||||
		RepoID:     repoID,
 | 
					 | 
				
			||||||
		UploaderID: actorID,
 | 
					 | 
				
			||||||
		ReleaseID:  releaseID,
 | 
					 | 
				
			||||||
		Name:       fileName,
 | 
					 | 
				
			||||||
	}, io.MultiReader(bytes.NewReader(buf), file))
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/repository"
 | 
						"code.gitea.io/gitea/modules/repository"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/storage"
 | 
						"code.gitea.io/gitea/modules/storage"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
						"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) {
 | 
					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 {
 | 
							for _, attach := range attachments {
 | 
				
			||||||
			if attach.ReleaseID != rel.ID {
 | 
								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)
 | 
								deletedUUIDs.Add(attach.UUID)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -240,7 +244,10 @@ func UpdateRelease(doer *user_model.User, gitRepo *git.Repository, rel *repo_mod
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		for _, attach := range attachments {
 | 
							for _, attach := range attachments {
 | 
				
			||||||
			if attach.ReleaseID != rel.ID {
 | 
								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": {
 | 
					    "/repos/{owner}/{repo}/issues/comments/{id}/reactions": {
 | 
				
			||||||
      "get": {
 | 
					      "get": {
 | 
				
			||||||
        "consumes": [
 | 
					        "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": {
 | 
					    "/repos/{owner}/{repo}/issues/{index}/comments": {
 | 
				
			||||||
      "get": {
 | 
					      "get": {
 | 
				
			||||||
        "produces": [
 | 
					        "produces": [
 | 
				
			||||||
@@ -13882,6 +14416,13 @@
 | 
				
			|||||||
      "description": "Comment represents a comment on a commit or issue",
 | 
					      "description": "Comment represents a comment on a commit or issue",
 | 
				
			||||||
      "type": "object",
 | 
					      "type": "object",
 | 
				
			||||||
      "properties": {
 | 
					      "properties": {
 | 
				
			||||||
 | 
					        "assets": {
 | 
				
			||||||
 | 
					          "type": "array",
 | 
				
			||||||
 | 
					          "items": {
 | 
				
			||||||
 | 
					            "$ref": "#/definitions/Attachment"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "x-go-name": "Attachments"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "body": {
 | 
					        "body": {
 | 
				
			||||||
          "type": "string",
 | 
					          "type": "string",
 | 
				
			||||||
          "x-go-name": "Body"
 | 
					          "x-go-name": "Body"
 | 
				
			||||||
@@ -16634,6 +17175,13 @@
 | 
				
			|||||||
      "description": "Issue represents an issue in a repository",
 | 
					      "description": "Issue represents an issue in a repository",
 | 
				
			||||||
      "type": "object",
 | 
					      "type": "object",
 | 
				
			||||||
      "properties": {
 | 
					      "properties": {
 | 
				
			||||||
 | 
					        "assets": {
 | 
				
			||||||
 | 
					          "type": "array",
 | 
				
			||||||
 | 
					          "items": {
 | 
				
			||||||
 | 
					            "$ref": "#/definitions/Attachment"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "x-go-name": "Attachments"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "assignee": {
 | 
					        "assignee": {
 | 
				
			||||||
          "$ref": "#/definitions/User"
 | 
					          "$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