From 060d44e37988e1ddb381fc93e5ad5e5caa7a85c1 Mon Sep 17 00:00:00 2001 From: beardev-in Date: Sun, 3 May 2026 15:36:04 +0530 Subject: [PATCH] fix: adapt to upstream refactor, fix IssueAssignOrRemoveProject signature and column move logic --- models/project/issue.go | 17 ++++++++ routers/api/v1/repo/project.go | 49 ++++++++++++++++++---- tests/integration/api_repo_project_test.go | 6 +-- tests/integration/project_test.go | 5 +-- 4 files changed, 62 insertions(+), 15 deletions(-) diff --git a/models/project/issue.go b/models/project/issue.go index 85456b515cd..eaf79d2cfd3 100644 --- a/models/project/issue.go +++ b/models/project/issue.go @@ -95,3 +95,20 @@ func DeleteAllProjectIssueByIssueIDsAndProjectIDs(ctx context.Context, issueIDs, _, err := db.GetEngine(ctx).In("project_id", projectIDs).In("issue_id", issueIDs).Delete(&ProjectIssue{}) return err } + + +// MoveIssueToColumn moves a single issue to a specific column within a project. +func MoveIssueToColumn(ctx context.Context, issueID, projectID, columnID int64) error { + nextSorting, err := GetColumnIssueNextSorting(ctx, projectID, columnID) + if err != nil { + return err + } + _, err = db.GetEngine(ctx). + Where("issue_id=? AND project_id=?", issueID, projectID). + Cols("project_board_id", "sorting"). + Update(&ProjectIssue{ + ProjectColumnID: columnID, + Sorting: nextSorting, + }) + return err +} \ No newline at end of file diff --git a/routers/api/v1/repo/project.go b/routers/api/v1/repo/project.go index acd514deab6..048992d6e89 100644 --- a/routers/api/v1/repo/project.go +++ b/routers/api/v1/repo/project.go @@ -836,11 +836,15 @@ func assignIssueToProjectColumn(ctx *context.APIContext, add bool) { return } - projectID := column.ProjectID + if err := issue.LoadProjects(ctx); err != nil { + ctx.APIErrorInternal(err) + return + } + currentProjectIDs := make([]int64, 0, len(issue.Projects)) + for _, p := range issue.Projects { + currentProjectIDs = append(currentProjectIDs, p.ID) + } if !add { - // Confirm the issue is currently in this specific column before removing, - // since IssueAssignOrRemoveProject(projectID=0) clears the issue's project - // assignment unconditionally. exists, err := project_model.IsIssueInColumn(ctx, issue.ID, column.ProjectID, column.ID) if err != nil { ctx.APIErrorInternal(err) @@ -850,11 +854,38 @@ func assignIssueToProjectColumn(ctx *context.APIContext, add bool) { ctx.APIErrorNotFound() return } - projectID = 0 - } - if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, ctx.Doer, []int64{projectID}); err != nil { - ctx.APIErrorInternal(err) - return + newProjectIDs := make([]int64, 0, len(currentProjectIDs)) + for _, id := range currentProjectIDs { + if id != column.ProjectID { + newProjectIDs = append(newProjectIDs, id) + } + } + if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, ctx.Doer, newProjectIDs); err != nil { + ctx.APIErrorInternal(err) + return + } + } else { + // Check if issue is already in this project + alreadyInProject := false + for _, id := range currentProjectIDs { + if id == column.ProjectID { + alreadyInProject = true + break + } + } + if !alreadyInProject { + // Add to project first (lands in default column) + newProjectIDs := append(currentProjectIDs, column.ProjectID) + if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, ctx.Doer, newProjectIDs); err != nil { + ctx.APIErrorInternal(err) + return + } + } + // Move to target column + if err := project_model.MoveIssueToColumn(ctx, issue.ID, column.ProjectID, column.ID); err != nil { + ctx.APIErrorInternal(err) + return + } } if add { diff --git a/tests/integration/api_repo_project_test.go b/tests/integration/api_repo_project_test.go index c3be2851252..ec62d4e9620 100644 --- a/tests/integration/api_repo_project_test.go +++ b/tests/integration/api_repo_project_test.go @@ -563,9 +563,9 @@ func testAPIListProjectColumnIssues(t *testing.T) { err = project_model.NewColumn(t.Context(), column) assert.NoError(t, err) - err = issues_model.IssueAssignOrRemoveProject(t.Context(), issue, owner, project.ID, column.ID) + err = issues_model.IssueAssignOrRemoveProject(t.Context(), issue, owner, []int64{project.ID}) assert.NoError(t, err) - err = issues_model.IssueAssignOrRemoveProject(t.Context(), pull, owner, project.ID, column.ID) + err = issues_model.IssueAssignOrRemoveProject(t.Context(), pull, owner, []int64{project.ID}) assert.NoError(t, err) token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeReadIssue) @@ -617,7 +617,7 @@ func testAPIRemoveIssueFromProjectColumn(t *testing.T) { err = project_model.NewColumn(t.Context(), otherColumn) assert.NoError(t, err) - err = issues_model.IssueAssignOrRemoveProject(t.Context(), issue, owner, project.ID, column.ID) + err = issues_model.IssueAssignOrRemoveProject(t.Context(), issue, owner, []int64{project.ID}) assert.NoError(t, err) token := getUserToken(t, owner.Name, auth_model.AccessTokenScopeWriteIssue) diff --git a/tests/integration/project_test.go b/tests/integration/project_test.go index 00764bf883e..adef22b8e01 100644 --- a/tests/integration/project_test.go +++ b/tests/integration/project_test.go @@ -298,11 +298,10 @@ func TestOrgProjectFilterByMilestone(t *testing.T) { columns, err := project_model.GetProjectColumns(t.Context(), project.ID, db.ListOptionsAll) require.NoError(t, err) require.NotEmpty(t, columns) - defaultColumnID := columns[0].ID // Add issues to the project - require.NoError(t, issues_model.IssueAssignOrRemoveProject(t.Context(), issue16, user1, project.ID, defaultColumnID)) - require.NoError(t, issues_model.IssueAssignOrRemoveProject(t.Context(), issue17, user1, project.ID, defaultColumnID)) + require.NoError(t, issues_model.IssueAssignOrRemoveProject(t.Context(), issue16, user1, []int64{project.ID})) + require.NoError(t, issues_model.IssueAssignOrRemoveProject(t.Context(), issue17, user1, []int64{project.ID})) sess := loginUser(t, "user1") projectURL := fmt.Sprintf("/org3/-/projects/%d", project.ID)