mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
295 lines
7.2 KiB
Go
295 lines
7.2 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package repo
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
|
|
issues_model "github.com/hanzoai/git/models/issues"
|
|
user_model "github.com/hanzoai/git/models/user"
|
|
api "github.com/hanzoai/git/modules/structs"
|
|
"github.com/hanzoai/git/routers/api/v1/utils"
|
|
"github.com/hanzoai/git/services/context"
|
|
"github.com/hanzoai/git/services/convert"
|
|
)
|
|
|
|
// AddIssueSubscription Subscribe user to issue
|
|
func AddIssueSubscription(ctx *context.APIContext) {
|
|
// swagger:operation PUT /repos/{owner}/{repo}/issues/{index}/subscriptions/{user} issue issueAddSubscription
|
|
// ---
|
|
// summary: Subscribe user to issue
|
|
// consumes:
|
|
// - application/json
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: owner
|
|
// in: path
|
|
// description: owner of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: repo
|
|
// in: path
|
|
// description: name of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: index
|
|
// in: path
|
|
// description: index of the issue
|
|
// type: integer
|
|
// format: int64
|
|
// required: true
|
|
// - name: user
|
|
// in: path
|
|
// description: username of the user to subscribe the issue to
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// description: Already subscribed
|
|
// "201":
|
|
// description: Successfully Subscribed
|
|
// "304":
|
|
// description: User can only subscribe itself if he is no admin
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
setIssueSubscription(ctx, true)
|
|
}
|
|
|
|
// DelIssueSubscription Unsubscribe user from issue
|
|
func DelIssueSubscription(ctx *context.APIContext) {
|
|
// swagger:operation DELETE /repos/{owner}/{repo}/issues/{index}/subscriptions/{user} issue issueDeleteSubscription
|
|
// ---
|
|
// summary: Unsubscribe user from issue
|
|
// consumes:
|
|
// - application/json
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: owner
|
|
// in: path
|
|
// description: owner of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: repo
|
|
// in: path
|
|
// description: name of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: index
|
|
// in: path
|
|
// description: index of the issue
|
|
// type: integer
|
|
// format: int64
|
|
// required: true
|
|
// - name: user
|
|
// in: path
|
|
// description: username of the user to unsubscribe from an issue
|
|
// type: string
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// description: Already unsubscribed
|
|
// "201":
|
|
// description: Successfully Unsubscribed
|
|
// "304":
|
|
// description: User can only subscribe itself if he is no admin
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
setIssueSubscription(ctx, false)
|
|
}
|
|
|
|
func setIssueSubscription(ctx *context.APIContext, watch bool) {
|
|
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
|
if err != nil {
|
|
if issues_model.IsErrIssueNotExist(err) {
|
|
ctx.APIErrorNotFound()
|
|
} else {
|
|
ctx.APIErrorInternal(err)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
user, err := user_model.GetUserByName(ctx, ctx.PathParam("user"))
|
|
if err != nil {
|
|
if user_model.IsErrUserNotExist(err) {
|
|
ctx.APIErrorNotFound()
|
|
} else {
|
|
ctx.APIErrorInternal(err)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// only admin and user for itself can change subscription
|
|
if user.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin {
|
|
ctx.APIError(http.StatusForbidden, fmt.Sprintf("%s is not permitted to change subscriptions for %s", ctx.Doer.Name, user.Name))
|
|
return
|
|
}
|
|
|
|
current, err := issues_model.CheckIssueWatch(ctx, user, issue)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
// If watch state won't change
|
|
if current == watch {
|
|
ctx.Status(http.StatusOK)
|
|
return
|
|
}
|
|
|
|
// Update watch state
|
|
if err := issues_model.CreateOrUpdateIssueWatch(ctx, user.ID, issue.ID, watch); err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
ctx.Status(http.StatusCreated)
|
|
}
|
|
|
|
// CheckIssueSubscription check if user is subscribed to an issue
|
|
func CheckIssueSubscription(ctx *context.APIContext) {
|
|
// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/subscriptions/check issue issueCheckSubscription
|
|
// ---
|
|
// summary: Check if user is subscribed to an issue
|
|
// consumes:
|
|
// - application/json
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: owner
|
|
// in: path
|
|
// description: owner of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: repo
|
|
// in: path
|
|
// description: name of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: index
|
|
// in: path
|
|
// description: index of the issue
|
|
// type: integer
|
|
// format: int64
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/WatchInfo"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
|
if err != nil {
|
|
if issues_model.IsErrIssueNotExist(err) {
|
|
ctx.APIErrorNotFound()
|
|
} else {
|
|
ctx.APIErrorInternal(err)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
watching, err := issues_model.CheckIssueWatch(ctx, ctx.Doer, issue)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, api.WatchInfo{
|
|
Subscribed: watching,
|
|
Ignored: !watching,
|
|
Reason: nil,
|
|
CreatedAt: issue.CreatedUnix.AsTime(),
|
|
URL: issue.APIURL(ctx) + "/subscriptions",
|
|
RepositoryURL: ctx.Repo.Repository.APIURL(),
|
|
})
|
|
}
|
|
|
|
// GetIssueSubscribers return subscribers of an issue
|
|
func GetIssueSubscribers(ctx *context.APIContext) {
|
|
// swagger:operation GET /repos/{owner}/{repo}/issues/{index}/subscriptions issue issueSubscriptions
|
|
// ---
|
|
// summary: Get users who subscribed on an issue.
|
|
// consumes:
|
|
// - application/json
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: owner
|
|
// in: path
|
|
// description: owner of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: repo
|
|
// in: path
|
|
// description: name of the repo
|
|
// type: string
|
|
// required: true
|
|
// - name: index
|
|
// in: path
|
|
// description: index of the issue
|
|
// type: integer
|
|
// format: int64
|
|
// required: true
|
|
// - name: page
|
|
// in: query
|
|
// description: page number of results to return (1-based)
|
|
// type: integer
|
|
// - name: limit
|
|
// in: query
|
|
// description: page size of results
|
|
// type: integer
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/UserList"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
|
|
if err != nil {
|
|
if issues_model.IsErrIssueNotExist(err) {
|
|
ctx.APIErrorNotFound()
|
|
} else {
|
|
ctx.APIErrorInternal(err)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
iwl, err := issues_model.GetIssueWatchers(ctx, issue.ID, utils.GetListOptions(ctx))
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
userIDs := make([]int64, 0, len(iwl))
|
|
for _, iw := range iwl {
|
|
userIDs = append(userIDs, iw.UserID)
|
|
}
|
|
|
|
users, err := user_model.GetUsersByIDs(ctx, userIDs)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
apiUsers := make([]*api.User, 0, len(users))
|
|
for _, v := range users {
|
|
apiUsers = append(apiUsers, convert.ToUser(ctx, v, ctx.Doer))
|
|
}
|
|
|
|
count, err := issues_model.CountIssueWatchers(ctx, issue.ID)
|
|
if err != nil {
|
|
ctx.APIErrorInternal(err)
|
|
return
|
|
}
|
|
|
|
ctx.SetTotalCountHeader(count)
|
|
ctx.JSON(http.StatusOK, apiUsers)
|
|
}
|