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:
parent
8fb1e53ca2
commit
3c59d31bc6
21 changed files with 1754 additions and 84 deletions
|
@ -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
|
||||||
|
}
|
|
@ -37,20 +37,21 @@ func ToAPIIssue(ctx context.Context, issue *issues_model.Issue) *api.Issue {
|
||||||
}
|
}
|
||||||
|
|
||||||
apiIssue := &api.Issue{
|
apiIssue := &api.Issue{
|
||||||
ID: issue.ID,
|
ID: issue.ID,
|
||||||
URL: issue.APIURL(),
|
URL: issue.APIURL(),
|
||||||
HTMLURL: issue.HTMLURL(),
|
HTMLURL: issue.HTMLURL(),
|
||||||
Index: issue.Index,
|
Index: issue.Index,
|
||||||
Poster: ToUser(issue.Poster, nil),
|
Poster: ToUser(issue.Poster, nil),
|
||||||
Title: issue.Title,
|
Title: issue.Title,
|
||||||
Body: issue.Content,
|
Body: issue.Content,
|
||||||
Ref: issue.Ref,
|
Attachments: ToAttachments(issue.Attachments),
|
||||||
Labels: ToLabelList(issue.Labels, issue.Repo, issue.Repo.Owner),
|
Ref: issue.Ref,
|
||||||
State: issue.State(),
|
Labels: ToLabelList(issue.Labels, issue.Repo, issue.Repo.Owner),
|
||||||
IsLocked: issue.IsLocked,
|
State: issue.State(),
|
||||||
Comments: issue.NumComments,
|
IsLocked: issue.IsLocked,
|
||||||
Created: issue.CreatedUnix.AsTime(),
|
Comments: issue.NumComments,
|
||||||
Updated: issue.UpdatedUnix.AsTime(),
|
Created: issue.CreatedUnix.AsTime(),
|
||||||
|
Updated: issue.UpdatedUnix.AsTime(),
|
||||||
}
|
}
|
||||||
|
|
||||||
apiIssue.Repo = &api.RepositoryMeta{
|
apiIssue.Repo = &api.RepositoryMeta{
|
||||||
|
|
|
@ -16,14 +16,15 @@ import (
|
||||||
// ToComment converts a issues_model.Comment to the api.Comment format
|
// ToComment converts a issues_model.Comment to the api.Comment format
|
||||||
func ToComment(c *issues_model.Comment) *api.Comment {
|
func ToComment(c *issues_model.Comment) *api.Comment {
|
||||||
return &api.Comment{
|
return &api.Comment{
|
||||||
ID: c.ID,
|
ID: c.ID,
|
||||||
Poster: ToUser(c.Poster, nil),
|
Poster: ToUser(c.Poster, nil),
|
||||||
HTMLURL: c.HTMLURL(),
|
HTMLURL: c.HTMLURL(),
|
||||||
IssueURL: c.IssueURL(),
|
IssueURL: c.IssueURL(),
|
||||||
PRURL: c.PRURL(),
|
PRURL: c.PRURL(),
|
||||||
Body: c.Content,
|
Body: c.Content,
|
||||||
Created: c.CreatedUnix.AsTime(),
|
Attachments: ToAttachments(c.Attachments),
|
||||||
Updated: c.UpdatedUnix.AsTime(),
|
Created: c.CreatedUnix.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 {
|
||||||
|
|
|
@ -41,18 +41,19 @@ type RepositoryMeta struct {
|
||||||
// Issue represents an issue in a repository
|
// Issue represents an issue in a repository
|
||||||
// swagger:model
|
// swagger:model
|
||||||
type Issue struct {
|
type Issue struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
HTMLURL string `json:"html_url"`
|
HTMLURL string `json:"html_url"`
|
||||||
Index int64 `json:"number"`
|
Index int64 `json:"number"`
|
||||||
Poster *User `json:"user"`
|
Poster *User `json:"user"`
|
||||||
OriginalAuthor string `json:"original_author"`
|
OriginalAuthor string `json:"original_author"`
|
||||||
OriginalAuthorID int64 `json:"original_author_id"`
|
OriginalAuthorID int64 `json:"original_author_id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
Ref string `json:"ref"`
|
Ref string `json:"ref"`
|
||||||
Labels []*Label `json:"labels"`
|
Attachments []*Attachment `json:"assets"`
|
||||||
Milestone *Milestone `json:"milestone"`
|
Labels []*Label `json:"labels"`
|
||||||
|
Milestone *Milestone `json:"milestone"`
|
||||||
// deprecated
|
// deprecated
|
||||||
Assignee *User `json:"assignee"`
|
Assignee *User `json:"assignee"`
|
||||||
Assignees []*User `json:"assignees"`
|
Assignees []*User `json:"assignees"`
|
||||||
|
|
|
@ -9,14 +9,15 @@ import (
|
||||||
|
|
||||||
// Comment represents a comment on a commit or issue
|
// Comment represents a comment on a commit or issue
|
||||||
type Comment struct {
|
type Comment struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
HTMLURL string `json:"html_url"`
|
HTMLURL string `json:"html_url"`
|
||||||
PRURL string `json:"pull_request_url"`
|
PRURL string `json:"pull_request_url"`
|
||||||
IssueURL string `json:"issue_url"`
|
IssueURL string `json:"issue_url"`
|
||||||
Poster *User `json:"user"`
|
Poster *User `json:"user"`
|
||||||
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})
|
||||||
|
}
|
Loading…
Reference in a new issue