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 }