mirror of https://github.com/go-gitea/gitea
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>pull/22080/head
parent
8fb1e53ca2
commit
3c59d31bc6
@ -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 |
||||||
|
} |
@ -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 |
||||||
|
} |
@ -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 |
||||||
|
} |
@ -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}) |
||||||
|
} |
@ -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 new issue