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

     1  // Copyright 2016 The Gogs Authors. All rights reserved.
     2  // Copyright 2020 The Gitea Authors.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package repo
     6  
     7  import (
     8  	"net/http"
     9  	"strconv"
    10  	"time"
    11  
    12  	"code.gitea.io/gitea/models/db"
    13  	issues_model "code.gitea.io/gitea/models/issues"
    14  	"code.gitea.io/gitea/modules/optional"
    15  	api "code.gitea.io/gitea/modules/structs"
    16  	"code.gitea.io/gitea/modules/timeutil"
    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  )
    22  
    23  // ListMilestones list milestones for a repository
    24  func ListMilestones(ctx *context.APIContext) {
    25  	// swagger:operation GET /repos/{owner}/{repo}/milestones issue issueGetMilestonesList
    26  	// ---
    27  	// summary: Get all of a repository's opened milestones
    28  	// produces:
    29  	// - application/json
    30  	// parameters:
    31  	// - name: owner
    32  	//   in: path
    33  	//   description: owner of the repo
    34  	//   type: string
    35  	//   required: true
    36  	// - name: repo
    37  	//   in: path
    38  	//   description: name of the repo
    39  	//   type: string
    40  	//   required: true
    41  	// - name: state
    42  	//   in: query
    43  	//   description: Milestone state, Recognized values are open, closed and all. Defaults to "open"
    44  	//   type: string
    45  	// - name: name
    46  	//   in: query
    47  	//   description: filter by milestone name
    48  	//   type: string
    49  	// - name: page
    50  	//   in: query
    51  	//   description: page number of results to return (1-based)
    52  	//   type: integer
    53  	// - name: limit
    54  	//   in: query
    55  	//   description: page size of results
    56  	//   type: integer
    57  	// responses:
    58  	//   "200":
    59  	//     "$ref": "#/responses/MilestoneList"
    60  	//   "404":
    61  	//     "$ref": "#/responses/notFound"
    62  
    63  	state := api.StateType(ctx.FormString("state"))
    64  	var isClosed optional.Option[bool]
    65  	switch state {
    66  	case api.StateClosed, api.StateOpen:
    67  		isClosed = optional.Some(state == api.StateClosed)
    68  	}
    69  
    70  	milestones, total, err := db.FindAndCount[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{
    71  		ListOptions: utils.GetListOptions(ctx),
    72  		RepoID:      ctx.Repo.Repository.ID,
    73  		IsClosed:    isClosed,
    74  		Name:        ctx.FormString("name"),
    75  	})
    76  	if err != nil {
    77  		ctx.Error(http.StatusInternalServerError, "db.FindAndCount[issues_model.Milestone]", err)
    78  		return
    79  	}
    80  
    81  	apiMilestones := make([]*api.Milestone, len(milestones))
    82  	for i := range milestones {
    83  		apiMilestones[i] = convert.ToAPIMilestone(milestones[i])
    84  	}
    85  
    86  	ctx.SetTotalCountHeader(total)
    87  	ctx.JSON(http.StatusOK, &apiMilestones)
    88  }
    89  
    90  // GetMilestone get a milestone for a repository by ID and if not available by name
    91  func GetMilestone(ctx *context.APIContext) {
    92  	// swagger:operation GET /repos/{owner}/{repo}/milestones/{id} issue issueGetMilestone
    93  	// ---
    94  	// summary: Get a milestone
    95  	// produces:
    96  	// - application/json
    97  	// parameters:
    98  	// - name: owner
    99  	//   in: path
   100  	//   description: owner of the repo
   101  	//   type: string
   102  	//   required: true
   103  	// - name: repo
   104  	//   in: path
   105  	//   description: name of the repo
   106  	//   type: string
   107  	//   required: true
   108  	// - name: id
   109  	//   in: path
   110  	//   description: the milestone to get, identified by ID and if not available by name
   111  	//   type: string
   112  	//   required: true
   113  	// responses:
   114  	//   "200":
   115  	//     "$ref": "#/responses/Milestone"
   116  	//   "404":
   117  	//     "$ref": "#/responses/notFound"
   118  
   119  	milestone := getMilestoneByIDOrName(ctx)
   120  	if ctx.Written() {
   121  		return
   122  	}
   123  
   124  	ctx.JSON(http.StatusOK, convert.ToAPIMilestone(milestone))
   125  }
   126  
   127  // CreateMilestone create a milestone for a repository
   128  func CreateMilestone(ctx *context.APIContext) {
   129  	// swagger:operation POST /repos/{owner}/{repo}/milestones issue issueCreateMilestone
   130  	// ---
   131  	// summary: Create a milestone
   132  	// consumes:
   133  	// - application/json
   134  	// produces:
   135  	// - application/json
   136  	// parameters:
   137  	// - name: owner
   138  	//   in: path
   139  	//   description: owner of the repo
   140  	//   type: string
   141  	//   required: true
   142  	// - name: repo
   143  	//   in: path
   144  	//   description: name of the repo
   145  	//   type: string
   146  	//   required: true
   147  	// - name: body
   148  	//   in: body
   149  	//   schema:
   150  	//     "$ref": "#/definitions/CreateMilestoneOption"
   151  	// responses:
   152  	//   "201":
   153  	//     "$ref": "#/responses/Milestone"
   154  	//   "404":
   155  	//     "$ref": "#/responses/notFound"
   156  	form := web.GetForm(ctx).(*api.CreateMilestoneOption)
   157  
   158  	if form.Deadline == nil {
   159  		defaultDeadline, _ := time.ParseInLocation("2006-01-02", "9999-12-31", time.Local)
   160  		form.Deadline = &defaultDeadline
   161  	}
   162  
   163  	milestone := &issues_model.Milestone{
   164  		RepoID:       ctx.Repo.Repository.ID,
   165  		Name:         form.Title,
   166  		Content:      form.Description,
   167  		DeadlineUnix: timeutil.TimeStamp(form.Deadline.Unix()),
   168  	}
   169  
   170  	if form.State == "closed" {
   171  		milestone.IsClosed = true
   172  		milestone.ClosedDateUnix = timeutil.TimeStampNow()
   173  	}
   174  
   175  	if err := issues_model.NewMilestone(ctx, milestone); err != nil {
   176  		ctx.Error(http.StatusInternalServerError, "NewMilestone", err)
   177  		return
   178  	}
   179  	ctx.JSON(http.StatusCreated, convert.ToAPIMilestone(milestone))
   180  }
   181  
   182  // EditMilestone modify a milestone for a repository by ID and if not available by name
   183  func EditMilestone(ctx *context.APIContext) {
   184  	// swagger:operation PATCH /repos/{owner}/{repo}/milestones/{id} issue issueEditMilestone
   185  	// ---
   186  	// summary: Update a milestone
   187  	// consumes:
   188  	// - application/json
   189  	// produces:
   190  	// - application/json
   191  	// parameters:
   192  	// - name: owner
   193  	//   in: path
   194  	//   description: owner of the repo
   195  	//   type: string
   196  	//   required: true
   197  	// - name: repo
   198  	//   in: path
   199  	//   description: name of the repo
   200  	//   type: string
   201  	//   required: true
   202  	// - name: id
   203  	//   in: path
   204  	//   description: the milestone to edit, identified by ID and if not available by name
   205  	//   type: string
   206  	//   required: true
   207  	// - name: body
   208  	//   in: body
   209  	//   schema:
   210  	//     "$ref": "#/definitions/EditMilestoneOption"
   211  	// responses:
   212  	//   "200":
   213  	//     "$ref": "#/responses/Milestone"
   214  	//   "404":
   215  	//     "$ref": "#/responses/notFound"
   216  	form := web.GetForm(ctx).(*api.EditMilestoneOption)
   217  	milestone := getMilestoneByIDOrName(ctx)
   218  	if ctx.Written() {
   219  		return
   220  	}
   221  
   222  	if len(form.Title) > 0 {
   223  		milestone.Name = form.Title
   224  	}
   225  	if form.Description != nil {
   226  		milestone.Content = *form.Description
   227  	}
   228  	if form.Deadline != nil && !form.Deadline.IsZero() {
   229  		milestone.DeadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
   230  	}
   231  
   232  	oldIsClosed := milestone.IsClosed
   233  	if form.State != nil {
   234  		milestone.IsClosed = *form.State == string(api.StateClosed)
   235  	}
   236  
   237  	if err := issues_model.UpdateMilestone(ctx, milestone, oldIsClosed); err != nil {
   238  		ctx.Error(http.StatusInternalServerError, "UpdateMilestone", err)
   239  		return
   240  	}
   241  	ctx.JSON(http.StatusOK, convert.ToAPIMilestone(milestone))
   242  }
   243  
   244  // DeleteMilestone delete a milestone for a repository by ID and if not available by name
   245  func DeleteMilestone(ctx *context.APIContext) {
   246  	// swagger:operation DELETE /repos/{owner}/{repo}/milestones/{id} issue issueDeleteMilestone
   247  	// ---
   248  	// summary: Delete a milestone
   249  	// parameters:
   250  	// - name: owner
   251  	//   in: path
   252  	//   description: owner of the repo
   253  	//   type: string
   254  	//   required: true
   255  	// - name: repo
   256  	//   in: path
   257  	//   description: name of the repo
   258  	//   type: string
   259  	//   required: true
   260  	// - name: id
   261  	//   in: path
   262  	//   description: the milestone to delete, identified by ID and if not available by name
   263  	//   type: string
   264  	//   required: true
   265  	// responses:
   266  	//   "204":
   267  	//     "$ref": "#/responses/empty"
   268  	//   "404":
   269  	//     "$ref": "#/responses/notFound"
   270  
   271  	m := getMilestoneByIDOrName(ctx)
   272  	if ctx.Written() {
   273  		return
   274  	}
   275  
   276  	if err := issues_model.DeleteMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, m.ID); err != nil {
   277  		ctx.Error(http.StatusInternalServerError, "DeleteMilestoneByRepoID", err)
   278  		return
   279  	}
   280  	ctx.Status(http.StatusNoContent)
   281  }
   282  
   283  // getMilestoneByIDOrName get milestone by ID and if not available by name
   284  func getMilestoneByIDOrName(ctx *context.APIContext) *issues_model.Milestone {
   285  	mile := ctx.Params(":id")
   286  	mileID, _ := strconv.ParseInt(mile, 0, 64)
   287  
   288  	if mileID != 0 {
   289  		milestone, err := issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, mileID)
   290  		if err == nil {
   291  			return milestone
   292  		} else if !issues_model.IsErrMilestoneNotExist(err) {
   293  			ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
   294  			return nil
   295  		}
   296  	}
   297  
   298  	milestone, err := issues_model.GetMilestoneByRepoIDANDName(ctx, ctx.Repo.Repository.ID, mile)
   299  	if err != nil {
   300  		if issues_model.IsErrMilestoneNotExist(err) {
   301  			ctx.NotFound()
   302  			return nil
   303  		}
   304  		ctx.Error(http.StatusInternalServerError, "GetMilestoneByRepoID", err)
   305  		return nil
   306  	}
   307  
   308  	return milestone
   309  }