From 0a5e4fcd9138eddb0b09718a6773e93370e1450f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Mar 2024 11:22:01 +0800 Subject: [PATCH 01/13] Improve migration v292 --- models/migrations/v1_22/v292.go | 66 ++++++++++++++------------------- 1 file changed, 28 insertions(+), 38 deletions(-) diff --git a/models/migrations/v1_22/v292.go b/models/migrations/v1_22/v292.go index 7c051a2b753de..3a641522ec07c 100644 --- a/models/migrations/v1_22/v292.go +++ b/models/migrations/v1_22/v292.go @@ -7,7 +7,6 @@ import ( "code.gitea.io/gitea/models/project" "code.gitea.io/gitea/modules/setting" - "xorm.io/builder" "xorm.io/xorm" ) @@ -27,57 +26,48 @@ func CheckProjectColumnsConsistency(x *xorm.Engine) error { start := 0 + type Project struct { + ID int64 + CreatorID int64 + BoardID int64 + } + for { - var projects []project.Project - if err := sess.SQL("SELECT DISTINCT `p`.`id`, `p`.`creator_id` FROM `project` `p` WHERE (SELECT COUNT(*) FROM `project_board` `pb` WHERE `pb`.`project_id` = `p`.`id` AND `pb`.`default` = ?) != 1", true). + if start%200 == 0 { + if err := sess.Begin(); err != nil { + return err + } + } + + var projects []*Project + if err := sess.Select("project.id as id, project.creator_id, project_board.id as board_id"). + Join("LEFT", "project_board", "project_board.project_id = project.id AND project_board.default=?", true). + Where("project_board.id is NULL OR project_board.id = 0"). Limit(limit, start). Find(&projects); err != nil { return err } - if len(projects) == 0 { - break - } - start += len(projects) - for _, p := range projects { - var boards []project.Board - if err := sess.Where("project_id=? AND `default` = ?", p.ID, true).OrderBy("sorting").Find(&boards); err != nil { - return err - } - - if len(boards) == 0 { - if _, err := sess.Insert(project.Board{ - ProjectID: p.ID, - Default: true, - Title: "Uncategorized", - CreatorID: p.CreatorID, - }); err != nil { - return err - } - continue - } - - var boardsToUpdate []int64 - for id, b := range boards { - if id > 0 { - boardsToUpdate = append(boardsToUpdate, b.ID) - } - } - - if _, err := sess.Where(builder.Eq{"project_id": p.ID}.And(builder.In("id", boardsToUpdate))). - Cols("`default`").Update(&project.Board{Default: false}); err != nil { + if _, err := sess.Insert(project.Board{ + ProjectID: p.ID, + Default: true, + Title: "Uncategorized", + CreatorID: p.CreatorID, + }); err != nil { return err } } - if start%1000 == 0 { + start += len(projects) + if (start > 0 && start%200 == 0) || len(projects) == 0 { if err := sess.Commit(); err != nil { return err } - if err := sess.Begin(); err != nil { - return err - } + } + + if len(projects) == 0 { + break } } From 788c512e4a0a83688182c5e3a5cefd51f663d430 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Mar 2024 11:24:10 +0800 Subject: [PATCH 02/13] Use local struct --- models/migrations/v1_22/v292.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/models/migrations/v1_22/v292.go b/models/migrations/v1_22/v292.go index 3a641522ec07c..a1f651ba4f4f8 100644 --- a/models/migrations/v1_22/v292.go +++ b/models/migrations/v1_22/v292.go @@ -4,8 +4,8 @@ package v1_22 //nolint import ( - "code.gitea.io/gitea/models/project" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" "xorm.io/xorm" ) @@ -32,6 +32,20 @@ func CheckProjectColumnsConsistency(x *xorm.Engine) error { BoardID int64 } + type ProjectBoard struct { + ID int64 `xorm:"pk autoincr"` + Title string + Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board + Sorting int8 `xorm:"NOT NULL DEFAULT 0"` + Color string `xorm:"VARCHAR(7)"` + + ProjectID int64 `xorm:"INDEX NOT NULL"` + CreatorID int64 `xorm:"NOT NULL"` + + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + for { if start%200 == 0 { if err := sess.Begin(); err != nil { @@ -49,7 +63,7 @@ func CheckProjectColumnsConsistency(x *xorm.Engine) error { } for _, p := range projects { - if _, err := sess.Insert(project.Board{ + if _, err := sess.Insert(ProjectBoard{ ProjectID: p.ID, Default: true, Title: "Uncategorized", From 3fb007c7097434922673d7f23114c351d249caf9 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Mar 2024 12:16:27 +0800 Subject: [PATCH 03/13] Remove multiple defaults of projects and keep the last one --- models/migrations/v1_22/v292.go | 32 ++++++++++++++++++++++++++-- models/migrations/v1_22/v292_test.go | 6 +++--- tests/sqlite.ini.tmpl | 3 ++- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/models/migrations/v1_22/v292.go b/models/migrations/v1_22/v292.go index a1f651ba4f4f8..e82b674744799 100644 --- a/models/migrations/v1_22/v292.go +++ b/models/migrations/v1_22/v292.go @@ -55,7 +55,7 @@ func CheckProjectColumnsConsistency(x *xorm.Engine) error { var projects []*Project if err := sess.Select("project.id as id, project.creator_id, project_board.id as board_id"). - Join("LEFT", "project_board", "project_board.project_id = project.id AND project_board.default=?", true). + Join("LEFT", "project_board", "project_board.project_id = project.id AND project_board.`default`=?", true). Where("project_board.id is NULL OR project_board.id = 0"). Limit(limit, start). Find(&projects); err != nil { @@ -85,5 +85,33 @@ func CheckProjectColumnsConsistency(x *xorm.Engine) error { } } - return sess.Commit() + return removeDuplicatedBoardDefault(x) +} + +func removeDuplicatedBoardDefault(x *xorm.Engine) error { + type ProjectInfo struct { + ProjectID int64 + DefaultNum int + } + var projects []ProjectInfo + if err := x.Select("project_id, count(*) AS default_num"). + Table("project_board"). + Where("`default` = ?", true). + GroupBy("project_id"). + Having("default_num > 1"). + Find(&projects); err != nil { + return err + } + + for _, project := range projects { + if _, err := x.Where("project_id=?", project.ProjectID). + Table("project_board"). + Limit(project.DefaultNum - 1). + Update(map[string]bool{ + "`default`": false, + }); err != nil { + return err + } + } + return nil } diff --git a/models/migrations/v1_22/v292_test.go b/models/migrations/v1_22/v292_test.go index 5e32e0220f590..ccc92f39a60d2 100644 --- a/models/migrations/v1_22/v292_test.go +++ b/models/migrations/v1_22/v292_test.go @@ -31,14 +31,14 @@ func Test_CheckProjectColumnsConsistency(t *testing.T) { assert.Equal(t, int64(1), defaultBoard.ProjectID) assert.True(t, defaultBoard.Default) - // check if multiple defaults were removed + // check if multiple defaults, previous were removed and last will be kept expectDefaultBoard, err := project.GetBoard(db.DefaultContext, 2) assert.NoError(t, err) assert.Equal(t, int64(2), expectDefaultBoard.ProjectID) - assert.True(t, expectDefaultBoard.Default) + assert.False(t, expectDefaultBoard.Default) expectNonDefaultBoard, err := project.GetBoard(db.DefaultContext, 3) assert.NoError(t, err) assert.Equal(t, int64(2), expectNonDefaultBoard.ProjectID) - assert.False(t, expectNonDefaultBoard.Default) + assert.True(t, expectNonDefaultBoard.Default) } diff --git a/tests/sqlite.ini.tmpl b/tests/sqlite.ini.tmpl index 1cbcd8b2e591a..7e1794f777145 100644 --- a/tests/sqlite.ini.tmpl +++ b/tests/sqlite.ini.tmpl @@ -4,6 +4,7 @@ RUN_MODE = prod [database] DB_TYPE = sqlite3 PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/gitea.db +LOG_SQL = true [indexer] REPO_INDEXER_ENABLED = true @@ -83,7 +84,7 @@ PROVIDER_CONFIG = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/data/sessions MODE = {{TEST_LOGGER}} ROOT_PATH = {{REPO_TEST_DIR}}sqlite-log ENABLE_SSH_LOG = true -logger.xorm.MODE = file +logger.xorm.MODE = file, console [log.test] LEVEL = Info From 09637614c2b080d5224d0c05450868e7bd769a5c Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Mar 2024 12:24:45 +0800 Subject: [PATCH 04/13] Fix bug --- models/migrations/v1_22/v292.go | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/models/migrations/v1_22/v292.go b/models/migrations/v1_22/v292.go index e82b674744799..5bc72dccc58a1 100644 --- a/models/migrations/v1_22/v292.go +++ b/models/migrations/v1_22/v292.go @@ -24,8 +24,6 @@ func CheckProjectColumnsConsistency(x *xorm.Engine) error { limit = 50 } - start := 0 - type Project struct { ID int64 CreatorID int64 @@ -47,20 +45,23 @@ func CheckProjectColumnsConsistency(x *xorm.Engine) error { } for { - if start%200 == 0 { - if err := sess.Begin(); err != nil { - return err - } + if err := sess.Begin(); err != nil { + return err } + // all these projects without defaults will be fixed in the same loop, so + // we just need to always get projects without defaults until no such project var projects []*Project if err := sess.Select("project.id as id, project.creator_id, project_board.id as board_id"). Join("LEFT", "project_board", "project_board.project_id = project.id AND project_board.`default`=?", true). Where("project_board.id is NULL OR project_board.id = 0"). - Limit(limit, start). + Limit(limit). Find(&projects); err != nil { return err } + if len(projects) == 0 { + break + } for _, p := range projects { if _, err := sess.Insert(ProjectBoard{ @@ -73,15 +74,8 @@ func CheckProjectColumnsConsistency(x *xorm.Engine) error { } } - start += len(projects) - if (start > 0 && start%200 == 0) || len(projects) == 0 { - if err := sess.Commit(); err != nil { - return err - } - } - - if len(projects) == 0 { - break + if err := sess.Commit(); err != nil { + return err } } From bb2d1a095220251a6d1952630edbd3acc6d7f7a4 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Mar 2024 12:33:23 +0800 Subject: [PATCH 05/13] Fix test --- models/migrations/v1_22/v292.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/migrations/v1_22/v292.go b/models/migrations/v1_22/v292.go index 5bc72dccc58a1..988a4363efa1c 100644 --- a/models/migrations/v1_22/v292.go +++ b/models/migrations/v1_22/v292.go @@ -92,7 +92,7 @@ func removeDuplicatedBoardDefault(x *xorm.Engine) error { Table("project_board"). Where("`default` = ?", true). GroupBy("project_id"). - Having("default_num > 1"). + Having("count(*) > 1"). Find(&projects); err != nil { return err } From 9544b7048c3167184aca4e6dc926425b34daf615 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Mar 2024 13:15:52 +0800 Subject: [PATCH 06/13] Fix broken migration --- models/migrations/migrations.go | 2 + models/migrations/v1_22/v292.go | 110 +---------------- models/migrations/v1_22/v293.go | 111 ++++++++++++++++++ .../v1_22/{v292_test.go => v293_test.go} | 0 4 files changed, 117 insertions(+), 106 deletions(-) create mode 100644 models/migrations/v1_22/v293.go rename models/migrations/v1_22/{v292_test.go => v293_test.go} (100%) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 77895fba61899..0daa799ff6754 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -569,6 +569,8 @@ var migrations = []Migration{ // v291 -> v292 NewMigration("Add Index to attachment.comment_id", v1_22.AddCommentIDIndexofAttachment), // v292 -> v293 + NewMigration("Ensure every project has exactly one default column - No Op", noopMigration), + // v293 -> v294 NewMigration("Ensure every project has exactly one default column", v1_22.CheckProjectColumnsConsistency), } diff --git a/models/migrations/v1_22/v292.go b/models/migrations/v1_22/v292.go index 988a4363efa1c..beca556aee298 100644 --- a/models/migrations/v1_22/v292.go +++ b/models/migrations/v1_22/v292.go @@ -3,109 +3,7 @@ package v1_22 //nolint -import ( - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/timeutil" - - "xorm.io/xorm" -) - -// CheckProjectColumnsConsistency ensures there is exactly one default board per project present -func CheckProjectColumnsConsistency(x *xorm.Engine) error { - sess := x.NewSession() - defer sess.Close() - - if err := sess.Begin(); err != nil { - return err - } - - limit := setting.Database.IterateBufferSize - if limit <= 0 { - limit = 50 - } - - type Project struct { - ID int64 - CreatorID int64 - BoardID int64 - } - - type ProjectBoard struct { - ID int64 `xorm:"pk autoincr"` - Title string - Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board - Sorting int8 `xorm:"NOT NULL DEFAULT 0"` - Color string `xorm:"VARCHAR(7)"` - - ProjectID int64 `xorm:"INDEX NOT NULL"` - CreatorID int64 `xorm:"NOT NULL"` - - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` - } - - for { - if err := sess.Begin(); err != nil { - return err - } - - // all these projects without defaults will be fixed in the same loop, so - // we just need to always get projects without defaults until no such project - var projects []*Project - if err := sess.Select("project.id as id, project.creator_id, project_board.id as board_id"). - Join("LEFT", "project_board", "project_board.project_id = project.id AND project_board.`default`=?", true). - Where("project_board.id is NULL OR project_board.id = 0"). - Limit(limit). - Find(&projects); err != nil { - return err - } - if len(projects) == 0 { - break - } - - for _, p := range projects { - if _, err := sess.Insert(ProjectBoard{ - ProjectID: p.ID, - Default: true, - Title: "Uncategorized", - CreatorID: p.CreatorID, - }); err != nil { - return err - } - } - - if err := sess.Commit(); err != nil { - return err - } - } - - return removeDuplicatedBoardDefault(x) -} - -func removeDuplicatedBoardDefault(x *xorm.Engine) error { - type ProjectInfo struct { - ProjectID int64 - DefaultNum int - } - var projects []ProjectInfo - if err := x.Select("project_id, count(*) AS default_num"). - Table("project_board"). - Where("`default` = ?", true). - GroupBy("project_id"). - Having("count(*) > 1"). - Find(&projects); err != nil { - return err - } - - for _, project := range projects { - if _, err := x.Where("project_id=?", project.ProjectID). - Table("project_board"). - Limit(project.DefaultNum - 1). - Update(map[string]bool{ - "`default`": false, - }); err != nil { - return err - } - } - return nil -} +// NOTE: noop the original migration has bug which some projects will be skip, so +// these projects will have no default board. +// So that this migration will be skipped and go to v293.go +// This file is a placeholder so that readers can know what happened diff --git a/models/migrations/v1_22/v293.go b/models/migrations/v1_22/v293.go new file mode 100644 index 0000000000000..988a4363efa1c --- /dev/null +++ b/models/migrations/v1_22/v293.go @@ -0,0 +1,111 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint + +import ( + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/xorm" +) + +// CheckProjectColumnsConsistency ensures there is exactly one default board per project present +func CheckProjectColumnsConsistency(x *xorm.Engine) error { + sess := x.NewSession() + defer sess.Close() + + if err := sess.Begin(); err != nil { + return err + } + + limit := setting.Database.IterateBufferSize + if limit <= 0 { + limit = 50 + } + + type Project struct { + ID int64 + CreatorID int64 + BoardID int64 + } + + type ProjectBoard struct { + ID int64 `xorm:"pk autoincr"` + Title string + Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board + Sorting int8 `xorm:"NOT NULL DEFAULT 0"` + Color string `xorm:"VARCHAR(7)"` + + ProjectID int64 `xorm:"INDEX NOT NULL"` + CreatorID int64 `xorm:"NOT NULL"` + + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + } + + for { + if err := sess.Begin(); err != nil { + return err + } + + // all these projects without defaults will be fixed in the same loop, so + // we just need to always get projects without defaults until no such project + var projects []*Project + if err := sess.Select("project.id as id, project.creator_id, project_board.id as board_id"). + Join("LEFT", "project_board", "project_board.project_id = project.id AND project_board.`default`=?", true). + Where("project_board.id is NULL OR project_board.id = 0"). + Limit(limit). + Find(&projects); err != nil { + return err + } + if len(projects) == 0 { + break + } + + for _, p := range projects { + if _, err := sess.Insert(ProjectBoard{ + ProjectID: p.ID, + Default: true, + Title: "Uncategorized", + CreatorID: p.CreatorID, + }); err != nil { + return err + } + } + + if err := sess.Commit(); err != nil { + return err + } + } + + return removeDuplicatedBoardDefault(x) +} + +func removeDuplicatedBoardDefault(x *xorm.Engine) error { + type ProjectInfo struct { + ProjectID int64 + DefaultNum int + } + var projects []ProjectInfo + if err := x.Select("project_id, count(*) AS default_num"). + Table("project_board"). + Where("`default` = ?", true). + GroupBy("project_id"). + Having("count(*) > 1"). + Find(&projects); err != nil { + return err + } + + for _, project := range projects { + if _, err := x.Where("project_id=?", project.ProjectID). + Table("project_board"). + Limit(project.DefaultNum - 1). + Update(map[string]bool{ + "`default`": false, + }); err != nil { + return err + } + } + return nil +} diff --git a/models/migrations/v1_22/v292_test.go b/models/migrations/v1_22/v293_test.go similarity index 100% rename from models/migrations/v1_22/v292_test.go rename to models/migrations/v1_22/v293_test.go From cc0b580f3f51935d9c75fdb30e5593b643f14fbf Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Mar 2024 13:43:34 +0800 Subject: [PATCH 07/13] revert unnecessary change --- tests/sqlite.ini.tmpl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/sqlite.ini.tmpl b/tests/sqlite.ini.tmpl index 7e1794f777145..1cbcd8b2e591a 100644 --- a/tests/sqlite.ini.tmpl +++ b/tests/sqlite.ini.tmpl @@ -4,7 +4,6 @@ RUN_MODE = prod [database] DB_TYPE = sqlite3 PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/gitea.db -LOG_SQL = true [indexer] REPO_INDEXER_ENABLED = true @@ -84,7 +83,7 @@ PROVIDER_CONFIG = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/data/sessions MODE = {{TEST_LOGGER}} ROOT_PATH = {{REPO_TEST_DIR}}sqlite-log ENABLE_SSH_LOG = true -logger.xorm.MODE = file, console +logger.xorm.MODE = file [log.test] LEVEL = Info From 4455719870b8a863c9e2dd7522c8f40c32dd6e89 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Mar 2024 14:17:36 +0800 Subject: [PATCH 08/13] fix migration --- models/migrations/v1_22/v293.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/models/migrations/v1_22/v293.go b/models/migrations/v1_22/v293.go index 988a4363efa1c..a87c0c28434ae 100644 --- a/models/migrations/v1_22/v293.go +++ b/models/migrations/v1_22/v293.go @@ -15,10 +15,6 @@ func CheckProjectColumnsConsistency(x *xorm.Engine) error { sess := x.NewSession() defer sess.Close() - if err := sess.Begin(); err != nil { - return err - } - limit := setting.Database.IterateBufferSize if limit <= 0 { limit = 50 From 0c28c14c33ac1fdef7ab0e43af6925867c1cbd7b Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Mar 2024 15:19:08 +0800 Subject: [PATCH 09/13] Fix test --- models/migrations/v1_22/v293.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/models/migrations/v1_22/v293.go b/models/migrations/v1_22/v293.go index a87c0c28434ae..d15ee80e01227 100644 --- a/models/migrations/v1_22/v293.go +++ b/models/migrations/v1_22/v293.go @@ -55,9 +55,6 @@ func CheckProjectColumnsConsistency(x *xorm.Engine) error { Find(&projects); err != nil { return err } - if len(projects) == 0 { - break - } for _, p := range projects { if _, err := sess.Insert(ProjectBoard{ @@ -69,11 +66,15 @@ func CheckProjectColumnsConsistency(x *xorm.Engine) error { return err } } - if err := sess.Commit(); err != nil { return err } + + if len(projects) == 0 { + break + } } + sess.Close() return removeDuplicatedBoardDefault(x) } @@ -97,6 +98,7 @@ func removeDuplicatedBoardDefault(x *xorm.Engine) error { if _, err := x.Where("project_id=?", project.ProjectID). Table("project_board"). Limit(project.DefaultNum - 1). + Desc("id"). Update(map[string]bool{ "`default`": false, }); err != nil { From 7bd2cc1cf86ebfdfa4b706bf558649f55b79cdaf Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Mar 2024 15:24:59 +0800 Subject: [PATCH 10/13] Add transaction for projects operations --- models/project/board.go | 89 ++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 49 deletions(-) diff --git a/models/project/board.go b/models/project/board.go index 5605f259b5005..4eedda7d2b4ed 100644 --- a/models/project/board.go +++ b/models/project/board.go @@ -260,71 +260,62 @@ func (p *Project) GetBoards(ctx context.Context) (BoardList, error) { // getDefaultBoard return default board and ensure only one exists func (p *Project) getDefaultBoard(ctx context.Context) (*Board, error) { - var boards []Board - if err := db.GetEngine(ctx).Where("project_id=? AND `default` = ?", p.ID, true).OrderBy("sorting").Find(&boards); err != nil { + var board Board + has, err := db.GetEngine(ctx). + Where("project_id=? AND `default` = ?", p.ID, true). + Desc("id").Get(&board) + if err != nil { return nil, err } - // create a default board if none is found - if len(boards) == 0 { - board := Board{ - ProjectID: p.ID, - Default: true, - Title: "Uncategorized", - CreatorID: p.CreatorID, - } - if _, err := db.GetEngine(ctx).Insert(); err != nil { - return nil, err - } + if has { return &board, nil } - // unset default boards where too many default boards exist - if len(boards) > 1 { - var boardsToUpdate []int64 - for id, b := range boards { - if id > 0 { - boardsToUpdate = append(boardsToUpdate, b.ID) - } - } - - if _, err := db.GetEngine(ctx).Where(builder.Eq{"project_id": p.ID}.And(builder.In("id", boardsToUpdate))). - Cols("`default`").Update(&Board{Default: false}); err != nil { - return nil, err - } + // create a default board if none is found + board = Board{ + ProjectID: p.ID, + Default: true, + Title: "Uncategorized", + CreatorID: p.CreatorID, } - - return &boards[0], nil + if _, err := db.GetEngine(ctx).Insert(); err != nil { + return nil, err + } + return &board, nil } // SetDefaultBoard represents a board for issues not assigned to one func SetDefaultBoard(ctx context.Context, projectID, boardID int64) error { - if _, err := GetBoard(ctx, boardID); err != nil { - return err - } - - if _, err := db.GetEngine(ctx).Where(builder.Eq{ - "project_id": projectID, - "`default`": true, - }).Cols("`default`").Update(&Board{Default: false}); err != nil { - return err - } + return db.WithTx(ctx, func(ctx context.Context) error { + if _, err := GetBoard(ctx, boardID); err != nil { + return err + } - _, err := db.GetEngine(ctx).ID(boardID).Where(builder.Eq{"project_id": projectID}). - Cols("`default`").Update(&Board{Default: true}) + if _, err := db.GetEngine(ctx).Where(builder.Eq{ + "project_id": projectID, + "`default`": true, + }).Cols("`default`").Update(&Board{Default: false}); err != nil { + return err + } - return err + _, err := db.GetEngine(ctx).ID(boardID). + Where(builder.Eq{"project_id": projectID}). + Cols("`default`").Update(&Board{Default: true}) + return err + }) } // UpdateBoardSorting update project board sorting func UpdateBoardSorting(ctx context.Context, bs BoardList) error { - for i := range bs { - _, err := db.GetEngine(ctx).ID(bs[i].ID).Cols( - "sorting", - ).Update(bs[i]) - if err != nil { - return err + return db.WithTx(ctx, func(ctx context.Context) error { + for i := range bs { + if _, err := db.GetEngine(ctx).ID(bs[i].ID).Cols( + "sorting", + ).Update(bs[i]); err != nil { + return err + } } - } - return nil + return nil + }) } From 94ad45c14c77ae030ea2a6c04ed69979da7fe7a1 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Mar 2024 15:35:38 +0800 Subject: [PATCH 11/13] Fix bug --- models/migrations/v1_22/v293.go | 1 - 1 file changed, 1 deletion(-) diff --git a/models/migrations/v1_22/v293.go b/models/migrations/v1_22/v293.go index d15ee80e01227..53cc719294bdb 100644 --- a/models/migrations/v1_22/v293.go +++ b/models/migrations/v1_22/v293.go @@ -98,7 +98,6 @@ func removeDuplicatedBoardDefault(x *xorm.Engine) error { if _, err := x.Where("project_id=?", project.ProjectID). Table("project_board"). Limit(project.DefaultNum - 1). - Desc("id"). Update(map[string]bool{ "`default`": false, }); err != nil { From 69625eaa6404aa2b87d1d593a5bde0a82b19f76f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Mar 2024 16:00:53 +0800 Subject: [PATCH 12/13] Fix test --- models/project/board_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/models/project/board_test.go b/models/project/board_test.go index c1c6f0180bd52..71ba29a5896dc 100644 --- a/models/project/board_test.go +++ b/models/project/board_test.go @@ -31,8 +31,12 @@ func TestGetDefaultBoard(t *testing.T) { board, err = projectWithMultipleDefaults.getDefaultBoard(db.DefaultContext) assert.NoError(t, err) assert.Equal(t, int64(6), board.ProjectID) - assert.Equal(t, int64(8), board.ID) + assert.Equal(t, int64(9), board.ID) + // set 8 as default board + assert.NoError(t, SetDefaultBoard(db.DefaultContext, board.ProjectID, 8)) + + // then 9 will become a non-default board board, err = GetBoard(db.DefaultContext, 9) assert.NoError(t, err) assert.Equal(t, int64(6), board.ProjectID) From 296387c82181361e5077b962fb708328cb458a07 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Thu, 28 Mar 2024 10:47:18 -0400 Subject: [PATCH 13/13] Fix test --- models/project/board.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/project/board.go b/models/project/board.go index 4eedda7d2b4ed..5f142a356c68d 100644 --- a/models/project/board.go +++ b/models/project/board.go @@ -209,7 +209,6 @@ func deleteBoardByProjectID(ctx context.Context, projectID int64) error { // GetBoard fetches the current board of a project func GetBoard(ctx context.Context, boardID int64) (*Board, error) { board := new(Board) - has, err := db.GetEngine(ctx).ID(boardID).Get(board) if err != nil { return nil, err @@ -279,7 +278,7 @@ func (p *Project) getDefaultBoard(ctx context.Context) (*Board, error) { Title: "Uncategorized", CreatorID: p.CreatorID, } - if _, err := db.GetEngine(ctx).Insert(); err != nil { + if _, err := db.GetEngine(ctx).Insert(&board); err != nil { return nil, err } return &board, nil