code.gitea.io/gitea@v1.22.3/routers/api/v1/repo/release_attachment.go (about)

     1  // Copyright 2018 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package repo
     5  
     6  import (
     7  	"io"
     8  	"net/http"
     9  	"strings"
    10  
    11  	repo_model "code.gitea.io/gitea/models/repo"
    12  	"code.gitea.io/gitea/modules/log"
    13  	"code.gitea.io/gitea/modules/setting"
    14  	api "code.gitea.io/gitea/modules/structs"
    15  	"code.gitea.io/gitea/modules/web"
    16  	"code.gitea.io/gitea/services/attachment"
    17  	"code.gitea.io/gitea/services/context"
    18  	"code.gitea.io/gitea/services/context/upload"
    19  	"code.gitea.io/gitea/services/convert"
    20  )
    21  
    22  func checkReleaseMatchRepo(ctx *context.APIContext, releaseID int64) bool {
    23  	release, err := repo_model.GetReleaseByID(ctx, releaseID)
    24  	if err != nil {
    25  		if repo_model.IsErrReleaseNotExist(err) {
    26  			ctx.NotFound()
    27  			return false
    28  		}
    29  		ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
    30  		return false
    31  	}
    32  	if release.RepoID != ctx.Repo.Repository.ID {
    33  		ctx.NotFound()
    34  		return false
    35  	}
    36  	return true
    37  }
    38  
    39  // GetReleaseAttachment gets a single attachment of the release
    40  func GetReleaseAttachment(ctx *context.APIContext) {
    41  	// swagger:operation GET /repos/{owner}/{repo}/releases/{id}/assets/{attachment_id} repository repoGetReleaseAttachment
    42  	// ---
    43  	// summary: Get a release attachment
    44  	// produces:
    45  	// - application/json
    46  	// parameters:
    47  	// - name: owner
    48  	//   in: path
    49  	//   description: owner of the repo
    50  	//   type: string
    51  	//   required: true
    52  	// - name: repo
    53  	//   in: path
    54  	//   description: name of the repo
    55  	//   type: string
    56  	//   required: true
    57  	// - name: id
    58  	//   in: path
    59  	//   description: id of the release
    60  	//   type: integer
    61  	//   format: int64
    62  	//   required: true
    63  	// - name: attachment_id
    64  	//   in: path
    65  	//   description: id of the attachment to get
    66  	//   type: integer
    67  	//   format: int64
    68  	//   required: true
    69  	// responses:
    70  	//   "200":
    71  	//     "$ref": "#/responses/Attachment"
    72  	//   "404":
    73  	//     "$ref": "#/responses/notFound"
    74  
    75  	releaseID := ctx.ParamsInt64(":id")
    76  	if !checkReleaseMatchRepo(ctx, releaseID) {
    77  		return
    78  	}
    79  
    80  	attachID := ctx.ParamsInt64(":attachment_id")
    81  	attach, err := repo_model.GetAttachmentByID(ctx, attachID)
    82  	if err != nil {
    83  		if repo_model.IsErrAttachmentNotExist(err) {
    84  			ctx.NotFound()
    85  			return
    86  		}
    87  		ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err)
    88  		return
    89  	}
    90  	if attach.ReleaseID != releaseID {
    91  		log.Info("User requested attachment is not in release, release_id %v, attachment_id: %v", releaseID, attachID)
    92  		ctx.NotFound()
    93  		return
    94  	}
    95  	// FIXME Should prove the existence of the given repo, but results in unnecessary database requests
    96  	ctx.JSON(http.StatusOK, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
    97  }
    98  
    99  // ListReleaseAttachments lists all attachments of the release
   100  func ListReleaseAttachments(ctx *context.APIContext) {
   101  	// swagger:operation GET /repos/{owner}/{repo}/releases/{id}/assets repository repoListReleaseAttachments
   102  	// ---
   103  	// summary: List release's attachments
   104  	// produces:
   105  	// - application/json
   106  	// parameters:
   107  	// - name: owner
   108  	//   in: path
   109  	//   description: owner of the repo
   110  	//   type: string
   111  	//   required: true
   112  	// - name: repo
   113  	//   in: path
   114  	//   description: name of the repo
   115  	//   type: string
   116  	//   required: true
   117  	// - name: id
   118  	//   in: path
   119  	//   description: id of the release
   120  	//   type: integer
   121  	//   format: int64
   122  	//   required: true
   123  	// responses:
   124  	//   "200":
   125  	//     "$ref": "#/responses/AttachmentList"
   126  	//   "404":
   127  	//     "$ref": "#/responses/notFound"
   128  
   129  	releaseID := ctx.ParamsInt64(":id")
   130  	release, err := repo_model.GetReleaseByID(ctx, releaseID)
   131  	if err != nil {
   132  		if repo_model.IsErrReleaseNotExist(err) {
   133  			ctx.NotFound()
   134  			return
   135  		}
   136  		ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
   137  		return
   138  	}
   139  	if release.RepoID != ctx.Repo.Repository.ID {
   140  		ctx.NotFound()
   141  		return
   142  	}
   143  	if err := release.LoadAttributes(ctx); err != nil {
   144  		ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
   145  		return
   146  	}
   147  	ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release).Attachments)
   148  }
   149  
   150  // CreateReleaseAttachment creates an attachment and saves the given file
   151  func CreateReleaseAttachment(ctx *context.APIContext) {
   152  	// swagger:operation POST /repos/{owner}/{repo}/releases/{id}/assets repository repoCreateReleaseAttachment
   153  	// ---
   154  	// summary: Create a release attachment
   155  	// produces:
   156  	// - application/json
   157  	// consumes:
   158  	// - multipart/form-data
   159  	// - application/octet-stream
   160  	// parameters:
   161  	// - name: owner
   162  	//   in: path
   163  	//   description: owner of the repo
   164  	//   type: string
   165  	//   required: true
   166  	// - name: repo
   167  	//   in: path
   168  	//   description: name of the repo
   169  	//   type: string
   170  	//   required: true
   171  	// - name: id
   172  	//   in: path
   173  	//   description: id of the release
   174  	//   type: integer
   175  	//   format: int64
   176  	//   required: true
   177  	// - name: name
   178  	//   in: query
   179  	//   description: name of the attachment
   180  	//   type: string
   181  	//   required: false
   182  	// - name: attachment
   183  	//   in: formData
   184  	//   description: attachment to upload
   185  	//   type: file
   186  	//   required: false
   187  	// responses:
   188  	//   "201":
   189  	//     "$ref": "#/responses/Attachment"
   190  	//   "400":
   191  	//     "$ref": "#/responses/error"
   192  	//   "404":
   193  	//     "$ref": "#/responses/notFound"
   194  
   195  	// Check if attachments are enabled
   196  	if !setting.Attachment.Enabled {
   197  		ctx.NotFound("Attachment is not enabled")
   198  		return
   199  	}
   200  
   201  	// Check if release exists an load release
   202  	releaseID := ctx.ParamsInt64(":id")
   203  	if !checkReleaseMatchRepo(ctx, releaseID) {
   204  		return
   205  	}
   206  
   207  	// Get uploaded file from request
   208  	var content io.ReadCloser
   209  	var filename string
   210  	var size int64 = -1
   211  
   212  	if strings.HasPrefix(strings.ToLower(ctx.Req.Header.Get("Content-Type")), "multipart/form-data") {
   213  		file, header, err := ctx.Req.FormFile("attachment")
   214  		if err != nil {
   215  			ctx.Error(http.StatusInternalServerError, "GetFile", err)
   216  			return
   217  		}
   218  		defer file.Close()
   219  
   220  		content = file
   221  		size = header.Size
   222  		filename = header.Filename
   223  		if name := ctx.FormString("name"); name != "" {
   224  			filename = name
   225  		}
   226  	} else {
   227  		content = ctx.Req.Body
   228  		filename = ctx.FormString("name")
   229  	}
   230  
   231  	if filename == "" {
   232  		ctx.Error(http.StatusBadRequest, "CreateReleaseAttachment", "Could not determine name of attachment.")
   233  		return
   234  	}
   235  
   236  	// Create a new attachment and save the file
   237  	attach, err := attachment.UploadAttachment(ctx, content, setting.Repository.Release.AllowedTypes, size, &repo_model.Attachment{
   238  		Name:       filename,
   239  		UploaderID: ctx.Doer.ID,
   240  		RepoID:     ctx.Repo.Repository.ID,
   241  		ReleaseID:  releaseID,
   242  	})
   243  	if err != nil {
   244  		if upload.IsErrFileTypeForbidden(err) {
   245  			ctx.Error(http.StatusBadRequest, "DetectContentType", err)
   246  			return
   247  		}
   248  		ctx.Error(http.StatusInternalServerError, "NewAttachment", err)
   249  		return
   250  	}
   251  
   252  	ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
   253  }
   254  
   255  // EditReleaseAttachment updates the given attachment
   256  func EditReleaseAttachment(ctx *context.APIContext) {
   257  	// swagger:operation PATCH /repos/{owner}/{repo}/releases/{id}/assets/{attachment_id} repository repoEditReleaseAttachment
   258  	// ---
   259  	// summary: Edit a release attachment
   260  	// produces:
   261  	// - application/json
   262  	// consumes:
   263  	// - application/json
   264  	// parameters:
   265  	// - name: owner
   266  	//   in: path
   267  	//   description: owner of the repo
   268  	//   type: string
   269  	//   required: true
   270  	// - name: repo
   271  	//   in: path
   272  	//   description: name of the repo
   273  	//   type: string
   274  	//   required: true
   275  	// - name: id
   276  	//   in: path
   277  	//   description: id of the release
   278  	//   type: integer
   279  	//   format: int64
   280  	//   required: true
   281  	// - name: attachment_id
   282  	//   in: path
   283  	//   description: id of the attachment to edit
   284  	//   type: integer
   285  	//   format: int64
   286  	//   required: true
   287  	// - name: body
   288  	//   in: body
   289  	//   schema:
   290  	//     "$ref": "#/definitions/EditAttachmentOptions"
   291  	// responses:
   292  	//   "201":
   293  	//     "$ref": "#/responses/Attachment"
   294  	//   "404":
   295  	//     "$ref": "#/responses/notFound"
   296  
   297  	form := web.GetForm(ctx).(*api.EditAttachmentOptions)
   298  
   299  	// Check if release exists an load release
   300  	releaseID := ctx.ParamsInt64(":id")
   301  	if !checkReleaseMatchRepo(ctx, releaseID) {
   302  		return
   303  	}
   304  
   305  	attachID := ctx.ParamsInt64(":attachment_id")
   306  	attach, err := repo_model.GetAttachmentByID(ctx, attachID)
   307  	if err != nil {
   308  		if repo_model.IsErrAttachmentNotExist(err) {
   309  			ctx.NotFound()
   310  			return
   311  		}
   312  		ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err)
   313  		return
   314  	}
   315  	if attach.ReleaseID != releaseID {
   316  		log.Info("User requested attachment is not in release, release_id %v, attachment_id: %v", releaseID, attachID)
   317  		ctx.NotFound()
   318  		return
   319  	}
   320  	// FIXME Should prove the existence of the given repo, but results in unnecessary database requests
   321  	if form.Name != "" {
   322  		attach.Name = form.Name
   323  	}
   324  
   325  	if err := repo_model.UpdateAttachment(ctx, attach); err != nil {
   326  		ctx.Error(http.StatusInternalServerError, "UpdateAttachment", attach)
   327  	}
   328  	ctx.JSON(http.StatusCreated, convert.ToAPIAttachment(ctx.Repo.Repository, attach))
   329  }
   330  
   331  // DeleteReleaseAttachment delete a given attachment
   332  func DeleteReleaseAttachment(ctx *context.APIContext) {
   333  	// swagger:operation DELETE /repos/{owner}/{repo}/releases/{id}/assets/{attachment_id} repository repoDeleteReleaseAttachment
   334  	// ---
   335  	// summary: Delete a release attachment
   336  	// produces:
   337  	// - application/json
   338  	// parameters:
   339  	// - name: owner
   340  	//   in: path
   341  	//   description: owner of the repo
   342  	//   type: string
   343  	//   required: true
   344  	// - name: repo
   345  	//   in: path
   346  	//   description: name of the repo
   347  	//   type: string
   348  	//   required: true
   349  	// - name: id
   350  	//   in: path
   351  	//   description: id of the release
   352  	//   type: integer
   353  	//   format: int64
   354  	//   required: true
   355  	// - name: attachment_id
   356  	//   in: path
   357  	//   description: id of the attachment to delete
   358  	//   type: integer
   359  	//   format: int64
   360  	//   required: true
   361  	// responses:
   362  	//   "204":
   363  	//     "$ref": "#/responses/empty"
   364  	//   "404":
   365  	//     "$ref": "#/responses/notFound"
   366  
   367  	// Check if release exists an load release
   368  	releaseID := ctx.ParamsInt64(":id")
   369  	if !checkReleaseMatchRepo(ctx, releaseID) {
   370  		return
   371  	}
   372  
   373  	attachID := ctx.ParamsInt64(":attachment_id")
   374  	attach, err := repo_model.GetAttachmentByID(ctx, attachID)
   375  	if err != nil {
   376  		if repo_model.IsErrAttachmentNotExist(err) {
   377  			ctx.NotFound()
   378  			return
   379  		}
   380  		ctx.Error(http.StatusInternalServerError, "GetAttachmentByID", err)
   381  		return
   382  	}
   383  	if attach.ReleaseID != releaseID {
   384  		log.Info("User requested attachment is not in release, release_id %v, attachment_id: %v", releaseID, attachID)
   385  		ctx.NotFound()
   386  		return
   387  	}
   388  	// FIXME Should prove the existence of the given repo, but results in unnecessary database requests
   389  
   390  	if err := repo_model.DeleteAttachment(ctx, attach, true); err != nil {
   391  		ctx.Error(http.StatusInternalServerError, "DeleteAttachment", err)
   392  		return
   393  	}
   394  	ctx.Status(http.StatusNoContent)
   395  }