aboutsummaryrefslogtreecommitdiffstats
path: root/models
diff options
context:
space:
mode:
authorLunny Xiao <xiaolunwen@gmail.com>2022-03-29 22:16:31 +0800
committerGitHub <noreply@github.com>2022-03-29 22:16:31 +0800
commitbd97736b9c7a16023bc9abf17be6157284f655b1 (patch)
treeeea5870d406bd00fa2e675901f24d9f1fbf1796f /models
parentea6efba9b3fd298c842a935266060e405e2ae4a3 (diff)
downloadgitea-bd97736b9c7a16023bc9abf17be6157284f655b1.tar.gz
gitea-bd97736b9c7a16023bc9abf17be6157284f655b1.zip
Move project files into models/project sub package (#17704)
* Move project files into models/project sub package * Fix test * Fix test * Fix test * Fix build * Fix test * Fix template bug * Fix bug * Fix lint * Fix test * Fix import * Improve codes Co-authored-by: 6543 <6543@obermui.de>
Diffstat (limited to 'models')
-rw-r--r--models/error.go38
-rw-r--r--models/issue.go21
-rw-r--r--models/issue_comment.go9
-rw-r--r--models/issue_project.go181
-rw-r--r--models/project/board.go289
-rw-r--r--models/project/issue.go100
-rw-r--r--models/project/main_test.go23
-rw-r--r--models/project/project.go (renamed from models/project.go)112
-rw-r--r--models/project/project_test.go (renamed from models/project_test.go)22
-rw-r--r--models/project_board.go321
-rw-r--r--models/project_issue.go218
-rw-r--r--models/repo.go5
-rw-r--r--models/statistic.go5
13 files changed, 700 insertions, 644 deletions
diff --git a/models/error.go b/models/error.go
index 50d7175725..8ea2f2f8af 100644
--- a/models/error.go
+++ b/models/error.go
@@ -1063,44 +1063,6 @@ func (err ErrLabelNotExist) Error() string {
return fmt.Sprintf("label does not exist [label_id: %d]", err.LabelID)
}
-// __________ __ __
-// \______ \_______ ____ |__| ____ _____/ |_ ______
-// | ___/\_ __ \/ _ \ | |/ __ \_/ ___\ __\/ ___/
-// | | | | \( <_> ) | \ ___/\ \___| | \___ \
-// |____| |__| \____/\__| |\___ >\___ >__| /____ >
-// \______| \/ \/ \/
-
-// ErrProjectNotExist represents a "ProjectNotExist" kind of error.
-type ErrProjectNotExist struct {
- ID int64
- RepoID int64
-}
-
-// IsErrProjectNotExist checks if an error is a ErrProjectNotExist
-func IsErrProjectNotExist(err error) bool {
- _, ok := err.(ErrProjectNotExist)
- return ok
-}
-
-func (err ErrProjectNotExist) Error() string {
- return fmt.Sprintf("projects does not exist [id: %d]", err.ID)
-}
-
-// ErrProjectBoardNotExist represents a "ProjectBoardNotExist" kind of error.
-type ErrProjectBoardNotExist struct {
- BoardID int64
-}
-
-// IsErrProjectBoardNotExist checks if an error is a ErrProjectBoardNotExist
-func IsErrProjectBoardNotExist(err error) bool {
- _, ok := err.(ErrProjectBoardNotExist)
- return ok
-}
-
-func (err ErrProjectBoardNotExist) Error() string {
- return fmt.Sprintf("project board does not exist [id: %d]", err.BoardID)
-}
-
// _____ .__.__ __
// / \ |__| | ____ _______/ |_ ____ ____ ____
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
diff --git a/models/issue.go b/models/issue.go
index 31f7a0edb0..d2a2b6a329 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -19,6 +19,7 @@ import (
"code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
+ project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
@@ -45,14 +46,14 @@ type Issue struct {
PosterID int64 `xorm:"INDEX"`
Poster *user_model.User `xorm:"-"`
OriginalAuthor string
- OriginalAuthorID int64 `xorm:"index"`
- Title string `xorm:"name"`
- Content string `xorm:"LONGTEXT"`
- RenderedContent string `xorm:"-"`
- Labels []*Label `xorm:"-"`
- MilestoneID int64 `xorm:"INDEX"`
- Milestone *Milestone `xorm:"-"`
- Project *Project `xorm:"-"`
+ OriginalAuthorID int64 `xorm:"index"`
+ Title string `xorm:"name"`
+ Content string `xorm:"LONGTEXT"`
+ RenderedContent string `xorm:"-"`
+ Labels []*Label `xorm:"-"`
+ MilestoneID int64 `xorm:"INDEX"`
+ Milestone *Milestone `xorm:"-"`
+ Project *project_model.Project `xorm:"-"`
Priority int
AssigneeID int64 `xorm:"-"`
Assignee *user_model.User `xorm:"-"`
@@ -2135,7 +2136,7 @@ func deleteIssue(ctx context.Context, issue *Issue) error {
&IssueWatch{},
&Stopwatch{},
&TrackedTime{},
- &ProjectIssue{},
+ &project_model.ProjectIssue{},
&repo_model.Attachment{},
&PullRequest{},
); err != nil {
@@ -2469,7 +2470,7 @@ func deleteIssuesByRepoID(sess db.Engine, repoID int64) (attachmentPaths []strin
}
if _, err = sess.In("issue_id", deleteCond).
- Delete(&ProjectIssue{}); err != nil {
+ Delete(&project_model.ProjectIssue{}); err != nil {
return
}
diff --git a/models/issue_comment.go b/models/issue_comment.go
index 7927b0037b..500ed6d038 100644
--- a/models/issue_comment.go
+++ b/models/issue_comment.go
@@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
+ project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
@@ -204,8 +205,8 @@ type Comment struct {
RemovedLabels []*Label `xorm:"-"`
OldProjectID int64
ProjectID int64
- OldProject *Project `xorm:"-"`
- Project *Project `xorm:"-"`
+ OldProject *project_model.Project `xorm:"-"`
+ Project *project_model.Project `xorm:"-"`
OldMilestoneID int64
MilestoneID int64
OldMilestone *Milestone `xorm:"-"`
@@ -469,7 +470,7 @@ func (c *Comment) LoadLabel() error {
// LoadProject if comment.Type is CommentTypeProject, then load project.
func (c *Comment) LoadProject() error {
if c.OldProjectID > 0 {
- var oldProject Project
+ var oldProject project_model.Project
has, err := db.GetEngine(db.DefaultContext).ID(c.OldProjectID).Get(&oldProject)
if err != nil {
return err
@@ -479,7 +480,7 @@ func (c *Comment) LoadProject() error {
}
if c.ProjectID > 0 {
- var project Project
+ var project project_model.Project
has, err := db.GetEngine(db.DefaultContext).ID(c.ProjectID).Get(&project)
if err != nil {
return err
diff --git a/models/issue_project.go b/models/issue_project.go
new file mode 100644
index 0000000000..526ac95152
--- /dev/null
+++ b/models/issue_project.go
@@ -0,0 +1,181 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package models
+
+import (
+ "context"
+ "fmt"
+
+ "code.gitea.io/gitea/models/db"
+ project_model "code.gitea.io/gitea/models/project"
+ user_model "code.gitea.io/gitea/models/user"
+)
+
+// LoadProject load the project the issue was assigned to
+func (i *Issue) LoadProject() (err error) {
+ return i.loadProject(db.GetEngine(db.DefaultContext))
+}
+
+func (i *Issue) loadProject(e db.Engine) (err error) {
+ if i.Project == nil {
+ var p project_model.Project
+ if _, err = e.Table("project").
+ Join("INNER", "project_issue", "project.id=project_issue.project_id").
+ Where("project_issue.issue_id = ?", i.ID).
+ Get(&p); err != nil {
+ return err
+ }
+ i.Project = &p
+ }
+ return
+}
+
+// ProjectID return project id if issue was assigned to one
+func (i *Issue) ProjectID() int64 {
+ return i.projectID(db.GetEngine(db.DefaultContext))
+}
+
+func (i *Issue) projectID(e db.Engine) int64 {
+ var ip project_model.ProjectIssue
+ has, err := e.Where("issue_id=?", i.ID).Get(&ip)
+ if err != nil || !has {
+ return 0
+ }
+ return ip.ProjectID
+}
+
+// ProjectBoardID return project board id if issue was assigned to one
+func (i *Issue) ProjectBoardID() int64 {
+ return i.projectBoardID(db.GetEngine(db.DefaultContext))
+}
+
+func (i *Issue) projectBoardID(e db.Engine) int64 {
+ var ip project_model.ProjectIssue
+ has, err := e.Where("issue_id=?", i.ID).Get(&ip)
+ if err != nil || !has {
+ return 0
+ }
+ return ip.ProjectBoardID
+}
+
+// LoadIssuesFromBoard load issues assigned to this board
+func LoadIssuesFromBoard(b *project_model.Board) (IssueList, error) {
+ issueList := make([]*Issue, 0, 10)
+
+ if b.ID != 0 {
+ issues, err := Issues(&IssuesOptions{
+ ProjectBoardID: b.ID,
+ ProjectID: b.ProjectID,
+ })
+ if err != nil {
+ return nil, err
+ }
+ issueList = issues
+ }
+
+ if b.Default {
+ issues, err := Issues(&IssuesOptions{
+ ProjectBoardID: -1, // Issues without ProjectBoardID
+ ProjectID: b.ProjectID,
+ })
+ if err != nil {
+ return nil, err
+ }
+ issueList = append(issueList, issues...)
+ }
+
+ if err := IssueList(issueList).LoadComments(); err != nil {
+ return nil, err
+ }
+
+ return issueList, nil
+}
+
+// LoadIssuesFromBoardList load issues assigned to the boards
+func LoadIssuesFromBoardList(bs project_model.BoardList) (map[int64]IssueList, error) {
+ issuesMap := make(map[int64]IssueList, len(bs))
+ for i := range bs {
+ il, err := LoadIssuesFromBoard(bs[i])
+ if err != nil {
+ return nil, err
+ }
+ issuesMap[bs[i].ID] = il
+ }
+ return issuesMap, nil
+}
+
+// ChangeProjectAssign changes the project associated with an issue
+func ChangeProjectAssign(issue *Issue, doer *user_model.User, newProjectID int64) error {
+ ctx, committer, err := db.TxContext()
+ if err != nil {
+ return err
+ }
+ defer committer.Close()
+
+ if err := addUpdateIssueProject(ctx, issue, doer, newProjectID); err != nil {
+ return err
+ }
+
+ return committer.Commit()
+}
+
+func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
+ e := db.GetEngine(ctx)
+ oldProjectID := issue.projectID(e)
+
+ if _, err := e.Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
+ return err
+ }
+
+ if err := issue.loadRepo(ctx); err != nil {
+ return err
+ }
+
+ if oldProjectID > 0 || newProjectID > 0 {
+ if _, err := createComment(ctx, &CreateCommentOptions{
+ Type: CommentTypeProject,
+ Doer: doer,
+ Repo: issue.Repo,
+ Issue: issue,
+ OldProjectID: oldProjectID,
+ ProjectID: newProjectID,
+ }); err != nil {
+ return err
+ }
+ }
+
+ _, err := e.Insert(&project_model.ProjectIssue{
+ IssueID: issue.ID,
+ ProjectID: newProjectID,
+ })
+ return err
+}
+
+// MoveIssueAcrossProjectBoards move a card from one board to another
+func MoveIssueAcrossProjectBoards(issue *Issue, board *project_model.Board) error {
+ ctx, committer, err := db.TxContext()
+ if err != nil {
+ return err
+ }
+ defer committer.Close()
+ sess := db.GetEngine(ctx)
+
+ var pis project_model.ProjectIssue
+ has, err := sess.Where("issue_id=?", issue.ID).Get(&pis)
+ if err != nil {
+ return err
+ }
+
+ if !has {
+ return fmt.Errorf("issue has to be added to a project first")
+ }
+
+ pis.ProjectBoardID = board.ID
+ if _, err := sess.ID(pis.ID).Cols("project_board_id").Update(&pis); err != nil {
+ return err
+ }
+
+ return committer.Commit()
+}
diff --git a/models/project/board.go b/models/project/board.go
new file mode 100644
index 0000000000..f770a18f59
--- /dev/null
+++ b/models/project/board.go
@@ -0,0 +1,289 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package project
+
+import (
+ "context"
+ "fmt"
+ "regexp"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/builder"
+)
+
+type (
+ // BoardType is used to represent a project board type
+ BoardType uint8
+
+ // BoardList is a list of all project boards in a repository
+ BoardList []*Board
+)
+
+const (
+ // BoardTypeNone is a project board type that has no predefined columns
+ BoardTypeNone BoardType = iota
+
+ // BoardTypeBasicKanban is a project board type that has basic predefined columns
+ BoardTypeBasicKanban
+
+ // BoardTypeBugTriage is a project board type that has predefined columns suited to hunting down bugs
+ BoardTypeBugTriage
+)
+
+// BoardColorPattern is a regexp witch can validate BoardColor
+var BoardColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$")
+
+// Board is used to represent boards on a project
+type Board 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"`
+}
+
+// TableName return the real table name
+func (Board) TableName() string {
+ return "project_board"
+}
+
+// NumIssues return counter of all issues assigned to the board
+func (b *Board) NumIssues() int {
+ c, err := db.GetEngine(db.DefaultContext).Table("project_issue").
+ Where("project_id=?", b.ProjectID).
+ And("project_board_id=?", b.ID).
+ GroupBy("issue_id").
+ Cols("issue_id").
+ Count()
+ if err != nil {
+ return 0
+ }
+ return int(c)
+}
+
+func init() {
+ db.RegisterModel(new(Board))
+}
+
+// IsBoardTypeValid checks if the project board type is valid
+func IsBoardTypeValid(p BoardType) bool {
+ switch p {
+ case BoardTypeNone, BoardTypeBasicKanban, BoardTypeBugTriage:
+ return true
+ default:
+ return false
+ }
+}
+
+func createBoardsForProjectsType(ctx context.Context, project *Project) error {
+ var items []string
+
+ switch project.BoardType {
+
+ case BoardTypeBugTriage:
+ items = setting.Project.ProjectBoardBugTriageType
+
+ case BoardTypeBasicKanban:
+ items = setting.Project.ProjectBoardBasicKanbanType
+
+ case BoardTypeNone:
+ fallthrough
+ default:
+ return nil
+ }
+
+ if len(items) == 0 {
+ return nil
+ }
+
+ boards := make([]Board, 0, len(items))
+
+ for _, v := range items {
+ boards = append(boards, Board{
+ CreatedUnix: timeutil.TimeStampNow(),
+ CreatorID: project.CreatorID,
+ Title: v,
+ ProjectID: project.ID,
+ })
+ }
+
+ return db.Insert(ctx, boards)
+}
+
+// NewBoard adds a new project board to a given project
+func NewBoard(board *Board) error {
+ if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) {
+ return fmt.Errorf("bad color code: %s", board.Color)
+ }
+
+ _, err := db.GetEngine(db.DefaultContext).Insert(board)
+ return err
+}
+
+// DeleteBoardByID removes all issues references to the project board.
+func DeleteBoardByID(boardID int64) error {
+ ctx, committer, err := db.TxContext()
+ if err != nil {
+ return err
+ }
+ defer committer.Close()
+
+ if err := deleteBoardByID(ctx, boardID); err != nil {
+ return err
+ }
+
+ return committer.Commit()
+}
+
+func deleteBoardByID(ctx context.Context, boardID int64) error {
+ e := db.GetEngine(ctx)
+ board, err := getBoard(e, boardID)
+ if err != nil {
+ if IsErrProjectBoardNotExist(err) {
+ return nil
+ }
+
+ return err
+ }
+
+ if err = board.removeIssues(e); err != nil {
+ return err
+ }
+
+ if _, err := e.ID(board.ID).Delete(board); err != nil {
+ return err
+ }
+ return nil
+}
+
+func deleteBoardByProjectID(e db.Engine, projectID int64) error {
+ _, err := e.Where("project_id=?", projectID).Delete(&Board{})
+ return err
+}
+
+// GetBoard fetches the current board of a project
+func GetBoard(boardID int64) (*Board, error) {
+ return getBoard(db.GetEngine(db.DefaultContext), boardID)
+}
+
+func getBoard(e db.Engine, boardID int64) (*Board, error) {
+ board := new(Board)
+
+ has, err := e.ID(boardID).Get(board)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, ErrProjectBoardNotExist{BoardID: boardID}
+ }
+
+ return board, nil
+}
+
+// UpdateBoard updates a project board
+func UpdateBoard(board *Board) error {
+ return updateBoard(db.GetEngine(db.DefaultContext), board)
+}
+
+func updateBoard(e db.Engine, board *Board) error {
+ var fieldToUpdate []string
+
+ if board.Sorting != 0 {
+ fieldToUpdate = append(fieldToUpdate, "sorting")
+ }
+
+ if board.Title != "" {
+ fieldToUpdate = append(fieldToUpdate, "title")
+ }
+
+ if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) {
+ return fmt.Errorf("bad color code: %s", board.Color)
+ }
+ fieldToUpdate = append(fieldToUpdate, "color")
+
+ _, err := e.ID(board.ID).Cols(fieldToUpdate...).Update(board)
+
+ return err
+}
+
+// GetBoards fetches all boards related to a project
+// if no default board set, first board is a temporary "Uncategorized" board
+func GetBoards(projectID int64) (BoardList, error) {
+ return getBoards(db.GetEngine(db.DefaultContext), projectID)
+}
+
+func getBoards(e db.Engine, projectID int64) ([]*Board, error) {
+ boards := make([]*Board, 0, 5)
+
+ if err := e.Where("project_id=? AND `default`=?", projectID, false).OrderBy("Sorting").Find(&boards); err != nil {
+ return nil, err
+ }
+
+ defaultB, err := getDefaultBoard(e, projectID)
+ if err != nil {
+ return nil, err
+ }
+
+ return append([]*Board{defaultB}, boards...), nil
+}
+
+// getDefaultBoard return default board and create a dummy if none exist
+func getDefaultBoard(e db.Engine, projectID int64) (*Board, error) {
+ var board Board
+ exist, err := e.Where("project_id=? AND `default`=?", projectID, true).Get(&board)
+ if err != nil {
+ return nil, err
+ }
+ if exist {
+ return &board, nil
+ }
+
+ // represents a board for issues not assigned to one
+ return &Board{
+ ProjectID: projectID,
+ Title: "Uncategorized",
+ Default: true,
+ }, nil
+}
+
+// SetDefaultBoard represents a board for issues not assigned to one
+// if boardID is 0 unset default
+func SetDefaultBoard(projectID, boardID int64) error {
+ _, err := db.GetEngine(db.DefaultContext).Where(builder.Eq{
+ "project_id": projectID,
+ "`default`": true,
+ }).Cols("`default`").Update(&Board{Default: false})
+ if err != nil {
+ return err
+ }
+
+ if boardID > 0 {
+ _, err = db.GetEngine(db.DefaultContext).ID(boardID).Where(builder.Eq{"project_id": projectID}).
+ Cols("`default`").Update(&Board{Default: true})
+ }
+
+ return err
+}
+
+// UpdateBoardSorting update project board sorting
+func UpdateBoardSorting(bs BoardList) error {
+ for i := range bs {
+ _, err := db.GetEngine(db.DefaultContext).ID(bs[i].ID).Cols(
+ "sorting",
+ ).Update(bs[i])
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/models/project/issue.go b/models/project/issue.go
new file mode 100644
index 0000000000..0976185c49
--- /dev/null
+++ b/models/project/issue.go
@@ -0,0 +1,100 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package project
+
+import (
+ "context"
+ "fmt"
+
+ "code.gitea.io/gitea/models/db"
+)
+
+// ProjectIssue saves relation from issue to a project
+type ProjectIssue struct { //revive:disable-line:exported
+ ID int64 `xorm:"pk autoincr"`
+ IssueID int64 `xorm:"INDEX"`
+ ProjectID int64 `xorm:"INDEX"`
+
+ // If 0, then it has not been added to a specific board in the project
+ ProjectBoardID int64 `xorm:"INDEX"`
+}
+
+func init() {
+ db.RegisterModel(new(ProjectIssue))
+}
+
+func deleteProjectIssuesByProjectID(e db.Engine, projectID int64) error {
+ _, err := e.Where("project_id=?", projectID).Delete(&ProjectIssue{})
+ return err
+}
+
+// NumIssues return counter of all issues assigned to a project
+func (p *Project) NumIssues() int {
+ c, err := db.GetEngine(db.DefaultContext).Table("project_issue").
+ Where("project_id=?", p.ID).
+ GroupBy("issue_id").
+ Cols("issue_id").
+ Count()
+ if err != nil {
+ return 0
+ }
+ return int(c)
+}
+
+// NumClosedIssues return counter of closed issues assigned to a project
+func (p *Project) NumClosedIssues() int {
+ c, err := db.GetEngine(db.DefaultContext).Table("project_issue").
+ Join("INNER", "issue", "project_issue.issue_id=issue.id").
+ Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true).
+ Cols("issue_id").
+ Count()
+ if err != nil {
+ return 0
+ }
+ return int(c)
+}
+
+// NumOpenIssues return counter of open issues assigned to a project
+func (p *Project) NumOpenIssues() int {
+ c, err := db.GetEngine(db.DefaultContext).Table("project_issue").
+ Join("INNER", "issue", "project_issue.issue_id=issue.id").
+ Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false).Count("issue.id")
+ if err != nil {
+ return 0
+ }
+ return int(c)
+}
+
+// MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column
+func MoveIssuesOnProjectBoard(board *Board, sortedIssueIDs map[int64]int64) error {
+ return db.WithTx(func(ctx context.Context) error {
+ sess := db.GetEngine(ctx)
+
+ issueIDs := make([]int64, 0, len(sortedIssueIDs))
+ for _, issueID := range sortedIssueIDs {
+ issueIDs = append(issueIDs, issueID)
+ }
+ count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", board.ProjectID).In("issue_id", issueIDs).Count()
+ if err != nil {
+ return err
+ }
+ if int(count) != len(sortedIssueIDs) {
+ return fmt.Errorf("all issues have to be added to a project first")
+ }
+
+ for sorting, issueID := range sortedIssueIDs {
+ _, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", board.ID, sorting, issueID)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+}
+
+func (pb *Board) removeIssues(e db.Engine) error {
+ _, err := e.Exec("UPDATE `project_issue` SET project_board_id = 0 WHERE project_board_id = ? ", pb.ID)
+ return err
+}
diff --git a/models/project/main_test.go b/models/project/main_test.go
new file mode 100644
index 0000000000..5296a0f40e
--- /dev/null
+++ b/models/project/main_test.go
@@ -0,0 +1,23 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package project
+
+import (
+ "path/filepath"
+ "testing"
+
+ "code.gitea.io/gitea/models/unittest"
+
+ _ "code.gitea.io/gitea/models/repo"
+)
+
+func TestMain(m *testing.M) {
+ unittest.MainTest(m, filepath.Join("..", ".."),
+ "project.yml",
+ "project_board.yml",
+ "project_issue.yml",
+ "repository.yml",
+ )
+}
diff --git a/models/project.go b/models/project/project.go
index e6a650674b..a639879e78 100644
--- a/models/project.go
+++ b/models/project/project.go
@@ -2,9 +2,10 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
-package models
+package project
import (
+ "context"
"errors"
"fmt"
@@ -19,25 +20,56 @@ import (
type (
// ProjectsConfig is used to identify the type of board that is being created
ProjectsConfig struct {
- BoardType ProjectBoardType
+ BoardType BoardType
Translation string
}
- // ProjectType is used to identify the type of project in question and ownership
- ProjectType uint8
+ // Type is used to identify the type of project in question and ownership
+ Type uint8
)
const (
- // ProjectTypeIndividual is a type of project board that is owned by an individual
- ProjectTypeIndividual ProjectType = iota + 1
+ // TypeIndividual is a type of project board that is owned by an individual
+ TypeIndividual Type = iota + 1
- // ProjectTypeRepository is a project that is tied to a repository
- ProjectTypeRepository
+ // TypeRepository is a project that is tied to a repository
+ TypeRepository
- // ProjectTypeOrganization is a project that is tied to an organisation
- ProjectTypeOrganization
+ // TypeOrganization is a project that is tied to an organisation
+ TypeOrganization
)
+// ErrProjectNotExist represents a "ProjectNotExist" kind of error.
+type ErrProjectNotExist struct {
+ ID int64
+ RepoID int64
+}
+
+// IsErrProjectNotExist checks if an error is a ErrProjectNotExist
+func IsErrProjectNotExist(err error) bool {
+ _, ok := err.(ErrProjectNotExist)
+ return ok
+}
+
+func (err ErrProjectNotExist) Error() string {
+ return fmt.Sprintf("projects does not exist [id: %d]", err.ID)
+}
+
+// ErrProjectBoardNotExist represents a "ProjectBoardNotExist" kind of error.
+type ErrProjectBoardNotExist struct {
+ BoardID int64
+}
+
+// IsErrProjectBoardNotExist checks if an error is a ErrProjectBoardNotExist
+func IsErrProjectBoardNotExist(err error) bool {
+ _, ok := err.(ErrProjectBoardNotExist)
+ return ok
+}
+
+func (err ErrProjectBoardNotExist) Error() string {
+ return fmt.Sprintf("project board does not exist [id: %d]", err.BoardID)
+}
+
// Project represents a project board
type Project struct {
ID int64 `xorm:"pk autoincr"`
@@ -46,8 +78,8 @@ type Project struct {
RepoID int64 `xorm:"INDEX"`
CreatorID int64 `xorm:"NOT NULL"`
IsClosed bool `xorm:"INDEX"`
- BoardType ProjectBoardType
- Type ProjectType
+ BoardType BoardType
+ Type Type
RenderedContent string `xorm:"-"`
@@ -63,37 +95,39 @@ func init() {
// GetProjectsConfig retrieves the types of configurations projects could have
func GetProjectsConfig() []ProjectsConfig {
return []ProjectsConfig{
- {ProjectBoardTypeNone, "repo.projects.type.none"},
- {ProjectBoardTypeBasicKanban, "repo.projects.type.basic_kanban"},
- {ProjectBoardTypeBugTriage, "repo.projects.type.bug_triage"},
+ {BoardTypeNone, "repo.projects.type.none"},
+ {BoardTypeBasicKanban, "repo.projects.type.basic_kanban"},
+ {BoardTypeBugTriage, "repo.projects.type.bug_triage"},
}
}
-// IsProjectTypeValid checks if a project type is valid
-func IsProjectTypeValid(p ProjectType) bool {
+// IsTypeValid checks if a project type is valid
+func IsTypeValid(p Type) bool {
switch p {
- case ProjectTypeRepository:
+ case TypeRepository:
return true
default:
return false
}
}
-// ProjectSearchOptions are options for GetProjects
-type ProjectSearchOptions struct {
+// SearchOptions are options for GetProjects
+type SearchOptions struct {
RepoID int64
Page int
IsClosed util.OptionalBool
SortType string
- Type ProjectType
+ Type Type
}
// GetProjects returns a list of all projects that have been created in the repository
-func GetProjects(opts ProjectSearchOptions) ([]*Project, int64, error) {
- return getProjects(db.GetEngine(db.DefaultContext), opts)
+func GetProjects(opts SearchOptions) ([]*Project, int64, error) {
+ return GetProjectsCtx(db.DefaultContext, opts)
}
-func getProjects(e db.Engine, opts ProjectSearchOptions) ([]*Project, int64, error) {
+// GetProjectsCtx returns a list of all projects that have been created in the repository
+func GetProjectsCtx(ctx context.Context, opts SearchOptions) ([]*Project, int64, error) {
+ e := db.GetEngine(ctx)
projects := make([]*Project, 0, setting.UI.IssuePagingNum)
var cond builder.Cond = builder.Eq{"repo_id": opts.RepoID}
@@ -135,11 +169,11 @@ func getProjects(e db.Engine, opts ProjectSearchOptions) ([]*Project, int64, err
// NewProject creates a new Project
func NewProject(p *Project) error {
- if !IsProjectBoardTypeValid(p.BoardType) {
- p.BoardType = ProjectBoardTypeNone
+ if !IsBoardTypeValid(p.BoardType) {
+ p.BoardType = BoardTypeNone
}
- if !IsProjectTypeValid(p.Type) {
+ if !IsTypeValid(p.Type) {
return errors.New("project type is not valid")
}
@@ -157,7 +191,7 @@ func NewProject(p *Project) error {
return err
}
- if err := createBoardsForProjectsType(db.GetEngine(ctx), p); err != nil {
+ if err := createBoardsForProjectsType(ctx, p); err != nil {
return err
}
@@ -200,7 +234,7 @@ func updateRepositoryProjectCount(e db.Engine, repoID int64) error {
builder.Eq{
"`num_projects`": builder.Select("count(*)").From("`project`").
Where(builder.Eq{"`project`.`repo_id`": repoID}.
- And(builder.Eq{"`project`.`type`": ProjectTypeRepository})),
+ And(builder.Eq{"`project`.`type`": TypeRepository})),
}).From("`repository`").Where(builder.Eq{"id": repoID})); err != nil {
return err
}
@@ -209,7 +243,7 @@ func updateRepositoryProjectCount(e db.Engine, repoID int64) error {
builder.Eq{
"`num_closed_projects`": builder.Select("count(*)").From("`project`").
Where(builder.Eq{"`project`.`repo_id`": repoID}.
- And(builder.Eq{"`project`.`type`": ProjectTypeRepository}).
+ And(builder.Eq{"`project`.`type`": TypeRepository}).
And(builder.Eq{"`project`.`is_closed`": true})),
}).From("`repository`").Where(builder.Eq{"id": repoID})); err != nil {
return err
@@ -224,18 +258,17 @@ func ChangeProjectStatusByRepoIDAndID(repoID, projectID int64, isClosed bool) er
return err
}
defer committer.Close()
- sess := db.GetEngine(ctx)
p := new(Project)
- has, err := sess.ID(projectID).Where("repo_id = ?", repoID).Get(p)
+ has, err := db.GetEngine(ctx).ID(projectID).Where("repo_id = ?", repoID).Get(p)
if err != nil {
return err
} else if !has {
return ErrProjectNotExist{ID: projectID, RepoID: repoID}
}
- if err := changeProjectStatus(sess, p, isClosed); err != nil {
+ if err := changeProjectStatus(ctx, p, isClosed); err != nil {
return err
}
@@ -250,16 +283,17 @@ func ChangeProjectStatus(p *Project, isClosed bool) error {
}
defer committer.Close()
- if err := changeProjectStatus(db.GetEngine(ctx), p, isClosed); err != nil {
+ if err := changeProjectStatus(ctx, p, isClosed); err != nil {
return err
}
return committer.Commit()
}
-func changeProjectStatus(e db.Engine, p *Project, isClosed bool) error {
+func changeProjectStatus(ctx context.Context, p *Project, isClosed bool) error {
p.IsClosed = isClosed
p.ClosedDateUnix = timeutil.TimeStampNow()
+ e := db.GetEngine(ctx)
count, err := e.ID(p.ID).Where("repo_id = ? AND is_closed = ?", p.RepoID, !isClosed).Cols("is_closed", "closed_date_unix").Update(p)
if err != nil {
return err
@@ -279,14 +313,16 @@ func DeleteProjectByID(id int64) error {
}
defer committer.Close()
- if err := deleteProjectByID(db.GetEngine(ctx), id); err != nil {
+ if err := DeleteProjectByIDCtx(ctx, id); err != nil {
return err
}
return committer.Commit()
}
-func deleteProjectByID(e db.Engine, id int64) error {
+// DeleteProjectByIDCtx deletes a project from a repository.
+func DeleteProjectByIDCtx(ctx context.Context, id int64) error {
+ e := db.GetEngine(ctx)
p, err := getProjectByID(e, id)
if err != nil {
if IsErrProjectNotExist(err) {
@@ -299,7 +335,7 @@ func deleteProjectByID(e db.Engine, id int64) error {
return err
}
- if err := deleteProjectBoardByProjectID(e, id); err != nil {
+ if err := deleteBoardByProjectID(e, id); err != nil {
return err
}
diff --git a/models/project_test.go b/models/project/project_test.go
index 70dabb7674..211a890874 100644
--- a/models/project_test.go
+++ b/models/project/project_test.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
-package models
+package project
import (
"testing"
@@ -14,33 +14,33 @@ import (
)
func TestIsProjectTypeValid(t *testing.T) {
- const UnknownType ProjectType = 15
+ const UnknownType Type = 15
cases := []struct {
- typ ProjectType
+ typ Type
valid bool
}{
- {ProjectTypeIndividual, false},
- {ProjectTypeRepository, true},
- {ProjectTypeOrganization, false},
+ {TypeIndividual, false},
+ {TypeRepository, true},
+ {TypeOrganization, false},
{UnknownType, false},
}
for _, v := range cases {
- assert.Equal(t, v.valid, IsProjectTypeValid(v.typ))
+ assert.Equal(t, v.valid, IsTypeValid(v.typ))
}
}
func TestGetProjects(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- projects, _, err := GetProjects(ProjectSearchOptions{RepoID: 1})
+ projects, _, err := GetProjects(SearchOptions{RepoID: 1})
assert.NoError(t, err)
// 1 value for this repo exists in the fixtures
assert.Len(t, projects, 1)
- projects, _, err = GetProjects(ProjectSearchOptions{RepoID: 3})
+ projects, _, err = GetProjects(SearchOptions{RepoID: 3})
assert.NoError(t, err)
// 1 value for this repo exists in the fixtures
@@ -51,8 +51,8 @@ func TestProject(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
project := &Project{
- Type: ProjectTypeRepository,
- BoardType: ProjectBoardTypeBasicKanban,
+ Type: TypeRepository,
+ BoardType: BoardTypeBasicKanban,
Title: "New Project",
RepoID: 1,
CreatedUnix: timeutil.TimeStampNow(),
diff --git a/models/project_board.go b/models/project_board.go
deleted file mode 100644
index d40cfd06f0..0000000000
--- a/models/project_board.go
+++ /dev/null
@@ -1,321 +0,0 @@
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package models
-
-import (
- "fmt"
- "regexp"
-
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/timeutil"
-
- "xorm.io/builder"
-)
-
-type (
- // ProjectBoardType is used to represent a project board type
- ProjectBoardType uint8
-
- // ProjectBoardList is a list of all project boards in a repository
- ProjectBoardList []*ProjectBoard
-)
-
-const (
- // ProjectBoardTypeNone is a project board type that has no predefined columns
- ProjectBoardTypeNone ProjectBoardType = iota
-
- // ProjectBoardTypeBasicKanban is a project board type that has basic predefined columns
- ProjectBoardTypeBasicKanban
-
- // ProjectBoardTypeBugTriage is a project board type that has predefined columns suited to hunting down bugs
- ProjectBoardTypeBugTriage
-)
-
-// BoardColorPattern is a regexp witch can validate BoardColor
-var BoardColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$")
-
-// ProjectBoard is used to represent boards on a project
-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"`
-
- Issues []*Issue `xorm:"-"`
-}
-
-func init() {
- db.RegisterModel(new(ProjectBoard))
-}
-
-// IsProjectBoardTypeValid checks if the project board type is valid
-func IsProjectBoardTypeValid(p ProjectBoardType) bool {
- switch p {
- case ProjectBoardTypeNone, ProjectBoardTypeBasicKanban, ProjectBoardTypeBugTriage:
- return true
- default:
- return false
- }
-}
-
-func createBoardsForProjectsType(sess db.Engine, project *Project) error {
- var items []string
-
- switch project.BoardType {
-
- case ProjectBoardTypeBugTriage:
- items = setting.Project.ProjectBoardBugTriageType
-
- case ProjectBoardTypeBasicKanban:
- items = setting.Project.ProjectBoardBasicKanbanType
-
- case ProjectBoardTypeNone:
- fallthrough
- default:
- return nil
- }
-
- if len(items) == 0 {
- return nil
- }
-
- boards := make([]ProjectBoard, 0, len(items))
-
- for _, v := range items {
- boards = append(boards, ProjectBoard{
- CreatedUnix: timeutil.TimeStampNow(),
- CreatorID: project.CreatorID,
- Title: v,
- ProjectID: project.ID,
- })
- }
-
- _, err := sess.Insert(boards)
- return err
-}
-
-// NewProjectBoard adds a new project board to a given project
-func NewProjectBoard(board *ProjectBoard) error {
- if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) {
- return fmt.Errorf("bad color code: %s", board.Color)
- }
-
- _, err := db.GetEngine(db.DefaultContext).Insert(board)
- return err
-}
-
-// DeleteProjectBoardByID removes all issues references to the project board.
-func DeleteProjectBoardByID(boardID int64) error {
- ctx, committer, err := db.TxContext()
- if err != nil {
- return err
- }
- defer committer.Close()
-
- if err := deleteProjectBoardByID(db.GetEngine(ctx), boardID); err != nil {
- return err
- }
-
- return committer.Commit()
-}
-
-func deleteProjectBoardByID(e db.Engine, boardID int64) error {
- board, err := getProjectBoard(e, boardID)
- if err != nil {
- if IsErrProjectBoardNotExist(err) {
- return nil
- }
-
- return err
- }
-
- if err = board.removeIssues(e); err != nil {
- return err
- }
-
- if _, err := e.ID(board.ID).Delete(board); err != nil {
- return err
- }
- return nil
-}
-
-func deleteProjectBoardByProjectID(e db.Engine, projectID int64) error {
- _, err := e.Where("project_id=?", projectID).Delete(&ProjectBoard{})
- return err
-}
-
-// GetProjectBoard fetches the current board of a project
-func GetProjectBoard(boardID int64) (*ProjectBoard, error) {
- return getProjectBoard(db.GetEngine(db.DefaultContext), boardID)
-}
-
-func getProjectBoard(e db.Engine, boardID int64) (*ProjectBoard, error) {
- board := new(ProjectBoard)
-
- has, err := e.ID(boardID).Get(board)
- if err != nil {
- return nil, err
- } else if !has {
- return nil, ErrProjectBoardNotExist{BoardID: boardID}
- }
-
- return board, nil
-}
-
-// UpdateProjectBoard updates a project board
-func UpdateProjectBoard(board *ProjectBoard) error {
- return updateProjectBoard(db.GetEngine(db.DefaultContext), board)
-}
-
-func updateProjectBoard(e db.Engine, board *ProjectBoard) error {
- var fieldToUpdate []string
-
- if board.Sorting != 0 {
- fieldToUpdate = append(fieldToUpdate, "sorting")
- }
-
- if board.Title != "" {
- fieldToUpdate = append(fieldToUpdate, "title")
- }
-
- if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) {
- return fmt.Errorf("bad color code: %s", board.Color)
- }
- fieldToUpdate = append(fieldToUpdate, "color")
-
- _, err := e.ID(board.ID).Cols(fieldToUpdate...).Update(board)
-
- return err
-}
-
-// GetProjectBoards fetches all boards related to a project
-// if no default board set, first board is a temporary "Uncategorized" board
-func GetProjectBoards(projectID int64) (ProjectBoardList, error) {
- return getProjectBoards(db.GetEngine(db.DefaultContext), projectID)
-}
-
-func getProjectBoards(e db.Engine, projectID int64) ([]*ProjectBoard, error) {
- boards := make([]*ProjectBoard, 0, 5)
-
- if err := e.Where("project_id=? AND `default`=?", projectID, false).OrderBy("Sorting").Find(&boards); err != nil {
- return nil, err
- }
-
- defaultB, err := getDefaultBoard(e, projectID)
- if err != nil {
- return nil, err
- }
-
- return append([]*ProjectBoard{defaultB}, boards...), nil
-}
-
-// getDefaultBoard return default board and create a dummy if none exist
-func getDefaultBoard(e db.Engine, projectID int64) (*ProjectBoard, error) {
- var board ProjectBoard
- exist, err := e.Where("project_id=? AND `default`=?", projectID, true).Get(&board)
- if err != nil {
- return nil, err
- }
- if exist {
- return &board, nil
- }
-
- // represents a board for issues not assigned to one
- return &ProjectBoard{
- ProjectID: projectID,
- Title: "Uncategorized",
- Default: true,
- }, nil
-}
-
-// SetDefaultBoard represents a board for issues not assigned to one
-// if boardID is 0 unset default
-func SetDefaultBoard(projectID, boardID int64) error {
- _, err := db.GetEngine(db.DefaultContext).Where(builder.Eq{
- "project_id": projectID,
- "`default`": true,
- }).Cols("`default`").Update(&ProjectBoard{Default: false})
- if err != nil {
- return err
- }
-
- if boardID > 0 {
- _, err = db.GetEngine(db.DefaultContext).ID(boardID).Where(builder.Eq{"project_id": projectID}).
- Cols("`default`").Update(&ProjectBoard{Default: true})
- }
-
- return err
-}
-
-// LoadIssues load issues assigned to this board
-func (b *ProjectBoard) LoadIssues() (IssueList, error) {
- issueList := make([]*Issue, 0, 10)
-
- if b.ID != 0 {
- issues, err := Issues(&IssuesOptions{
- ProjectBoardID: b.ID,
- ProjectID: b.ProjectID,
- SortType: "project-column-sorting",
- })
- if err != nil {
- return nil, err
- }
- issueList = issues
- }
-
- if b.Default {
- issues, err := Issues(&IssuesOptions{
- ProjectBoardID: -1, // Issues without ProjectBoardID
- ProjectID: b.ProjectID,
- SortType: "project-column-sorting",
- })
- if err != nil {
- return nil, err
- }
- issueList = append(issueList, issues...)
- }
-
- if err := IssueList(issueList).LoadComments(); err != nil {
- return nil, err
- }
-
- b.Issues = issueList
- return issueList, nil
-}
-
-// LoadIssues load issues assigned to the boards
-func (bs ProjectBoardList) LoadIssues() (IssueList, error) {
- issues := make(IssueList, 0, len(bs)*10)
- for i := range bs {
- il, err := bs[i].LoadIssues()
- if err != nil {
- return nil, err
- }
- bs[i].Issues = il
- issues = append(issues, il...)
- }
- return issues, nil
-}
-
-// UpdateProjectBoardSorting update project board sorting
-func UpdateProjectBoardSorting(bs ProjectBoardList) error {
- for i := range bs {
- _, err := db.GetEngine(db.DefaultContext).ID(bs[i].ID).Cols(
- "sorting",
- ).Update(bs[i])
- if err != nil {
- return err
- }
- }
- return nil
-}
diff --git a/models/project_issue.go b/models/project_issue.go
deleted file mode 100644
index c7735addcc..0000000000
--- a/models/project_issue.go
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package models
-
-import (
- "context"
- "fmt"
-
- "code.gitea.io/gitea/models/db"
- user_model "code.gitea.io/gitea/models/user"
-)
-
-// ProjectIssue saves relation from issue to a project
-type ProjectIssue struct {
- ID int64 `xorm:"pk autoincr"`
- IssueID int64 `xorm:"INDEX"`
- ProjectID int64 `xorm:"INDEX"`
-
- // If 0, then it has not been added to a specific board in the project
- ProjectBoardID int64 `xorm:"INDEX"`
- Sorting int64 `xorm:"NOT NULL DEFAULT 0"`
-}
-
-func init() {
- db.RegisterModel(new(ProjectIssue))
-}
-
-func deleteProjectIssuesByProjectID(e db.Engine, projectID int64) error {
- _, err := e.Where("project_id=?", projectID).Delete(&ProjectIssue{})
- return err
-}
-
-// ___
-// |_ _|___ ___ _ _ ___
-// | |/ __/ __| | | |/ _ \
-// | |\__ \__ \ |_| | __/
-// |___|___/___/\__,_|\___|
-
-// LoadProject load the project the issue was assigned to
-func (i *Issue) LoadProject() (err error) {
- return i.loadProject(db.GetEngine(db.DefaultContext))
-}
-
-func (i *Issue) loadProject(e db.Engine) (err error) {
- if i.Project == nil {
- var p Project
- if _, err = e.Table("project").
- Join("INNER", "project_issue", "project.id=project_issue.project_id").
- Where("project_issue.issue_id = ?", i.ID).
- Get(&p); err != nil {
- return err
- }
- i.Project = &p
- }
- return
-}
-
-// ProjectID return project id if issue was assigned to one
-func (i *Issue) ProjectID() int64 {
- return i.projectID(db.GetEngine(db.DefaultContext))
-}
-
-func (i *Issue) projectID(e db.Engine) int64 {
- var ip ProjectIssue
- has, err := e.Where("issue_id=?", i.ID).Get(&ip)
- if err != nil || !has {
- return 0
- }
- return ip.ProjectID
-}
-
-// ProjectBoardID return project board id if issue was assigned to one
-func (i *Issue) ProjectBoardID() int64 {
- return i.projectBoardID(db.GetEngine(db.DefaultContext))
-}
-
-func (i *Issue) projectBoardID(e db.Engine) int64 {
- var ip ProjectIssue
- has, err := e.Where("issue_id=?", i.ID).Get(&ip)
- if err != nil || !has {
- return 0
- }
- return ip.ProjectBoardID
-}
-
-// ____ _ _
-// | _ \ _ __ ___ (_) ___ ___| |_
-// | |_) | '__/ _ \| |/ _ \/ __| __|
-// | __/| | | (_) | | __/ (__| |_
-// |_| |_| \___// |\___|\___|\__|
-// |__/
-
-// NumIssues return counter of all issues assigned to a project
-func (p *Project) NumIssues() int {
- c, err := db.GetEngine(db.DefaultContext).Table("project_issue").
- Where("project_id=?", p.ID).
- GroupBy("issue_id").
- Cols("issue_id").
- Count()
- if err != nil {
- return 0
- }
- return int(c)
-}
-
-// NumClosedIssues return counter of closed issues assigned to a project
-func (p *Project) NumClosedIssues() int {
- c, err := db.GetEngine(db.DefaultContext).Table("project_issue").
- Join("INNER", "issue", "project_issue.issue_id=issue.id").
- Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true).
- Cols("issue_id").
- Count()
- if err != nil {
- return 0
- }
- return int(c)
-}
-
-// NumOpenIssues return counter of open issues assigned to a project
-func (p *Project) NumOpenIssues() int {
- c, err := db.GetEngine(db.DefaultContext).Table("project_issue").
- Join("INNER", "issue", "project_issue.issue_id=issue.id").
- Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false).
- Cols("issue_id").
- Count()
- if err != nil {
- return 0
- }
- return int(c)
-}
-
-// ChangeProjectAssign changes the project associated with an issue
-func ChangeProjectAssign(issue *Issue, doer *user_model.User, newProjectID int64) error {
- ctx, committer, err := db.TxContext()
- if err != nil {
- return err
- }
- defer committer.Close()
-
- if err := addUpdateIssueProject(ctx, issue, doer, newProjectID); err != nil {
- return err
- }
-
- return committer.Commit()
-}
-
-func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
- e := db.GetEngine(ctx)
- oldProjectID := issue.projectID(e)
-
- if _, err := e.Where("project_issue.issue_id=?", issue.ID).Delete(&ProjectIssue{}); err != nil {
- return err
- }
-
- if err := issue.loadRepo(ctx); err != nil {
- return err
- }
-
- if oldProjectID > 0 || newProjectID > 0 {
- if _, err := createComment(ctx, &CreateCommentOptions{
- Type: CommentTypeProject,
- Doer: doer,
- Repo: issue.Repo,
- Issue: issue,
- OldProjectID: oldProjectID,
- ProjectID: newProjectID,
- }); err != nil {
- return err
- }
- }
-
- _, err := e.Insert(&ProjectIssue{
- IssueID: issue.ID,
- ProjectID: newProjectID,
- })
- return err
-}
-
-// ____ _ _ ____ _
-// | _ \ _ __ ___ (_) ___ ___| |_| __ ) ___ __ _ _ __ __| |
-// | |_) | '__/ _ \| |/ _ \/ __| __| _ \ / _ \ / _` | '__/ _` |
-// | __/| | | (_) | | __/ (__| |_| |_) | (_) | (_| | | | (_| |
-// |_| |_| \___// |\___|\___|\__|____/ \___/ \__,_|_| \__,_|
-// |__/
-
-// MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column
-func MoveIssuesOnProjectBoard(board *ProjectBoard, sortedIssueIDs map[int64]int64) error {
- return db.WithTx(func(ctx context.Context) error {
- sess := db.GetEngine(ctx)
-
- issueIDs := make([]int64, 0, len(sortedIssueIDs))
- for _, issueID := range sortedIssueIDs {
- issueIDs = append(issueIDs, issueID)
- }
- count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", board.ProjectID).In("issue_id", issueIDs).Count()
- if err != nil {
- return err
- }
- if int(count) != len(sortedIssueIDs) {
- return fmt.Errorf("all issues have to be added to a project first")
- }
-
- for sorting, issueID := range sortedIssueIDs {
- _, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", board.ID, sorting, issueID)
- if err != nil {
- return err
- }
- }
- return nil
- })
-}
-
-func (pb *ProjectBoard) removeIssues(e db.Engine) error {
- _, err := e.Exec("UPDATE `project_issue` SET project_board_id = 0, sorting = 0 WHERE project_board_id = ? ", pb.ID)
- return err
-}
diff --git a/models/repo.go b/models/repo.go
index 5a6a6b1a31..70539e849b 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -21,6 +21,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
+ project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
@@ -748,14 +749,14 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
}
}
- projects, _, err := getProjects(sess, ProjectSearchOptions{
+ projects, _, err := project_model.GetProjectsCtx(ctx, project_model.SearchOptions{
RepoID: repoID,
})
if err != nil {
return fmt.Errorf("get projects: %v", err)
}
for i := range projects {
- if err := deleteProjectByID(sess, projects[i].ID); err != nil {
+ if err := project_model.DeleteProjectByIDCtx(ctx, projects[i].ID); err != nil {
return fmt.Errorf("delete project [%d]: %v", projects[i].ID, err)
}
}
diff --git a/models/statistic.go b/models/statistic.go
index 67be4c5d42..a63cc2878b 100644
--- a/models/statistic.go
+++ b/models/statistic.go
@@ -9,6 +9,7 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
+ project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
@@ -106,7 +107,7 @@ func GetStatistic() (stats Statistic) {
stats.Counter.HookTask, _ = e.Count(new(webhook.HookTask))
stats.Counter.Team, _ = e.Count(new(organization.Team))
stats.Counter.Attachment, _ = e.Count(new(repo_model.Attachment))
- stats.Counter.Project, _ = e.Count(new(Project))
- stats.Counter.ProjectBoard, _ = e.Count(new(ProjectBoard))
+ stats.Counter.Project, _ = e.Count(new(project_model.Project))
+ stats.Counter.ProjectBoard, _ = e.Count(new(project_model.Board))
return
}