code.gitea.io/gitea@v1.21.7/routers/api/v1/repo/issue_subscription.go (about)

     1  // Copyright 2019 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  	issues_model "code.gitea.io/gitea/models/issues"
    11  	user_model "code.gitea.io/gitea/models/user"
    12  	"code.gitea.io/gitea/modules/context"
    13  	api "code.gitea.io/gitea/modules/structs"
    14  	"code.gitea.io/gitea/routers/api/v1/utils"
    15  	"code.gitea.io/gitea/services/convert"
    16  )
    17  
    18  // AddIssueSubscription Subscribe user to issue
    19  func AddIssueSubscription(ctx *context.APIContext) {
    20  	// swagger:operation PUT /repos/{owner}/{repo}/issues/{index}/subscriptions/{user} issue issueAddSubscription
    21  	// ---
    22  	// summary: Subscribe user to issue
    23  	// consumes:
    24  	// - application/json
    25  	// produces:
    26  	// - application/json
    27  	// parameters:
    28  	// - name: owner
    29  	//   in: path
    30  	//   description: owner of the repo
    31  	//   type: string
    32  	//   required: true
    33  	// - name: repo
    34  	//   in: path
    35  	//   description: name of the repo
    36  	//   type: string
    37  	//   required: true
    38  	// - name: index
    39  	//   in: path
    40  	//   description: index of the issue
    41  	//   type: integer
    42  	//   format: int64
    43  	//   required: true
    44  	// - name: user
    45  	//   in: path
    46  	//   description: user to subscribe
    47  	//   type: string
    48  	//   required: true
    49  	// responses:
    50  	//   "200":
    51  	//     description: Already subscribed
    52  	//   "201":
    53  	//     description: Successfully Subscribed
    54  	//   "304":
    55  	//     description: User can only subscribe itself if he is no admin
    56  	//   "404":
    57  	//     "$ref": "#/responses/notFound"
    58  
    59  	setIssueSubscription(ctx, true)
    60  }
    61  
    62  // DelIssueSubscription Unsubscribe user from issue
    63  func DelIssueSubscription(ctx *context.APIContext) {
    64  	// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/subscriptions/{user} issue issueDeleteSubscription
    65  	// ---
    66  	// summary: Unsubscribe user from issue
    67  	// consumes:
    68  	// - application/json
    69  	// produces:
    70  	// - application/json
    71  	// parameters:
    72  	// - name: owner
    73  	//   in: path
    74  	//   description: owner of the repo
    75  	//   type: string
    76  	//   required: true
    77  	// - name: repo
    78  	//   in: path
    79  	//   description: name of the repo
    80  	//   type: string
    81  	//   required: true
    82  	// - name: index
    83  	//   in: path
    84  	//   description: index of the issue
    85  	//   type: integer
    86  	//   format: int64
    87  	//   required: true
    88  	// - name: user
    89  	//   in: path
    90  	//   description: user witch unsubscribe
    91  	//   type: string
    92  	//   required: true
    93  	// responses:
    94  	//   "200":
    95  	//     description: Already unsubscribed
    96  	//   "201":
    97  	//     description: Successfully Unsubscribed
    98  	//   "304":
    99  	//     description: User can only subscribe itself if he is no admin
   100  	//   "404":
   101  	//     "$ref": "#/responses/notFound"
   102  
   103  	setIssueSubscription(ctx, false)
   104  }
   105  
   106  func setIssueSubscription(ctx *context.APIContext, watch bool) {
   107  	issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
   108  	if err != nil {
   109  		if issues_model.IsErrIssueNotExist(err) {
   110  			ctx.NotFound()
   111  		} else {
   112  			ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
   113  		}
   114  
   115  		return
   116  	}
   117  
   118  	user, err := user_model.GetUserByName(ctx, ctx.Params(":user"))
   119  	if err != nil {
   120  		if user_model.IsErrUserNotExist(err) {
   121  			ctx.NotFound()
   122  		} else {
   123  			ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
   124  		}
   125  
   126  		return
   127  	}
   128  
   129  	// only admin and user for itself can change subscription
   130  	if user.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin {
   131  		ctx.Error(http.StatusForbidden, "User", fmt.Errorf("%s is not permitted to change subscriptions for %s", ctx.Doer.Name, user.Name))
   132  		return
   133  	}
   134  
   135  	current, err := issues_model.CheckIssueWatch(ctx, user, issue)
   136  	if err != nil {
   137  		ctx.Error(http.StatusInternalServerError, "CheckIssueWatch", err)
   138  		return
   139  	}
   140  
   141  	// If watch state wont change
   142  	if current == watch {
   143  		ctx.Status(http.StatusOK)
   144  		return
   145  	}
   146  
   147  	// Update watch state
   148  	if err := issues_model.CreateOrUpdateIssueWatch(ctx, user.ID, issue.ID, watch); err != nil {
   149  		ctx.Error(http.StatusInternalServerError, "CreateOrUpdateIssueWatch", err)
   150  		return
   151  	}
   152  
   153  	ctx.Status(http.StatusCreated)
   154  }
   155  
   156  // CheckIssueSubscription check if user is subscribed to an issue
   157  func CheckIssueSubscription(ctx *context.APIContext) {
   158  	// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/subscriptions/check issue issueCheckSubscription
   159  	// ---
   160  	// summary: Check if user is subscribed to an issue
   161  	// consumes:
   162  	// - application/json
   163  	// produces:
   164  	// - application/json
   165  	// parameters:
   166  	// - name: owner
   167  	//   in: path
   168  	//   description: owner of the repo
   169  	//   type: string
   170  	//   required: true
   171  	// - name: repo
   172  	//   in: path
   173  	//   description: name of the repo
   174  	//   type: string
   175  	//   required: true
   176  	// - name: index
   177  	//   in: path
   178  	//   description: index of the issue
   179  	//   type: integer
   180  	//   format: int64
   181  	//   required: true
   182  	// responses:
   183  	//   "200":
   184  	//     "$ref": "#/responses/WatchInfo"
   185  	//   "404":
   186  	//     "$ref": "#/responses/notFound"
   187  
   188  	issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
   189  	if err != nil {
   190  		if issues_model.IsErrIssueNotExist(err) {
   191  			ctx.NotFound()
   192  		} else {
   193  			ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
   194  		}
   195  
   196  		return
   197  	}
   198  
   199  	watching, err := issues_model.CheckIssueWatch(ctx, ctx.Doer, issue)
   200  	if err != nil {
   201  		ctx.InternalServerError(err)
   202  		return
   203  	}
   204  	ctx.JSON(http.StatusOK, api.WatchInfo{
   205  		Subscribed:    watching,
   206  		Ignored:       !watching,
   207  		Reason:        nil,
   208  		CreatedAt:     issue.CreatedUnix.AsTime(),
   209  		URL:           issue.APIURL() + "/subscriptions",
   210  		RepositoryURL: ctx.Repo.Repository.APIURL(),
   211  	})
   212  }
   213  
   214  // GetIssueSubscribers return subscribers of an issue
   215  func GetIssueSubscribers(ctx *context.APIContext) {
   216  	// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/subscriptions issue issueSubscriptions
   217  	// ---
   218  	// summary: Get users who subscribed on an issue.
   219  	// consumes:
   220  	// - application/json
   221  	// produces:
   222  	// - application/json
   223  	// parameters:
   224  	// - name: owner
   225  	//   in: path
   226  	//   description: owner of the repo
   227  	//   type: string
   228  	//   required: true
   229  	// - name: repo
   230  	//   in: path
   231  	//   description: name of the repo
   232  	//   type: string
   233  	//   required: true
   234  	// - name: index
   235  	//   in: path
   236  	//   description: index of the issue
   237  	//   type: integer
   238  	//   format: int64
   239  	//   required: true
   240  	// - name: page
   241  	//   in: query
   242  	//   description: page number of results to return (1-based)
   243  	//   type: integer
   244  	// - name: limit
   245  	//   in: query
   246  	//   description: page size of results
   247  	//   type: integer
   248  	// responses:
   249  	//   "200":
   250  	//     "$ref": "#/responses/UserList"
   251  	//   "404":
   252  	//     "$ref": "#/responses/notFound"
   253  
   254  	issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":index"))
   255  	if err != nil {
   256  		if issues_model.IsErrIssueNotExist(err) {
   257  			ctx.NotFound()
   258  		} else {
   259  			ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
   260  		}
   261  
   262  		return
   263  	}
   264  
   265  	iwl, err := issues_model.GetIssueWatchers(ctx, issue.ID, utils.GetListOptions(ctx))
   266  	if err != nil {
   267  		ctx.Error(http.StatusInternalServerError, "GetIssueWatchers", err)
   268  		return
   269  	}
   270  
   271  	userIDs := make([]int64, 0, len(iwl))
   272  	for _, iw := range iwl {
   273  		userIDs = append(userIDs, iw.UserID)
   274  	}
   275  
   276  	users, err := user_model.GetUsersByIDs(ctx, userIDs)
   277  	if err != nil {
   278  		ctx.Error(http.StatusInternalServerError, "GetUsersByIDs", err)
   279  		return
   280  	}
   281  	apiUsers := make([]*api.User, 0, len(users))
   282  	for _, v := range users {
   283  		apiUsers = append(apiUsers, convert.ToUser(ctx, v, ctx.Doer))
   284  	}
   285  
   286  	count, err := issues_model.CountIssueWatchers(ctx, issue.ID)
   287  	if err != nil {
   288  		ctx.Error(http.StatusInternalServerError, "CountIssueWatchers", err)
   289  		return
   290  	}
   291  
   292  	ctx.SetTotalCountHeader(count)
   293  	ctx.JSON(http.StatusOK, apiUsers)
   294  }