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  }