code.gitea.io/gitea@v1.22.3/routers/api/v1/repo/release.go (about) 1 // Copyright 2016 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package repo 5 6 import ( 7 "fmt" 8 "net/http" 9 10 "code.gitea.io/gitea/models" 11 "code.gitea.io/gitea/models/db" 12 "code.gitea.io/gitea/models/perm" 13 repo_model "code.gitea.io/gitea/models/repo" 14 "code.gitea.io/gitea/models/unit" 15 "code.gitea.io/gitea/modules/git" 16 api "code.gitea.io/gitea/modules/structs" 17 "code.gitea.io/gitea/modules/web" 18 "code.gitea.io/gitea/routers/api/v1/utils" 19 "code.gitea.io/gitea/services/context" 20 "code.gitea.io/gitea/services/convert" 21 release_service "code.gitea.io/gitea/services/release" 22 ) 23 24 // GetRelease get a single release of a repository 25 func GetRelease(ctx *context.APIContext) { 26 // swagger:operation GET /repos/{owner}/{repo}/releases/{id} repository repoGetRelease 27 // --- 28 // summary: Get a release 29 // produces: 30 // - application/json 31 // parameters: 32 // - name: owner 33 // in: path 34 // description: owner of the repo 35 // type: string 36 // required: true 37 // - name: repo 38 // in: path 39 // description: name of the repo 40 // type: string 41 // required: true 42 // - name: id 43 // in: path 44 // description: id of the release to get 45 // type: integer 46 // format: int64 47 // required: true 48 // responses: 49 // "200": 50 // "$ref": "#/responses/Release" 51 // "404": 52 // "$ref": "#/responses/notFound" 53 54 id := ctx.ParamsInt64(":id") 55 release, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id) 56 if err != nil && !repo_model.IsErrReleaseNotExist(err) { 57 ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err) 58 return 59 } 60 if err != nil && repo_model.IsErrReleaseNotExist(err) || release.IsTag { 61 ctx.NotFound() 62 return 63 } 64 65 if err := release.LoadAttributes(ctx); err != nil { 66 ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) 67 return 68 } 69 ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release)) 70 } 71 72 // GetLatestRelease gets the most recent non-prerelease, non-draft release of a repository, sorted by created_at 73 func GetLatestRelease(ctx *context.APIContext) { 74 // swagger:operation GET /repos/{owner}/{repo}/releases/latest repository repoGetLatestRelease 75 // --- 76 // summary: Gets the most recent non-prerelease, non-draft release of a repository, sorted by created_at 77 // produces: 78 // - application/json 79 // parameters: 80 // - name: owner 81 // in: path 82 // description: owner of the repo 83 // type: string 84 // required: true 85 // - name: repo 86 // in: path 87 // description: name of the repo 88 // type: string 89 // required: true 90 // responses: 91 // "200": 92 // "$ref": "#/responses/Release" 93 // "404": 94 // "$ref": "#/responses/notFound" 95 release, err := repo_model.GetLatestReleaseByRepoID(ctx, ctx.Repo.Repository.ID) 96 if err != nil && !repo_model.IsErrReleaseNotExist(err) { 97 ctx.Error(http.StatusInternalServerError, "GetLatestRelease", err) 98 return 99 } 100 if err != nil && repo_model.IsErrReleaseNotExist(err) || 101 release.IsTag || release.RepoID != ctx.Repo.Repository.ID { 102 ctx.NotFound() 103 return 104 } 105 106 if err := release.LoadAttributes(ctx); err != nil { 107 ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) 108 return 109 } 110 ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, release)) 111 } 112 113 // ListReleases list a repository's releases 114 func ListReleases(ctx *context.APIContext) { 115 // swagger:operation GET /repos/{owner}/{repo}/releases repository repoListReleases 116 // --- 117 // summary: List a repo's releases 118 // produces: 119 // - application/json 120 // parameters: 121 // - name: owner 122 // in: path 123 // description: owner of the repo 124 // type: string 125 // required: true 126 // - name: repo 127 // in: path 128 // description: name of the repo 129 // type: string 130 // required: true 131 // - name: draft 132 // in: query 133 // description: filter (exclude / include) drafts, if you dont have repo write access none will show 134 // type: boolean 135 // - name: pre-release 136 // in: query 137 // description: filter (exclude / include) pre-releases 138 // type: boolean 139 // - name: page 140 // in: query 141 // description: page number of results to return (1-based) 142 // type: integer 143 // - name: limit 144 // in: query 145 // description: page size of results 146 // type: integer 147 // responses: 148 // "200": 149 // "$ref": "#/responses/ReleaseList" 150 // "404": 151 // "$ref": "#/responses/notFound" 152 listOptions := utils.GetListOptions(ctx) 153 154 opts := repo_model.FindReleasesOptions{ 155 ListOptions: listOptions, 156 IncludeDrafts: ctx.Repo.AccessMode >= perm.AccessModeWrite || ctx.Repo.UnitAccessMode(unit.TypeReleases) >= perm.AccessModeWrite, 157 IncludeTags: false, 158 IsDraft: ctx.FormOptionalBool("draft"), 159 IsPreRelease: ctx.FormOptionalBool("pre-release"), 160 RepoID: ctx.Repo.Repository.ID, 161 } 162 163 releases, err := db.Find[repo_model.Release](ctx, opts) 164 if err != nil { 165 ctx.Error(http.StatusInternalServerError, "GetReleasesByRepoID", err) 166 return 167 } 168 rels := make([]*api.Release, len(releases)) 169 for i, release := range releases { 170 if err := release.LoadAttributes(ctx); err != nil { 171 ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) 172 return 173 } 174 rels[i] = convert.ToAPIRelease(ctx, ctx.Repo.Repository, release) 175 } 176 177 filteredCount, err := db.Count[repo_model.Release](ctx, opts) 178 if err != nil { 179 ctx.InternalServerError(err) 180 return 181 } 182 183 ctx.SetLinkHeader(int(filteredCount), listOptions.PageSize) 184 ctx.SetTotalCountHeader(filteredCount) 185 ctx.JSON(http.StatusOK, rels) 186 } 187 188 // CreateRelease create a release 189 func CreateRelease(ctx *context.APIContext) { 190 // swagger:operation POST /repos/{owner}/{repo}/releases repository repoCreateRelease 191 // --- 192 // summary: Create a release 193 // consumes: 194 // - application/json 195 // produces: 196 // - application/json 197 // parameters: 198 // - name: owner 199 // in: path 200 // description: owner of the repo 201 // type: string 202 // required: true 203 // - name: repo 204 // in: path 205 // description: name of the repo 206 // type: string 207 // required: true 208 // - name: body 209 // in: body 210 // schema: 211 // "$ref": "#/definitions/CreateReleaseOption" 212 // responses: 213 // "201": 214 // "$ref": "#/responses/Release" 215 // "404": 216 // "$ref": "#/responses/notFound" 217 // "409": 218 // "$ref": "#/responses/error" 219 // "422": 220 // "$ref": "#/responses/validationError" 221 222 form := web.GetForm(ctx).(*api.CreateReleaseOption) 223 if ctx.Repo.Repository.IsEmpty { 224 ctx.Error(http.StatusUnprocessableEntity, "RepoIsEmpty", fmt.Errorf("repo is empty")) 225 return 226 } 227 rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, form.TagName) 228 if err != nil { 229 if !repo_model.IsErrReleaseNotExist(err) { 230 ctx.Error(http.StatusInternalServerError, "GetRelease", err) 231 return 232 } 233 // If target is not provided use default branch 234 if len(form.Target) == 0 { 235 form.Target = ctx.Repo.Repository.DefaultBranch 236 } 237 rel = &repo_model.Release{ 238 RepoID: ctx.Repo.Repository.ID, 239 PublisherID: ctx.Doer.ID, 240 Publisher: ctx.Doer, 241 TagName: form.TagName, 242 Target: form.Target, 243 Title: form.Title, 244 Note: form.Note, 245 IsDraft: form.IsDraft, 246 IsPrerelease: form.IsPrerelease, 247 IsTag: false, 248 Repo: ctx.Repo.Repository, 249 } 250 if err := release_service.CreateRelease(ctx.Repo.GitRepo, rel, nil, ""); err != nil { 251 if repo_model.IsErrReleaseAlreadyExist(err) { 252 ctx.Error(http.StatusConflict, "ReleaseAlreadyExist", err) 253 } else if models.IsErrProtectedTagName(err) { 254 ctx.Error(http.StatusUnprocessableEntity, "ProtectedTagName", err) 255 } else if git.IsErrNotExist(err) { 256 ctx.Error(http.StatusNotFound, "ErrNotExist", fmt.Errorf("target \"%v\" not found: %w", rel.Target, err)) 257 } else { 258 ctx.Error(http.StatusInternalServerError, "CreateRelease", err) 259 } 260 return 261 } 262 } else { 263 if !rel.IsTag { 264 ctx.Error(http.StatusConflict, "GetRelease", "Release is has no Tag") 265 return 266 } 267 268 rel.Title = form.Title 269 rel.Note = form.Note 270 rel.IsDraft = form.IsDraft 271 rel.IsPrerelease = form.IsPrerelease 272 rel.PublisherID = ctx.Doer.ID 273 rel.IsTag = false 274 rel.Repo = ctx.Repo.Repository 275 rel.Publisher = ctx.Doer 276 rel.Target = form.Target 277 278 if err = release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, nil, nil, nil); err != nil { 279 ctx.Error(http.StatusInternalServerError, "UpdateRelease", err) 280 return 281 } 282 } 283 ctx.JSON(http.StatusCreated, convert.ToAPIRelease(ctx, ctx.Repo.Repository, rel)) 284 } 285 286 // EditRelease edit a release 287 func EditRelease(ctx *context.APIContext) { 288 // swagger:operation PATCH /repos/{owner}/{repo}/releases/{id} repository repoEditRelease 289 // --- 290 // summary: Update a release 291 // consumes: 292 // - application/json 293 // produces: 294 // - application/json 295 // parameters: 296 // - name: owner 297 // in: path 298 // description: owner of the repo 299 // type: string 300 // required: true 301 // - name: repo 302 // in: path 303 // description: name of the repo 304 // type: string 305 // required: true 306 // - name: id 307 // in: path 308 // description: id of the release to edit 309 // type: integer 310 // format: int64 311 // required: true 312 // - name: body 313 // in: body 314 // schema: 315 // "$ref": "#/definitions/EditReleaseOption" 316 // responses: 317 // "200": 318 // "$ref": "#/responses/Release" 319 // "404": 320 // "$ref": "#/responses/notFound" 321 322 form := web.GetForm(ctx).(*api.EditReleaseOption) 323 id := ctx.ParamsInt64(":id") 324 rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id) 325 if err != nil && !repo_model.IsErrReleaseNotExist(err) { 326 ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err) 327 return 328 } 329 if err != nil && repo_model.IsErrReleaseNotExist(err) || rel.IsTag { 330 ctx.NotFound() 331 return 332 } 333 334 if len(form.TagName) > 0 { 335 rel.TagName = form.TagName 336 } 337 if len(form.Target) > 0 { 338 rel.Target = form.Target 339 } 340 if len(form.Title) > 0 { 341 rel.Title = form.Title 342 } 343 if len(form.Note) > 0 { 344 rel.Note = form.Note 345 } 346 if form.IsDraft != nil { 347 rel.IsDraft = *form.IsDraft 348 } 349 if form.IsPrerelease != nil { 350 rel.IsPrerelease = *form.IsPrerelease 351 } 352 if err := release_service.UpdateRelease(ctx, ctx.Doer, ctx.Repo.GitRepo, rel, nil, nil, nil); err != nil { 353 ctx.Error(http.StatusInternalServerError, "UpdateRelease", err) 354 return 355 } 356 357 // reload data from database 358 rel, err = repo_model.GetReleaseByID(ctx, id) 359 if err != nil { 360 ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err) 361 return 362 } 363 if err := rel.LoadAttributes(ctx); err != nil { 364 ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) 365 return 366 } 367 ctx.JSON(http.StatusOK, convert.ToAPIRelease(ctx, ctx.Repo.Repository, rel)) 368 } 369 370 // DeleteRelease delete a release from a repository 371 func DeleteRelease(ctx *context.APIContext) { 372 // swagger:operation DELETE /repos/{owner}/{repo}/releases/{id} repository repoDeleteRelease 373 // --- 374 // summary: Delete a release 375 // parameters: 376 // - name: owner 377 // in: path 378 // description: owner of the repo 379 // type: string 380 // required: true 381 // - name: repo 382 // in: path 383 // description: name of the repo 384 // type: string 385 // required: true 386 // - name: id 387 // in: path 388 // description: id of the release to delete 389 // type: integer 390 // format: int64 391 // required: true 392 // responses: 393 // "204": 394 // "$ref": "#/responses/empty" 395 // "404": 396 // "$ref": "#/responses/notFound" 397 // "422": 398 // "$ref": "#/responses/validationError" 399 400 id := ctx.ParamsInt64(":id") 401 rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id) 402 if err != nil && !repo_model.IsErrReleaseNotExist(err) { 403 ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err) 404 return 405 } 406 if err != nil && repo_model.IsErrReleaseNotExist(err) || rel.IsTag { 407 ctx.NotFound() 408 return 409 } 410 if err := release_service.DeleteReleaseByID(ctx, ctx.Repo.Repository, rel, ctx.Doer, false); err != nil { 411 if models.IsErrProtectedTagName(err) { 412 ctx.Error(http.StatusUnprocessableEntity, "delTag", "user not allowed to delete protected tag") 413 return 414 } 415 ctx.Error(http.StatusInternalServerError, "DeleteReleaseByID", err) 416 return 417 } 418 ctx.Status(http.StatusNoContent) 419 }