736 lines
25 KiB
Go
736 lines
25 KiB
Go
package gitlab
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/kandev/kandev/internal/common/logger"
|
|
ws "github.com/kandev/kandev/pkg/websocket"
|
|
)
|
|
|
|
const (
|
|
errMsgInvalidPayload = "invalid payload"
|
|
errMsgIDRequired = "id required"
|
|
respKeyDeleted = "deleted"
|
|
)
|
|
|
|
// RegisterRoutesWithDispatcher registers HTTP + WS routes for GitLab.
|
|
// Preserves the existing RegisterRoutes(router, svc, log) signature by
|
|
// adding a sibling function so callers can pass a dispatcher.
|
|
func RegisterRoutesWithDispatcher(router *gin.Engine, dispatcher *ws.Dispatcher, svc *Service, log *logger.Logger) {
|
|
NewController(svc, log).RegisterHTTPRoutes(router)
|
|
// GitLab requests are workspace-scoped on the HTTP surface. The legacy
|
|
// WebSocket handlers predate workspace-owned connections and cannot carry
|
|
// the same authorization boundary, so they intentionally remain unmounted.
|
|
_ = dispatcher
|
|
}
|
|
|
|
// RegisterMockRoutes registers mock control endpoints if the underlying
|
|
// client is a MockClient. No-op otherwise.
|
|
func RegisterMockRoutes(router *gin.Engine, svc *Service, log *logger.Logger) {
|
|
mock, ok := svc.Client().(*MockClient)
|
|
if !ok {
|
|
return
|
|
}
|
|
NewMockController(mock, svc, log).RegisterRoutes(router)
|
|
log.Info("registered GitLab mock control endpoints")
|
|
}
|
|
|
|
// registerWSHandlers wires the GitLab WS action surface onto the dispatcher.
|
|
func registerWSHandlers(dispatcher *ws.Dispatcher, svc *Service, log *logger.Logger) {
|
|
dispatcher.RegisterFunc(ws.ActionGitLabStatus, wsStatus(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabTaskMRsList, wsListTaskMRs(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabTaskMRGet, wsGetTaskMR(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabMRFeedbackGet, wsGetMRFeedback(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabReviewWatchesList, wsListReviewWatches(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabReviewWatchCreate, wsCreateReviewWatch(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabReviewWatchUpdate, wsUpdateReviewWatch(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabReviewWatchDelete, wsDeleteReviewWatch(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabReviewTrigger, wsTriggerReviewWatch(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabReviewTriggerAll, wsTriggerAllReviewChecks(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabMRWatchesList, wsListMRWatches(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabMRWatchDelete, wsDeleteMRWatch(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabMRFilesGet, wsGetMRFiles(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabMRCommitsGet, wsGetMRCommits(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabTaskMRSync, wsSyncTaskMR(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabStats, wsGetStats(svc, log))
|
|
|
|
dispatcher.RegisterFunc(ws.ActionGitLabMRMerge, wsMergeMR(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabMRApprove, wsApproveMR(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabMRUnapprove, wsUnapproveMR(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabMRSetLabels, wsSetMRLabels(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabMRSetAssignees, wsSetMRAssignees(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabMRDiscussionNew, wsNewDiscussionNote(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabMRDiscussionResolve, wsResolveDiscussion(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabProjectMergeMethodsGet, wsGetProjectMergeMethods(svc, log))
|
|
|
|
dispatcher.RegisterFunc(ws.ActionGitLabIssueWatchesList, wsListIssueWatches(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabIssueWatchCreate, wsCreateIssueWatch(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabIssueWatchUpdate, wsUpdateIssueWatch(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabIssueWatchDelete, wsDeleteIssueWatch(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabIssueTrigger, wsTriggerIssueWatch(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabIssueTriggerAll, wsTriggerAllIssueChecks(svc, log))
|
|
|
|
dispatcher.RegisterFunc(ws.ActionGitLabActionPresetsList, wsListActionPresets(svc))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabActionPresetsUpdate, wsUpdateActionPresets(svc))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabActionPresetsReset, wsResetActionPresets(svc))
|
|
|
|
dispatcher.RegisterFunc(ws.ActionGitLabListUserProjects, wsListUserProjects(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabSearchProjects, wsSearchProjects(svc, log))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabProjectBranches, wsListProjectBranches(svc, log))
|
|
|
|
dispatcher.RegisterFunc(ws.ActionGitLabCleanupReviewTasks, wsCleanupReviewTasks(svc))
|
|
dispatcher.RegisterFunc(ws.ActionGitLabCleanupIssueTasks, wsCleanupIssueTasks(svc))
|
|
}
|
|
|
|
// --- Generic helpers ---
|
|
|
|
func parseInto[T any](msg *ws.Message, out *T) error { return msg.ParsePayload(out) }
|
|
|
|
func badRequest(msg *ws.Message, reason string) (*ws.Message, error) {
|
|
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeBadRequest, reason, nil)
|
|
}
|
|
|
|
func internalError(msg *ws.Message, err error) (*ws.Message, error) {
|
|
return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeInternalError, err.Error(), nil)
|
|
}
|
|
|
|
func okResponse(msg *ws.Message, payload interface{}) (*ws.Message, error) {
|
|
return ws.NewResponse(msg.ID, msg.Action, payload)
|
|
}
|
|
|
|
// --- Status / list / get / sync ---
|
|
|
|
func wsStatus(svc *Service, log *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
status, err := svc.GetStatus(ctx)
|
|
if err != nil {
|
|
log.Warn("gitlab status: " + err.Error())
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, status)
|
|
}
|
|
}
|
|
|
|
func wsListTaskMRs(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
WorkspaceID string `json:"workspace_id"`
|
|
TaskID string `json:"task_id"`
|
|
}
|
|
// Malformed payloads must not silently fall through to the "list
|
|
// everything in workspace" branch — that exposes more data than the
|
|
// caller intended. Empty payload (both fields absent) is rejected
|
|
// further down by the workspace_id check.
|
|
if err := parseInto(msg, &req); err != nil {
|
|
return badRequest(msg, errMsgInvalidPayload)
|
|
}
|
|
if req.TaskID != "" {
|
|
mrs, err := svc.ListTaskMRsByTask(ctx, req.TaskID)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"task_mrs": mrs})
|
|
}
|
|
if req.WorkspaceID == "" {
|
|
return badRequest(msg, "workspace_id or task_id required")
|
|
}
|
|
grouped, err := svc.ListTaskMRsByWorkspace(ctx, req.WorkspaceID)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, TaskMRsResponse{TaskMRs: grouped})
|
|
}
|
|
}
|
|
|
|
func wsGetTaskMR(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
TaskID string `json:"task_id"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.TaskID == "" {
|
|
return badRequest(msg, "task_id required")
|
|
}
|
|
mrs, err := svc.ListTaskMRsByTask(ctx, req.TaskID)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"task_mrs": mrs})
|
|
}
|
|
}
|
|
|
|
func wsGetMRFeedback(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
Project string `json:"project"`
|
|
IID int `json:"iid"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.Project == "" || req.IID <= 0 {
|
|
return badRequest(msg, "project and iid required")
|
|
}
|
|
feedback, err := svc.GetMRFeedback(ctx, req.Project, req.IID)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, feedback)
|
|
}
|
|
}
|
|
|
|
func wsGetMRFiles(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
Project string `json:"project"`
|
|
IID int `json:"iid"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.Project == "" || req.IID <= 0 {
|
|
return badRequest(msg, "project and iid required")
|
|
}
|
|
files, err := svc.GetMRFiles(ctx, req.Project, req.IID)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"files": files})
|
|
}
|
|
}
|
|
|
|
func wsGetMRCommits(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
Project string `json:"project"`
|
|
IID int `json:"iid"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.Project == "" || req.IID <= 0 {
|
|
return badRequest(msg, "project and iid required")
|
|
}
|
|
commits, err := svc.GetMRCommits(ctx, req.Project, req.IID)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"commits": commits})
|
|
}
|
|
}
|
|
|
|
func wsSyncTaskMR(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
TaskID string `json:"task_id"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.TaskID == "" {
|
|
return badRequest(msg, "task_id required")
|
|
}
|
|
rows, err := svc.TriggerMRSync(ctx, req.TaskID)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"task_mrs": rows})
|
|
}
|
|
}
|
|
|
|
func wsGetStats(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
stats, err := svc.GetStats(ctx)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, stats)
|
|
}
|
|
}
|
|
|
|
// --- Review watch handlers ---
|
|
|
|
func wsListReviewWatches(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
WorkspaceID string `json:"workspace_id"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil {
|
|
return badRequest(msg, errMsgInvalidPayload)
|
|
}
|
|
var watches []*ReviewWatch
|
|
var err error
|
|
if req.WorkspaceID == "" {
|
|
watches, err = svc.ListAllReviewWatches(ctx)
|
|
} else {
|
|
watches, err = svc.ListReviewWatches(ctx, req.WorkspaceID)
|
|
}
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"watches": watches})
|
|
}
|
|
}
|
|
|
|
func wsCreateReviewWatch(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req CreateReviewWatchRequest
|
|
if err := parseInto(msg, &req); err != nil {
|
|
return badRequest(msg, errMsgInvalidPayload)
|
|
}
|
|
w, err := svc.CreateReviewWatch(ctx, &req)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, w)
|
|
}
|
|
}
|
|
|
|
func wsUpdateReviewWatch(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
ID string `json:"id"`
|
|
UpdateReviewWatchRequest
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.ID == "" {
|
|
return badRequest(msg, errMsgIDRequired)
|
|
}
|
|
if err := svc.UpdateReviewWatch(ctx, req.ID, &req.UpdateReviewWatchRequest); err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"updated": true})
|
|
}
|
|
}
|
|
|
|
func wsDeleteReviewWatch(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
ID string `json:"id"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.ID == "" {
|
|
return badRequest(msg, errMsgIDRequired)
|
|
}
|
|
if err := svc.DeleteReviewWatch(ctx, req.ID); err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{respKeyDeleted: true})
|
|
}
|
|
}
|
|
|
|
func wsTriggerReviewWatch(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
ID string `json:"id"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.ID == "" {
|
|
return badRequest(msg, errMsgIDRequired)
|
|
}
|
|
mrs, err := svc.TriggerReviewWatch(ctx, req.ID)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"mrs": mrs})
|
|
}
|
|
}
|
|
|
|
func wsTriggerAllReviewChecks(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
n, err := svc.TriggerReviewWatchAll(ctx)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"count": n})
|
|
}
|
|
}
|
|
|
|
// --- MR watch handlers ---
|
|
|
|
func wsListMRWatches(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
SessionID string `json:"session_id"`
|
|
TaskID string `json:"task_id"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil {
|
|
return badRequest(msg, errMsgInvalidPayload)
|
|
}
|
|
switch {
|
|
case req.SessionID != "":
|
|
ws, err := svc.ListMRWatchesBySession(ctx, req.SessionID)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"watches": ws})
|
|
case req.TaskID != "":
|
|
ws, err := svc.ListMRWatchesByTask(ctx, req.TaskID)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"watches": ws})
|
|
default:
|
|
ws, err := svc.ListActiveMRWatches(ctx)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"watches": ws})
|
|
}
|
|
}
|
|
}
|
|
|
|
func wsDeleteMRWatch(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
ID string `json:"id"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.ID == "" {
|
|
return badRequest(msg, errMsgIDRequired)
|
|
}
|
|
if err := svc.DeleteMRWatch(ctx, req.ID); err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{respKeyDeleted: true})
|
|
}
|
|
}
|
|
|
|
// --- Issue watch handlers ---
|
|
|
|
func wsListIssueWatches(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
WorkspaceID string `json:"workspace_id"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil {
|
|
return badRequest(msg, errMsgInvalidPayload)
|
|
}
|
|
var watches []*IssueWatch
|
|
var err error
|
|
if req.WorkspaceID == "" {
|
|
watches, err = svc.ListAllIssueWatches(ctx)
|
|
} else {
|
|
watches, err = svc.ListIssueWatches(ctx, req.WorkspaceID)
|
|
}
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"watches": watches})
|
|
}
|
|
}
|
|
|
|
func wsCreateIssueWatch(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req CreateIssueWatchRequest
|
|
if err := parseInto(msg, &req); err != nil {
|
|
return badRequest(msg, errMsgInvalidPayload)
|
|
}
|
|
w, err := svc.CreateIssueWatch(ctx, &req)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, w)
|
|
}
|
|
}
|
|
|
|
func wsUpdateIssueWatch(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
ID string `json:"id"`
|
|
UpdateIssueWatchRequest
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.ID == "" {
|
|
return badRequest(msg, errMsgIDRequired)
|
|
}
|
|
if err := svc.UpdateIssueWatch(ctx, req.ID, &req.UpdateIssueWatchRequest); err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"updated": true})
|
|
}
|
|
}
|
|
|
|
func wsDeleteIssueWatch(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
ID string `json:"id"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.ID == "" {
|
|
return badRequest(msg, errMsgIDRequired)
|
|
}
|
|
if err := svc.DeleteIssueWatch(ctx, req.ID); err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{respKeyDeleted: true})
|
|
}
|
|
}
|
|
|
|
func wsTriggerIssueWatch(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
ID string `json:"id"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.ID == "" {
|
|
return badRequest(msg, errMsgIDRequired)
|
|
}
|
|
issues, err := svc.TriggerIssueWatch(ctx, req.ID)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"issues": issues})
|
|
}
|
|
}
|
|
|
|
func wsTriggerAllIssueChecks(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
n, err := svc.TriggerIssueWatchAll(ctx)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"count": n})
|
|
}
|
|
}
|
|
|
|
// --- Write actions ---
|
|
|
|
func wsMergeMR(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
Project string `json:"project"`
|
|
IID int `json:"iid"`
|
|
Method string `json:"method"`
|
|
SquashCommitMessage string `json:"squash_commit_message"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.Project == "" || req.IID <= 0 {
|
|
return badRequest(msg, "project and iid required")
|
|
}
|
|
mr, err := svc.MergeMR(ctx, req.Project, req.IID, req.Method, req.SquashCommitMessage)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, mr)
|
|
}
|
|
}
|
|
|
|
func wsApproveMR(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
Project string `json:"project"`
|
|
IID int `json:"iid"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.Project == "" || req.IID <= 0 {
|
|
return badRequest(msg, "project and iid required")
|
|
}
|
|
if err := svc.SubmitMRApproval(ctx, req.Project, req.IID); err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"approved": true})
|
|
}
|
|
}
|
|
|
|
func wsUnapproveMR(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
Project string `json:"project"`
|
|
IID int `json:"iid"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.Project == "" || req.IID <= 0 {
|
|
return badRequest(msg, "project and iid required")
|
|
}
|
|
if err := svc.SubmitMRUnapproval(ctx, req.Project, req.IID); err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"unapproved": true})
|
|
}
|
|
}
|
|
|
|
func wsSetMRLabels(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
Project string `json:"project"`
|
|
IID int `json:"iid"`
|
|
Labels []string `json:"labels"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.Project == "" || req.IID <= 0 {
|
|
return badRequest(msg, "project and iid required")
|
|
}
|
|
if err := svc.SetMRLabels(ctx, req.Project, req.IID, req.Labels); err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"updated": true})
|
|
}
|
|
}
|
|
|
|
func wsSetMRAssignees(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
Project string `json:"project"`
|
|
IID int `json:"iid"`
|
|
AssigneeIDs []int `json:"assignee_ids"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.Project == "" || req.IID <= 0 {
|
|
return badRequest(msg, "project and iid required")
|
|
}
|
|
if err := svc.SetMRAssignees(ctx, req.Project, req.IID, req.AssigneeIDs); err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"updated": true})
|
|
}
|
|
}
|
|
|
|
func wsNewDiscussionNote(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
Project string `json:"project"`
|
|
IID int `json:"iid"`
|
|
DiscussionID string `json:"discussion_id"`
|
|
Body string `json:"body"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.Project == "" || req.IID <= 0 || req.DiscussionID == "" {
|
|
return badRequest(msg, "project, iid, discussion_id required")
|
|
}
|
|
if strings.TrimSpace(req.Body) == "" {
|
|
return badRequest(msg, "body required")
|
|
}
|
|
note, err := svc.CreateMRDiscussionNote(ctx, req.Project, req.IID, req.DiscussionID, req.Body)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, note)
|
|
}
|
|
}
|
|
|
|
func wsResolveDiscussion(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
Project string `json:"project"`
|
|
IID int `json:"iid"`
|
|
DiscussionID string `json:"discussion_id"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.Project == "" || req.IID <= 0 || req.DiscussionID == "" {
|
|
return badRequest(msg, "project, iid, discussion_id required")
|
|
}
|
|
if err := svc.ResolveMRDiscussion(ctx, req.Project, req.IID, req.DiscussionID); err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"resolved": true})
|
|
}
|
|
}
|
|
|
|
func wsGetProjectMergeMethods(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
Project string `json:"project"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.Project == "" {
|
|
return badRequest(msg, "project required")
|
|
}
|
|
methods, err := svc.GetProjectMergeMethods(ctx, req.Project)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, methods)
|
|
}
|
|
}
|
|
|
|
// --- Projects ---
|
|
|
|
func wsListUserProjects(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
projects, err := svc.ListUserProjects(ctx)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"projects": projects})
|
|
}
|
|
}
|
|
|
|
func wsSearchProjects(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
Query string `json:"query"`
|
|
Limit int `json:"limit"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil {
|
|
return badRequest(msg, errMsgInvalidPayload)
|
|
}
|
|
projects, err := svc.SearchProjects(ctx, req.Query, req.Limit)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"projects": projects})
|
|
}
|
|
}
|
|
|
|
func wsListProjectBranches(svc *Service, _ *logger.Logger) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
Project string `json:"project"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.Project == "" {
|
|
return badRequest(msg, "project required")
|
|
}
|
|
branches, err := svc.ListProjectBranches(ctx, req.Project)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]interface{}{"branches": branches})
|
|
}
|
|
}
|
|
|
|
// --- Action presets ---
|
|
|
|
func wsListActionPresets(svc *Service) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
WorkspaceID string `json:"workspace_id"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.WorkspaceID == "" {
|
|
return badRequest(msg, "workspace_id required")
|
|
}
|
|
presets, err := svc.GetActionPresetsOrDefault(ctx, req.WorkspaceID)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, presets)
|
|
}
|
|
}
|
|
|
|
func wsUpdateActionPresets(svc *Service) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req UpdateActionPresetsRequest
|
|
if err := parseInto(msg, &req); err != nil || req.WorkspaceID == "" {
|
|
return badRequest(msg, "workspace_id required")
|
|
}
|
|
presets, err := svc.UpdateActionPresets(ctx, &req)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, presets)
|
|
}
|
|
}
|
|
|
|
func wsResetActionPresets(svc *Service) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
var req struct {
|
|
WorkspaceID string `json:"workspace_id"`
|
|
}
|
|
if err := parseInto(msg, &req); err != nil || req.WorkspaceID == "" {
|
|
return badRequest(msg, "workspace_id required")
|
|
}
|
|
presets, err := svc.ResetActionPresets(ctx, req.WorkspaceID)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, presets)
|
|
}
|
|
}
|
|
|
|
// --- Cleanup ---
|
|
|
|
func wsCleanupReviewTasks(svc *Service) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
n, err := svc.CleanupAllReviewTasks(ctx)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]int{respKeyDeleted: n})
|
|
}
|
|
}
|
|
|
|
func wsCleanupIssueTasks(svc *Service) ws.HandlerFunc {
|
|
return func(ctx context.Context, msg *ws.Message) (*ws.Message, error) {
|
|
n, err := svc.CleanupAllIssueTasks(ctx)
|
|
if err != nil {
|
|
return internalError(msg, err)
|
|
}
|
|
return okResponse(msg, map[string]int{respKeyDeleted: n})
|
|
}
|
|
}
|