657 lines
21 KiB
Go
657 lines
21 KiB
Go
package azuredevops
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/kandev/kandev/internal/common/logger"
|
|
)
|
|
|
|
const notConfiguredCode = "azure_devops_not_configured"
|
|
|
|
type Controller struct {
|
|
service *Service
|
|
log *logger.Logger
|
|
}
|
|
|
|
func RegisterRoutes(router *gin.Engine, service *Service, log *logger.Logger) {
|
|
controller := &Controller{service: service, log: log}
|
|
api := router.Group("/api/v1/azure-devops")
|
|
api.GET("/config", controller.getConfig)
|
|
api.POST("/config", controller.setConfig)
|
|
api.DELETE("/config", controller.deleteConfig)
|
|
api.POST("/config/test", controller.testConfig)
|
|
api.POST("/config/copy", controller.copyConfig)
|
|
api.GET("/views", controller.getSavedViews)
|
|
api.PUT("/views", controller.setSavedViews)
|
|
api.GET("/workspace-settings", controller.getWorkspaceSettings)
|
|
api.PATCH("/workspace-settings", controller.updateWorkspaceSettings)
|
|
api.GET("/projects", controller.listProjects)
|
|
api.GET("/teams", controller.listTeams)
|
|
api.GET("/boards", controller.listBoards)
|
|
api.GET("/boards/:boardId", controller.getBoardSnapshot)
|
|
api.PATCH("/boards/:boardId/work-items/:id", controller.updateBoardWorkItem)
|
|
api.GET("/repositories", controller.listRepositories)
|
|
api.GET("/branches", controller.listBranches)
|
|
api.POST("/work-items/search", controller.searchWorkItems)
|
|
api.GET("/work-items/:id/comments", controller.listWorkItemComments)
|
|
api.PATCH("/work-items/:id", controller.updateWorkItem)
|
|
api.GET("/work-items/:id", controller.getWorkItem)
|
|
api.GET("/identity", controller.getIdentity)
|
|
api.GET("/watches/work-items", controller.listWorkItemWatches)
|
|
api.POST("/watches/work-items", controller.createWorkItemWatch)
|
|
api.PATCH("/watches/work-items/:id", controller.updateWorkItemWatch)
|
|
api.DELETE("/watches/work-items/:id", controller.deleteWorkItemWatch)
|
|
api.POST("/watches/work-items/:id/trigger", controller.triggerWorkItemWatch)
|
|
api.GET("/watches/work-items/:id/reset/preview", controller.previewResetWorkItemWatch)
|
|
api.POST("/watches/work-items/:id/reset", controller.resetWorkItemWatch)
|
|
api.GET("/watches/pull-requests", controller.listPullRequestWatches)
|
|
api.POST("/watches/pull-requests", controller.createPullRequestWatch)
|
|
api.PATCH("/watches/pull-requests/:id", controller.updatePullRequestWatch)
|
|
api.DELETE("/watches/pull-requests/:id", controller.deletePullRequestWatch)
|
|
api.POST("/watches/pull-requests/:id/trigger", controller.triggerPullRequestWatch)
|
|
api.GET("/watches/pull-requests/:id/reset/preview", controller.previewResetPullRequestWatch)
|
|
api.POST("/watches/pull-requests/:id/reset", controller.resetPullRequestWatch)
|
|
api.GET("/workspaces/:workspaceId/task-work-items", controller.listWorkspaceTaskWorkItems)
|
|
api.POST("/tasks/:taskId/work-items", controller.associateTaskWorkItem)
|
|
api.GET("/pull-requests", controller.listPullRequests)
|
|
api.GET("/pull-requests/:projectId/:repositoryId/:pullRequestId", controller.getPullRequest)
|
|
api.GET("/pull-requests/:projectId/:repositoryId/:pullRequestId/feedback", controller.getPullRequestFeedback)
|
|
api.GET("/workspaces/:workspaceId/task-prs", controller.listWorkspaceTaskPRs)
|
|
api.POST("/tasks/:taskId/pull-requests", controller.associateTaskPR)
|
|
api.POST("/tasks/:taskId/pull-requests/sync", controller.syncTaskPR)
|
|
}
|
|
|
|
func (c *Controller) getWorkspaceSettings(ctx *gin.Context) {
|
|
settings, err := c.service.GetWorkspaceSettings(ctx.Request.Context(), workspaceID(ctx))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, settings)
|
|
}
|
|
|
|
func (c *Controller) updateWorkspaceSettings(ctx *gin.Context) {
|
|
var request UpdateWorkspaceSettingsRequest
|
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
|
|
return
|
|
}
|
|
request.WorkspaceID = workspaceID(ctx)
|
|
settings, err := c.service.UpdateWorkspaceSettings(ctx.Request.Context(), &request)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, settings)
|
|
}
|
|
|
|
func (c *Controller) getSavedViews(ctx *gin.Context) {
|
|
views, err := c.service.GetSavedViewsForWorkspace(ctx.Request.Context(), workspaceID(ctx))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, gin.H{"views": views})
|
|
}
|
|
|
|
func (c *Controller) setSavedViews(ctx *gin.Context) {
|
|
var request struct {
|
|
Views []SavedView `json:"views"`
|
|
}
|
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
|
|
return
|
|
}
|
|
views, err := c.service.SetSavedViewsForWorkspace(
|
|
ctx.Request.Context(), workspaceID(ctx), request.Views,
|
|
)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, gin.H{"views": views})
|
|
}
|
|
|
|
func (c *Controller) getConfig(ctx *gin.Context) {
|
|
cfg, err := c.service.GetConfigForWorkspace(ctx.Request.Context(), workspaceID(ctx))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
if cfg == nil {
|
|
ctx.Status(http.StatusNoContent)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, cfg)
|
|
}
|
|
|
|
func (c *Controller) setConfig(ctx *gin.Context) {
|
|
var request SetConfigRequest
|
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
|
|
return
|
|
}
|
|
cfg, err := c.service.SetConfigForWorkspace(ctx.Request.Context(), workspaceID(ctx), &request)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, cfg)
|
|
}
|
|
|
|
func (c *Controller) deleteConfig(ctx *gin.Context) {
|
|
if err := c.service.DeleteConfigForWorkspace(ctx.Request.Context(), workspaceID(ctx)); err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, gin.H{"deleted": true})
|
|
}
|
|
|
|
func (c *Controller) testConfig(ctx *gin.Context) {
|
|
var request SetConfigRequest
|
|
if ctx.Request.ContentLength != 0 {
|
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
|
|
return
|
|
}
|
|
}
|
|
result, err := c.service.TestConnectionForWorkspace(ctx.Request.Context(), workspaceID(ctx), &request)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
func (c *Controller) copyConfig(ctx *gin.Context) {
|
|
var request struct {
|
|
TargetWorkspaceID string `json:"targetWorkspaceId"`
|
|
}
|
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
|
|
return
|
|
}
|
|
cfg, err := c.service.CopyConfigToWorkspace(ctx.Request.Context(), workspaceID(ctx), request.TargetWorkspaceID)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, cfg)
|
|
}
|
|
|
|
func (c *Controller) listProjects(ctx *gin.Context) {
|
|
projects, err := c.service.ListProjectsForWorkspace(ctx.Request.Context(), workspaceID(ctx))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, gin.H{"projects": projects})
|
|
}
|
|
|
|
func (c *Controller) listTeams(ctx *gin.Context) {
|
|
teams, err := c.service.ListTeamsForWorkspace(ctx.Request.Context(), workspaceID(ctx), ctx.Query("project"))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, gin.H{"teams": teams})
|
|
}
|
|
|
|
func (c *Controller) listBoards(ctx *gin.Context) {
|
|
boards, err := c.service.ListBoardsForWorkspace(ctx.Request.Context(), workspaceID(ctx), ctx.Query("project"), ctx.Query("team"))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, gin.H{"boards": boards})
|
|
}
|
|
|
|
func (c *Controller) getBoardSnapshot(ctx *gin.Context) {
|
|
snapshot, err := c.service.GetBoardSnapshotForWorkspace(ctx.Request.Context(), workspaceID(ctx), ctx.Query("project"), ctx.Query("team"), ctx.Param("boardId"))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, snapshot)
|
|
}
|
|
|
|
func (c *Controller) updateBoardWorkItem(ctx *gin.Context) {
|
|
id, err := strconv.Atoi(ctx.Param("id"))
|
|
if err != nil || id <= 0 {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid work item id"})
|
|
return
|
|
}
|
|
var request BoardWorkItemUpdateRequest
|
|
decoder := json.NewDecoder(ctx.Request.Body)
|
|
decoder.DisallowUnknownFields()
|
|
if err := decoder.Decode(&request); err != nil {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
|
|
return
|
|
}
|
|
item, err := c.service.UpdateBoardWorkItemForWorkspace(ctx.Request.Context(), workspaceID(ctx), ctx.Query("project"), ctx.Query("team"), ctx.Param("boardId"), id, request)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, item)
|
|
}
|
|
|
|
func (c *Controller) listRepositories(ctx *gin.Context) {
|
|
repositories, err := c.service.ListRepositoriesForWorkspace(
|
|
ctx.Request.Context(), workspaceID(ctx), ctx.Query("project"),
|
|
)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, gin.H{"repositories": repositories})
|
|
}
|
|
|
|
func (c *Controller) listBranches(ctx *gin.Context) {
|
|
branches, err := c.service.ListBranchesForWorkspace(
|
|
ctx.Request.Context(), workspaceID(ctx), ctx.Query("organization"), ctx.Query("project"),
|
|
ctx.Query("repository"),
|
|
)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, gin.H{"branches": branches})
|
|
}
|
|
|
|
func (c *Controller) searchWorkItems(ctx *gin.Context) {
|
|
var request struct {
|
|
Project string `json:"project"`
|
|
WIQL string `json:"wiql"`
|
|
Top int `json:"top"`
|
|
}
|
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
|
|
return
|
|
}
|
|
result, err := c.service.SearchWorkItemsForWorkspace(
|
|
ctx.Request.Context(), workspaceID(ctx), request.Project, request.WIQL, request.Top,
|
|
)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
func (c *Controller) getWorkItem(ctx *gin.Context) {
|
|
id, err := strconv.Atoi(ctx.Param("id"))
|
|
if err != nil {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid work item id"})
|
|
return
|
|
}
|
|
item, err := c.service.GetWorkItemDetailForWorkspace(
|
|
ctx.Request.Context(), workspaceID(ctx), ctx.Query("project"), id,
|
|
)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, item)
|
|
}
|
|
|
|
func (c *Controller) updateWorkItem(ctx *gin.Context) {
|
|
id, err := strconv.Atoi(ctx.Param("id"))
|
|
if err != nil || id <= 0 {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid work item id"})
|
|
return
|
|
}
|
|
var request WorkItemAssignmentRequest
|
|
decoder := json.NewDecoder(ctx.Request.Body)
|
|
decoder.DisallowUnknownFields()
|
|
if err := decoder.Decode(&request); err != nil {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
|
|
return
|
|
}
|
|
item, err := c.service.UpdateWorkItemForWorkspace(ctx.Request.Context(), workspaceID(ctx), ctx.Query("project"), id, request)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, item)
|
|
}
|
|
|
|
func (c *Controller) listWorkItemComments(ctx *gin.Context) {
|
|
id, err := strconv.Atoi(ctx.Param("id"))
|
|
if err != nil || id <= 0 {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid work item id"})
|
|
return
|
|
}
|
|
page, err := c.service.ListWorkItemCommentsForWorkspace(
|
|
ctx.Request.Context(), workspaceID(ctx), ctx.Query("project"), id, ctx.Query("continuation_token"),
|
|
)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, page)
|
|
}
|
|
|
|
func (c *Controller) getIdentity(ctx *gin.Context) {
|
|
identity, err := c.service.GetCurrentIdentityForWorkspace(ctx.Request.Context(), workspaceID(ctx))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, identity)
|
|
}
|
|
|
|
func (c *Controller) listWorkItemWatches(ctx *gin.Context) {
|
|
watches, err := c.service.ListWorkItemWatches(ctx.Request.Context(), workspaceID(ctx))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, gin.H{"watches": watches})
|
|
}
|
|
|
|
func (c *Controller) createWorkItemWatch(ctx *gin.Context) {
|
|
var request CreateWorkItemWatchRequest
|
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
|
|
return
|
|
}
|
|
request.WorkspaceID = workspaceID(ctx)
|
|
watch, err := c.service.CreateWorkItemWatch(ctx.Request.Context(), &request)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, watch)
|
|
}
|
|
|
|
func (c *Controller) updateWorkItemWatch(ctx *gin.Context) {
|
|
var request UpdateWorkItemWatchRequest
|
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
|
|
return
|
|
}
|
|
watch, err := c.service.UpdateWorkItemWatch(ctx.Request.Context(), workspaceID(ctx), ctx.Param("id"), &request)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, watch)
|
|
}
|
|
|
|
func (c *Controller) deleteWorkItemWatch(ctx *gin.Context) {
|
|
if err := c.service.DeleteWorkItemWatch(ctx.Request.Context(), workspaceID(ctx), ctx.Param("id")); err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, gin.H{"deleted": true})
|
|
}
|
|
|
|
func (c *Controller) triggerWorkItemWatch(ctx *gin.Context) {
|
|
result, err := c.service.TriggerWorkItemWatch(ctx.Request.Context(), workspaceID(ctx), ctx.Param("id"))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
func (c *Controller) previewResetWorkItemWatch(ctx *gin.Context) {
|
|
count, err := c.service.PreviewResetWorkItemWatch(ctx.Request.Context(), workspaceID(ctx), ctx.Param("id"))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, gin.H{"taskCount": count})
|
|
}
|
|
|
|
func (c *Controller) resetWorkItemWatch(ctx *gin.Context) {
|
|
result, err := c.service.ResetWorkItemWatch(ctx.Request.Context(), workspaceID(ctx), ctx.Param("id"))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, gin.H{"generation": result.Generation, "taskCount": len(result.TaskIDs)})
|
|
}
|
|
|
|
func (c *Controller) listPullRequestWatches(ctx *gin.Context) {
|
|
watches, err := c.service.ListPullRequestWatches(ctx.Request.Context(), workspaceID(ctx))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, gin.H{"watches": watches})
|
|
}
|
|
|
|
func (c *Controller) createPullRequestWatch(ctx *gin.Context) {
|
|
var request CreatePullRequestWatchRequest
|
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
|
|
return
|
|
}
|
|
request.WorkspaceID = workspaceID(ctx)
|
|
watch, err := c.service.CreatePullRequestWatch(ctx.Request.Context(), &request)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, watch)
|
|
}
|
|
|
|
func (c *Controller) updatePullRequestWatch(ctx *gin.Context) {
|
|
var request UpdatePullRequestWatchRequest
|
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"})
|
|
return
|
|
}
|
|
watch, err := c.service.UpdatePullRequestWatch(ctx.Request.Context(), workspaceID(ctx), ctx.Param("id"), &request)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, watch)
|
|
}
|
|
|
|
func (c *Controller) deletePullRequestWatch(ctx *gin.Context) {
|
|
if err := c.service.DeletePullRequestWatch(ctx.Request.Context(), workspaceID(ctx), ctx.Param("id")); err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, gin.H{"deleted": true})
|
|
}
|
|
|
|
func (c *Controller) triggerPullRequestWatch(ctx *gin.Context) {
|
|
result, err := c.service.TriggerPullRequestWatch(ctx.Request.Context(), workspaceID(ctx), ctx.Param("id"))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
func (c *Controller) previewResetPullRequestWatch(ctx *gin.Context) {
|
|
count, err := c.service.PreviewResetPullRequestWatch(ctx.Request.Context(), workspaceID(ctx), ctx.Param("id"))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, gin.H{"taskCount": count})
|
|
}
|
|
|
|
func (c *Controller) resetPullRequestWatch(ctx *gin.Context) {
|
|
result, err := c.service.ResetPullRequestWatch(ctx.Request.Context(), workspaceID(ctx), ctx.Param("id"))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, gin.H{"generation": result.Generation, "taskCount": len(result.TaskIDs)})
|
|
}
|
|
|
|
func (c *Controller) listPullRequests(ctx *gin.Context) {
|
|
skip, _ := strconv.Atoi(ctx.Query("skip"))
|
|
top, _ := strconv.Atoi(ctx.Query("top"))
|
|
result, err := c.service.ListPullRequestsForWorkspace(ctx.Request.Context(), workspaceID(ctx), PullRequestFilter{
|
|
ProjectID: ctx.Query("project"), RepositoryID: ctx.Query("repository"), Status: ctx.Query("status"),
|
|
CreatorID: ctx.Query("creator"), ReviewerID: ctx.Query("reviewer"),
|
|
SourceBranch: ctx.Query("source_branch"), TargetBranch: ctx.Query("target_branch"), Skip: skip, Top: top,
|
|
})
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
func (c *Controller) getPullRequest(ctx *gin.Context) {
|
|
id, ok := pullRequestID(ctx)
|
|
if !ok {
|
|
return
|
|
}
|
|
pr, err := c.service.GetPullRequestForWorkspace(ctx.Request.Context(), workspaceID(ctx), ctx.Param("projectId"), ctx.Param("repositoryId"), id)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, pr)
|
|
}
|
|
|
|
func (c *Controller) getPullRequestFeedback(ctx *gin.Context) {
|
|
id, ok := pullRequestID(ctx)
|
|
if !ok {
|
|
return
|
|
}
|
|
feedback, err := c.service.GetPullRequestFeedbackForWorkspace(ctx.Request.Context(), workspaceID(ctx), ctx.Param("projectId"), ctx.Param("repositoryId"), id)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, feedback)
|
|
}
|
|
|
|
type taskPRRequest struct {
|
|
RepositoryID string `json:"repositoryId" binding:"required"`
|
|
PullRequestID int `json:"pullRequestId" binding:"required"`
|
|
}
|
|
|
|
type taskWorkItemRequest struct {
|
|
ProjectID string `json:"projectId" binding:"required"`
|
|
WorkItemID int `json:"workItemId" binding:"required"`
|
|
}
|
|
|
|
func (c *Controller) listWorkspaceTaskWorkItems(ctx *gin.Context) {
|
|
rows, err := c.service.ListTaskWorkItemsByWorkspace(ctx.Request.Context(), ctx.Param("workspaceId"))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, TaskWorkItemsResponse{TaskWorkItems: rows})
|
|
}
|
|
|
|
func (c *Controller) associateTaskWorkItem(ctx *gin.Context) {
|
|
var request taskWorkItemRequest
|
|
if err := ctx.ShouldBindJSON(&request); err != nil || request.WorkItemID <= 0 {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "projectId and positive workItemId are required"})
|
|
return
|
|
}
|
|
row, err := c.service.AssociateTaskWorkItem(ctx.Request.Context(), workspaceID(ctx), ctx.Param("taskId"), request.ProjectID, request.WorkItemID)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, row)
|
|
}
|
|
|
|
func (c *Controller) listWorkspaceTaskPRs(ctx *gin.Context) {
|
|
rows, err := c.service.ListTaskPRsByWorkspace(ctx.Request.Context(), ctx.Param("workspaceId"))
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, TaskPRsResponse{TaskPRs: rows})
|
|
}
|
|
|
|
func (c *Controller) associateTaskPR(ctx *gin.Context) {
|
|
c.writeTaskPRSync(ctx, c.service.AssociateTaskPR)
|
|
}
|
|
|
|
func (c *Controller) syncTaskPR(ctx *gin.Context) {
|
|
c.writeTaskPRSync(ctx, c.service.SyncTaskPR)
|
|
}
|
|
|
|
func (c *Controller) writeTaskPRSync(
|
|
ctx *gin.Context,
|
|
sync func(context.Context, string, string, string, int) (*TaskPR, error),
|
|
) {
|
|
var request taskPRRequest
|
|
if err := ctx.ShouldBindJSON(&request); err != nil || request.PullRequestID <= 0 {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "repositoryId and positive pullRequestId are required"})
|
|
return
|
|
}
|
|
row, err := sync(
|
|
ctx.Request.Context(), workspaceID(ctx), ctx.Param("taskId"),
|
|
request.RepositoryID, request.PullRequestID,
|
|
)
|
|
if err != nil {
|
|
c.writeError(ctx, err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, row)
|
|
}
|
|
|
|
func (c *Controller) writeError(ctx *gin.Context, err error) {
|
|
status := http.StatusInternalServerError
|
|
code := ""
|
|
switch {
|
|
case errors.Is(err, ErrInvalidWorkspaceID), errors.Is(err, ErrInvalidConfig),
|
|
errors.Is(err, ErrInvalidTaskPRAssociation):
|
|
status = http.StatusBadRequest
|
|
case errors.Is(err, ErrWatchNotFound), errors.Is(err, ErrReservationNotFound):
|
|
status = http.StatusNotFound
|
|
case errors.Is(err, ErrWatchOwnershipLost):
|
|
status = http.StatusConflict
|
|
case errors.Is(err, ErrNotConfigured):
|
|
status, code = http.StatusServiceUnavailable, notConfiguredCode
|
|
default:
|
|
var apiErr *APIError
|
|
if errors.As(err, &apiErr) {
|
|
if apiErr.StatusCode == http.StatusConflict {
|
|
status, code = http.StatusConflict, "azure_devops_revision_conflict"
|
|
} else {
|
|
status = upstreamStatus(apiErr.StatusCode)
|
|
}
|
|
}
|
|
}
|
|
body := gin.H{"error": err.Error()}
|
|
if code != "" {
|
|
body["code"] = code
|
|
}
|
|
ctx.JSON(status, body)
|
|
}
|
|
|
|
func workspaceID(ctx *gin.Context) string { return strings.TrimSpace(ctx.Query("workspace_id")) }
|
|
func pullRequestID(ctx *gin.Context) (int, bool) {
|
|
id, err := strconv.Atoi(ctx.Param("pullRequestId"))
|
|
if err != nil || id <= 0 {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid pull request id"})
|
|
return 0, false
|
|
}
|
|
return id, true
|
|
}
|
|
func upstreamStatus(status int) int {
|
|
switch status {
|
|
case http.StatusUnauthorized, http.StatusForbidden, http.StatusTooManyRequests:
|
|
return status
|
|
default:
|
|
if status >= 500 {
|
|
return http.StatusBadGateway
|
|
}
|
|
return http.StatusBadRequest
|
|
}
|
|
}
|