mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	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>
This commit is contained in:
		@@ -1063,44 +1063,6 @@ func (err ErrLabelNotExist) Error() string {
 | 
				
			|||||||
	return fmt.Sprintf("label does not exist [label_id: %d]", err.LabelID)
 | 
						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)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//    _____  .__.__                   __
 | 
					//    _____  .__.__                   __
 | 
				
			||||||
//   /     \ |__|  |   ____   _______/  |_  ____   ____   ____
 | 
					//   /     \ |__|  |   ____   _______/  |_  ____   ____   ____
 | 
				
			||||||
//  /  \ /  \|  |  | _/ __ \ /  ___/\   __\/  _ \ /    \_/ __ \
 | 
					//  /  \ /  \|  |  | _/ __ \ /  ___/\   __\/  _ \ /    \_/ __ \
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/models/issues"
 | 
						"code.gitea.io/gitea/models/issues"
 | 
				
			||||||
	"code.gitea.io/gitea/models/organization"
 | 
						"code.gitea.io/gitea/models/organization"
 | 
				
			||||||
	"code.gitea.io/gitea/models/perm"
 | 
						"code.gitea.io/gitea/models/perm"
 | 
				
			||||||
 | 
						project_model "code.gitea.io/gitea/models/project"
 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
	"code.gitea.io/gitea/models/unit"
 | 
						"code.gitea.io/gitea/models/unit"
 | 
				
			||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
@@ -52,7 +53,7 @@ type Issue struct {
 | 
				
			|||||||
	Labels           []*Label               `xorm:"-"`
 | 
						Labels           []*Label               `xorm:"-"`
 | 
				
			||||||
	MilestoneID      int64                  `xorm:"INDEX"`
 | 
						MilestoneID      int64                  `xorm:"INDEX"`
 | 
				
			||||||
	Milestone        *Milestone             `xorm:"-"`
 | 
						Milestone        *Milestone             `xorm:"-"`
 | 
				
			||||||
	Project          *Project   `xorm:"-"`
 | 
						Project          *project_model.Project `xorm:"-"`
 | 
				
			||||||
	Priority         int
 | 
						Priority         int
 | 
				
			||||||
	AssigneeID       int64            `xorm:"-"`
 | 
						AssigneeID       int64            `xorm:"-"`
 | 
				
			||||||
	Assignee         *user_model.User `xorm:"-"`
 | 
						Assignee         *user_model.User `xorm:"-"`
 | 
				
			||||||
@@ -2135,7 +2136,7 @@ func deleteIssue(ctx context.Context, issue *Issue) error {
 | 
				
			|||||||
		&IssueWatch{},
 | 
							&IssueWatch{},
 | 
				
			||||||
		&Stopwatch{},
 | 
							&Stopwatch{},
 | 
				
			||||||
		&TrackedTime{},
 | 
							&TrackedTime{},
 | 
				
			||||||
		&ProjectIssue{},
 | 
							&project_model.ProjectIssue{},
 | 
				
			||||||
		&repo_model.Attachment{},
 | 
							&repo_model.Attachment{},
 | 
				
			||||||
		&PullRequest{},
 | 
							&PullRequest{},
 | 
				
			||||||
	); err != nil {
 | 
						); err != nil {
 | 
				
			||||||
@@ -2469,7 +2470,7 @@ func deleteIssuesByRepoID(sess db.Engine, repoID int64) (attachmentPaths []strin
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, err = sess.In("issue_id", deleteCond).
 | 
						if _, err = sess.In("issue_id", deleteCond).
 | 
				
			||||||
		Delete(&ProjectIssue{}); err != nil {
 | 
							Delete(&project_model.ProjectIssue{}); err != nil {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
	"code.gitea.io/gitea/models/issues"
 | 
						"code.gitea.io/gitea/models/issues"
 | 
				
			||||||
	"code.gitea.io/gitea/models/organization"
 | 
						"code.gitea.io/gitea/models/organization"
 | 
				
			||||||
 | 
						project_model "code.gitea.io/gitea/models/project"
 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/git"
 | 
						"code.gitea.io/gitea/modules/git"
 | 
				
			||||||
@@ -204,8 +205,8 @@ type Comment struct {
 | 
				
			|||||||
	RemovedLabels    []*Label `xorm:"-"`
 | 
						RemovedLabels    []*Label `xorm:"-"`
 | 
				
			||||||
	OldProjectID     int64
 | 
						OldProjectID     int64
 | 
				
			||||||
	ProjectID        int64
 | 
						ProjectID        int64
 | 
				
			||||||
	OldProject       *Project `xorm:"-"`
 | 
						OldProject       *project_model.Project `xorm:"-"`
 | 
				
			||||||
	Project          *Project `xorm:"-"`
 | 
						Project          *project_model.Project `xorm:"-"`
 | 
				
			||||||
	OldMilestoneID   int64
 | 
						OldMilestoneID   int64
 | 
				
			||||||
	MilestoneID      int64
 | 
						MilestoneID      int64
 | 
				
			||||||
	OldMilestone     *Milestone `xorm:"-"`
 | 
						OldMilestone     *Milestone `xorm:"-"`
 | 
				
			||||||
@@ -469,7 +470,7 @@ func (c *Comment) LoadLabel() error {
 | 
				
			|||||||
// LoadProject if comment.Type is CommentTypeProject, then load project.
 | 
					// LoadProject if comment.Type is CommentTypeProject, then load project.
 | 
				
			||||||
func (c *Comment) LoadProject() error {
 | 
					func (c *Comment) LoadProject() error {
 | 
				
			||||||
	if c.OldProjectID > 0 {
 | 
						if c.OldProjectID > 0 {
 | 
				
			||||||
		var oldProject Project
 | 
							var oldProject project_model.Project
 | 
				
			||||||
		has, err := db.GetEngine(db.DefaultContext).ID(c.OldProjectID).Get(&oldProject)
 | 
							has, err := db.GetEngine(db.DefaultContext).ID(c.OldProjectID).Get(&oldProject)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
@@ -479,7 +480,7 @@ func (c *Comment) LoadProject() error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if c.ProjectID > 0 {
 | 
						if c.ProjectID > 0 {
 | 
				
			||||||
		var project Project
 | 
							var project project_model.Project
 | 
				
			||||||
		has, err := db.GetEngine(db.DefaultContext).ID(c.ProjectID).Get(&project)
 | 
							has, err := db.GetEngine(db.DefaultContext).ID(c.ProjectID).Get(&project)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										181
									
								
								models/issue_project.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								models/issue_project.go
									
									
									
									
									
										Normal file
									
								
							@@ -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()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										289
									
								
								models/project/board.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								models/project/board.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										100
									
								
								models/project/issue.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								models/project/issue.go
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								models/project/main_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								models/project/main_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -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",
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2,9 +2,10 @@
 | 
				
			|||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package models
 | 
					package project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -19,25 +20,56 @@ import (
 | 
				
			|||||||
type (
 | 
					type (
 | 
				
			||||||
	// ProjectsConfig is used to identify the type of board that is being created
 | 
						// ProjectsConfig is used to identify the type of board that is being created
 | 
				
			||||||
	ProjectsConfig struct {
 | 
						ProjectsConfig struct {
 | 
				
			||||||
		BoardType   ProjectBoardType
 | 
							BoardType   BoardType
 | 
				
			||||||
		Translation string
 | 
							Translation string
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// ProjectType is used to identify the type of project in question and ownership
 | 
						// Type is used to identify the type of project in question and ownership
 | 
				
			||||||
	ProjectType uint8
 | 
						Type uint8
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	// ProjectTypeIndividual is a type of project board that is owned by an individual
 | 
						// TypeIndividual is a type of project board that is owned by an individual
 | 
				
			||||||
	ProjectTypeIndividual ProjectType = iota + 1
 | 
						TypeIndividual Type = iota + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// ProjectTypeRepository is a project that is tied to a repository
 | 
						// TypeRepository is a project that is tied to a repository
 | 
				
			||||||
	ProjectTypeRepository
 | 
						TypeRepository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// ProjectTypeOrganization is a project that is tied to an organisation
 | 
						// TypeOrganization is a project that is tied to an organisation
 | 
				
			||||||
	ProjectTypeOrganization
 | 
						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
 | 
					// Project represents a project board
 | 
				
			||||||
type Project struct {
 | 
					type Project struct {
 | 
				
			||||||
	ID          int64  `xorm:"pk autoincr"`
 | 
						ID          int64  `xorm:"pk autoincr"`
 | 
				
			||||||
@@ -46,8 +78,8 @@ type Project struct {
 | 
				
			|||||||
	RepoID      int64  `xorm:"INDEX"`
 | 
						RepoID      int64  `xorm:"INDEX"`
 | 
				
			||||||
	CreatorID   int64  `xorm:"NOT NULL"`
 | 
						CreatorID   int64  `xorm:"NOT NULL"`
 | 
				
			||||||
	IsClosed    bool   `xorm:"INDEX"`
 | 
						IsClosed    bool   `xorm:"INDEX"`
 | 
				
			||||||
	BoardType   ProjectBoardType
 | 
						BoardType   BoardType
 | 
				
			||||||
	Type        ProjectType
 | 
						Type        Type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	RenderedContent string `xorm:"-"`
 | 
						RenderedContent string `xorm:"-"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -63,37 +95,39 @@ func init() {
 | 
				
			|||||||
// GetProjectsConfig retrieves the types of configurations projects could have
 | 
					// GetProjectsConfig retrieves the types of configurations projects could have
 | 
				
			||||||
func GetProjectsConfig() []ProjectsConfig {
 | 
					func GetProjectsConfig() []ProjectsConfig {
 | 
				
			||||||
	return []ProjectsConfig{
 | 
						return []ProjectsConfig{
 | 
				
			||||||
		{ProjectBoardTypeNone, "repo.projects.type.none"},
 | 
							{BoardTypeNone, "repo.projects.type.none"},
 | 
				
			||||||
		{ProjectBoardTypeBasicKanban, "repo.projects.type.basic_kanban"},
 | 
							{BoardTypeBasicKanban, "repo.projects.type.basic_kanban"},
 | 
				
			||||||
		{ProjectBoardTypeBugTriage, "repo.projects.type.bug_triage"},
 | 
							{BoardTypeBugTriage, "repo.projects.type.bug_triage"},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// IsProjectTypeValid checks if a project type is valid
 | 
					// IsTypeValid checks if a project type is valid
 | 
				
			||||||
func IsProjectTypeValid(p ProjectType) bool {
 | 
					func IsTypeValid(p Type) bool {
 | 
				
			||||||
	switch p {
 | 
						switch p {
 | 
				
			||||||
	case ProjectTypeRepository:
 | 
						case TypeRepository:
 | 
				
			||||||
		return true
 | 
							return true
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		return false
 | 
							return false
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ProjectSearchOptions are options for GetProjects
 | 
					// SearchOptions are options for GetProjects
 | 
				
			||||||
type ProjectSearchOptions struct {
 | 
					type SearchOptions struct {
 | 
				
			||||||
	RepoID   int64
 | 
						RepoID   int64
 | 
				
			||||||
	Page     int
 | 
						Page     int
 | 
				
			||||||
	IsClosed util.OptionalBool
 | 
						IsClosed util.OptionalBool
 | 
				
			||||||
	SortType string
 | 
						SortType string
 | 
				
			||||||
	Type     ProjectType
 | 
						Type     Type
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetProjects returns a list of all projects that have been created in the repository
 | 
					// GetProjects returns a list of all projects that have been created in the repository
 | 
				
			||||||
func GetProjects(opts ProjectSearchOptions) ([]*Project, int64, error) {
 | 
					func GetProjects(opts SearchOptions) ([]*Project, int64, error) {
 | 
				
			||||||
	return getProjects(db.GetEngine(db.DefaultContext), opts)
 | 
						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)
 | 
						projects := make([]*Project, 0, setting.UI.IssuePagingNum)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var cond builder.Cond = builder.Eq{"repo_id": opts.RepoID}
 | 
						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
 | 
					// NewProject creates a new Project
 | 
				
			||||||
func NewProject(p *Project) error {
 | 
					func NewProject(p *Project) error {
 | 
				
			||||||
	if !IsProjectBoardTypeValid(p.BoardType) {
 | 
						if !IsBoardTypeValid(p.BoardType) {
 | 
				
			||||||
		p.BoardType = ProjectBoardTypeNone
 | 
							p.BoardType = BoardTypeNone
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !IsProjectTypeValid(p.Type) {
 | 
						if !IsTypeValid(p.Type) {
 | 
				
			||||||
		return errors.New("project type is not valid")
 | 
							return errors.New("project type is not valid")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -157,7 +191,7 @@ func NewProject(p *Project) error {
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := createBoardsForProjectsType(db.GetEngine(ctx), p); err != nil {
 | 
						if err := createBoardsForProjectsType(ctx, p); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -200,7 +234,7 @@ func updateRepositoryProjectCount(e db.Engine, repoID int64) error {
 | 
				
			|||||||
		builder.Eq{
 | 
							builder.Eq{
 | 
				
			||||||
			"`num_projects`": builder.Select("count(*)").From("`project`").
 | 
								"`num_projects`": builder.Select("count(*)").From("`project`").
 | 
				
			||||||
				Where(builder.Eq{"`project`.`repo_id`": repoID}.
 | 
									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 {
 | 
							}).From("`repository`").Where(builder.Eq{"id": repoID})); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -209,7 +243,7 @@ func updateRepositoryProjectCount(e db.Engine, repoID int64) error {
 | 
				
			|||||||
		builder.Eq{
 | 
							builder.Eq{
 | 
				
			||||||
			"`num_closed_projects`": builder.Select("count(*)").From("`project`").
 | 
								"`num_closed_projects`": builder.Select("count(*)").From("`project`").
 | 
				
			||||||
				Where(builder.Eq{"`project`.`repo_id`": repoID}.
 | 
									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})),
 | 
										And(builder.Eq{"`project`.`is_closed`": true})),
 | 
				
			||||||
		}).From("`repository`").Where(builder.Eq{"id": repoID})); err != nil {
 | 
							}).From("`repository`").Where(builder.Eq{"id": repoID})); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
@@ -224,18 +258,17 @@ func ChangeProjectStatusByRepoIDAndID(repoID, projectID int64, isClosed bool) er
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer committer.Close()
 | 
						defer committer.Close()
 | 
				
			||||||
	sess := db.GetEngine(ctx)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	p := new(Project)
 | 
						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 {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	} else if !has {
 | 
						} else if !has {
 | 
				
			||||||
		return ErrProjectNotExist{ID: projectID, RepoID: repoID}
 | 
							return ErrProjectNotExist{ID: projectID, RepoID: repoID}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := changeProjectStatus(sess, p, isClosed); err != nil {
 | 
						if err := changeProjectStatus(ctx, p, isClosed); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -250,16 +283,17 @@ func ChangeProjectStatus(p *Project, isClosed bool) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	defer committer.Close()
 | 
						defer committer.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := changeProjectStatus(db.GetEngine(ctx), p, isClosed); err != nil {
 | 
						if err := changeProjectStatus(ctx, p, isClosed); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return committer.Commit()
 | 
						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.IsClosed = isClosed
 | 
				
			||||||
	p.ClosedDateUnix = timeutil.TimeStampNow()
 | 
						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)
 | 
						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 {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
@@ -279,14 +313,16 @@ func DeleteProjectByID(id int64) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	defer committer.Close()
 | 
						defer committer.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := deleteProjectByID(db.GetEngine(ctx), id); err != nil {
 | 
						if err := DeleteProjectByIDCtx(ctx, id); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return committer.Commit()
 | 
						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)
 | 
						p, err := getProjectByID(e, id)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if IsErrProjectNotExist(err) {
 | 
							if IsErrProjectNotExist(err) {
 | 
				
			||||||
@@ -299,7 +335,7 @@ func deleteProjectByID(e db.Engine, id int64) error {
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := deleteProjectBoardByProjectID(e, id); err != nil {
 | 
						if err := deleteBoardByProjectID(e, id); err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package models
 | 
					package project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
@@ -14,33 +14,33 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestIsProjectTypeValid(t *testing.T) {
 | 
					func TestIsProjectTypeValid(t *testing.T) {
 | 
				
			||||||
	const UnknownType ProjectType = 15
 | 
						const UnknownType Type = 15
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cases := []struct {
 | 
						cases := []struct {
 | 
				
			||||||
		typ   ProjectType
 | 
							typ   Type
 | 
				
			||||||
		valid bool
 | 
							valid bool
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		{ProjectTypeIndividual, false},
 | 
							{TypeIndividual, false},
 | 
				
			||||||
		{ProjectTypeRepository, true},
 | 
							{TypeRepository, true},
 | 
				
			||||||
		{ProjectTypeOrganization, false},
 | 
							{TypeOrganization, false},
 | 
				
			||||||
		{UnknownType, false},
 | 
							{UnknownType, false},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, v := range cases {
 | 
						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) {
 | 
					func TestGetProjects(t *testing.T) {
 | 
				
			||||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
						assert.NoError(t, unittest.PrepareTestDatabase())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	projects, _, err := GetProjects(ProjectSearchOptions{RepoID: 1})
 | 
						projects, _, err := GetProjects(SearchOptions{RepoID: 1})
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 1 value for this repo exists in the fixtures
 | 
						// 1 value for this repo exists in the fixtures
 | 
				
			||||||
	assert.Len(t, projects, 1)
 | 
						assert.Len(t, projects, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	projects, _, err = GetProjects(ProjectSearchOptions{RepoID: 3})
 | 
						projects, _, err = GetProjects(SearchOptions{RepoID: 3})
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 1 value for this repo exists in the fixtures
 | 
						// 1 value for this repo exists in the fixtures
 | 
				
			||||||
@@ -51,8 +51,8 @@ func TestProject(t *testing.T) {
 | 
				
			|||||||
	assert.NoError(t, unittest.PrepareTestDatabase())
 | 
						assert.NoError(t, unittest.PrepareTestDatabase())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	project := &Project{
 | 
						project := &Project{
 | 
				
			||||||
		Type:        ProjectTypeRepository,
 | 
							Type:        TypeRepository,
 | 
				
			||||||
		BoardType:   ProjectBoardTypeBasicKanban,
 | 
							BoardType:   BoardTypeBasicKanban,
 | 
				
			||||||
		Title:       "New Project",
 | 
							Title:       "New Project",
 | 
				
			||||||
		RepoID:      1,
 | 
							RepoID:      1,
 | 
				
			||||||
		CreatedUnix: timeutil.TimeStampNow(),
 | 
							CreatedUnix: timeutil.TimeStampNow(),
 | 
				
			||||||
@@ -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
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -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
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -21,6 +21,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
	"code.gitea.io/gitea/models/organization"
 | 
						"code.gitea.io/gitea/models/organization"
 | 
				
			||||||
	"code.gitea.io/gitea/models/perm"
 | 
						"code.gitea.io/gitea/models/perm"
 | 
				
			||||||
 | 
						project_model "code.gitea.io/gitea/models/project"
 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
	"code.gitea.io/gitea/models/unit"
 | 
						"code.gitea.io/gitea/models/unit"
 | 
				
			||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						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,
 | 
							RepoID: repoID,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return fmt.Errorf("get projects: %v", err)
 | 
							return fmt.Errorf("get projects: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for i := range projects {
 | 
						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)
 | 
								return fmt.Errorf("delete project [%d]: %v", projects[i].ID, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/models/auth"
 | 
						"code.gitea.io/gitea/models/auth"
 | 
				
			||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
	"code.gitea.io/gitea/models/organization"
 | 
						"code.gitea.io/gitea/models/organization"
 | 
				
			||||||
 | 
						project_model "code.gitea.io/gitea/models/project"
 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
	"code.gitea.io/gitea/models/webhook"
 | 
						"code.gitea.io/gitea/models/webhook"
 | 
				
			||||||
@@ -106,7 +107,7 @@ func GetStatistic() (stats Statistic) {
 | 
				
			|||||||
	stats.Counter.HookTask, _ = e.Count(new(webhook.HookTask))
 | 
						stats.Counter.HookTask, _ = e.Count(new(webhook.HookTask))
 | 
				
			||||||
	stats.Counter.Team, _ = e.Count(new(organization.Team))
 | 
						stats.Counter.Team, _ = e.Count(new(organization.Team))
 | 
				
			||||||
	stats.Counter.Attachment, _ = e.Count(new(repo_model.Attachment))
 | 
						stats.Counter.Attachment, _ = e.Count(new(repo_model.Attachment))
 | 
				
			||||||
	stats.Counter.Project, _ = e.Count(new(Project))
 | 
						stats.Counter.Project, _ = e.Count(new(project_model.Project))
 | 
				
			||||||
	stats.Counter.ProjectBoard, _ = e.Count(new(ProjectBoard))
 | 
						stats.Counter.ProjectBoard, _ = e.Count(new(project_model.Board))
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
	"code.gitea.io/gitea/models/organization"
 | 
						"code.gitea.io/gitea/models/organization"
 | 
				
			||||||
 | 
						project_model "code.gitea.io/gitea/models/project"
 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
	"code.gitea.io/gitea/models/unit"
 | 
						"code.gitea.io/gitea/models/unit"
 | 
				
			||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
@@ -336,9 +337,9 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if ctx.Repo.CanWriteIssuesOrPulls(ctx.Params(":type") == "pulls") {
 | 
						if ctx.Repo.CanWriteIssuesOrPulls(ctx.Params(":type") == "pulls") {
 | 
				
			||||||
		projects, _, err := models.GetProjects(models.ProjectSearchOptions{
 | 
							projects, _, err := project_model.GetProjects(project_model.SearchOptions{
 | 
				
			||||||
			RepoID:   repo.ID,
 | 
								RepoID:   repo.ID,
 | 
				
			||||||
			Type:     models.ProjectTypeRepository,
 | 
								Type:     project_model.TypeRepository,
 | 
				
			||||||
			IsClosed: util.OptionalBoolOf(isShowClosed),
 | 
								IsClosed: util.OptionalBoolOf(isShowClosed),
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
@@ -446,22 +447,22 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.R
 | 
				
			|||||||
func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) {
 | 
					func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) {
 | 
				
			||||||
	var err error
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Data["OpenProjects"], _, err = models.GetProjects(models.ProjectSearchOptions{
 | 
						ctx.Data["OpenProjects"], _, err = project_model.GetProjects(project_model.SearchOptions{
 | 
				
			||||||
		RepoID:   repo.ID,
 | 
							RepoID:   repo.ID,
 | 
				
			||||||
		Page:     -1,
 | 
							Page:     -1,
 | 
				
			||||||
		IsClosed: util.OptionalBoolFalse,
 | 
							IsClosed: util.OptionalBoolFalse,
 | 
				
			||||||
		Type:     models.ProjectTypeRepository,
 | 
							Type:     project_model.TypeRepository,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("GetProjects", err)
 | 
							ctx.ServerError("GetProjects", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Data["ClosedProjects"], _, err = models.GetProjects(models.ProjectSearchOptions{
 | 
						ctx.Data["ClosedProjects"], _, err = project_model.GetProjects(project_model.SearchOptions{
 | 
				
			||||||
		RepoID:   repo.ID,
 | 
							RepoID:   repo.ID,
 | 
				
			||||||
		Page:     -1,
 | 
							Page:     -1,
 | 
				
			||||||
		IsClosed: util.OptionalBoolTrue,
 | 
							IsClosed: util.OptionalBoolTrue,
 | 
				
			||||||
		Type:     models.ProjectTypeRepository,
 | 
							Type:     project_model.TypeRepository,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("GetProjects", err)
 | 
							ctx.ServerError("GetProjects", err)
 | 
				
			||||||
@@ -814,7 +815,7 @@ func NewIssue(ctx *context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	projectID := ctx.FormInt64("project")
 | 
						projectID := ctx.FormInt64("project")
 | 
				
			||||||
	if projectID > 0 {
 | 
						if projectID > 0 {
 | 
				
			||||||
		project, err := models.GetProjectByID(projectID)
 | 
							project, err := project_model.GetProjectByID(projectID)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.Error("GetProjectByID: %d: %v", projectID, err)
 | 
								log.Error("GetProjectByID: %d: %v", projectID, err)
 | 
				
			||||||
		} else if project.RepoID != ctx.Repo.Repository.ID {
 | 
							} else if project.RepoID != ctx.Repo.Repository.ID {
 | 
				
			||||||
@@ -926,7 +927,7 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if form.ProjectID > 0 {
 | 
						if form.ProjectID > 0 {
 | 
				
			||||||
		p, err := models.GetProjectByID(form.ProjectID)
 | 
							p, err := project_model.GetProjectByID(form.ProjectID)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			ctx.ServerError("GetProjectByID", err)
 | 
								ctx.ServerError("GetProjectByID", err)
 | 
				
			||||||
			return nil, nil, 0, 0
 | 
								return nil, nil, 0, 0
 | 
				
			||||||
@@ -1413,7 +1414,7 @@ func ViewIssue(ctx *context.Context) {
 | 
				
			|||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			ghostProject := &models.Project{
 | 
								ghostProject := &project_model.Project{
 | 
				
			||||||
				ID:    -1,
 | 
									ID:    -1,
 | 
				
			||||||
				Title: ctx.Tr("repo.issues.deleted_project"),
 | 
									Title: ctx.Tr("repo.issues.deleted_project"),
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/models/perm"
 | 
						"code.gitea.io/gitea/models/perm"
 | 
				
			||||||
 | 
						project_model "code.gitea.io/gitea/models/project"
 | 
				
			||||||
	"code.gitea.io/gitea/models/unit"
 | 
						"code.gitea.io/gitea/models/unit"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/base"
 | 
						"code.gitea.io/gitea/modules/base"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
@@ -69,12 +70,12 @@ func Projects(ctx *context.Context) {
 | 
				
			|||||||
		total = repo.NumClosedProjects
 | 
							total = repo.NumClosedProjects
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	projects, count, err := models.GetProjects(models.ProjectSearchOptions{
 | 
						projects, count, err := project_model.GetProjects(project_model.SearchOptions{
 | 
				
			||||||
		RepoID:   repo.ID,
 | 
							RepoID:   repo.ID,
 | 
				
			||||||
		Page:     page,
 | 
							Page:     page,
 | 
				
			||||||
		IsClosed: util.OptionalBoolOf(isShowClosed),
 | 
							IsClosed: util.OptionalBoolOf(isShowClosed),
 | 
				
			||||||
		SortType: sortType,
 | 
							SortType: sortType,
 | 
				
			||||||
		Type:     models.ProjectTypeRepository,
 | 
							Type:     project_model.TypeRepository,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("GetProjects", err)
 | 
							ctx.ServerError("GetProjects", err)
 | 
				
			||||||
@@ -122,7 +123,7 @@ func Projects(ctx *context.Context) {
 | 
				
			|||||||
// NewProject render creating a project page
 | 
					// NewProject render creating a project page
 | 
				
			||||||
func NewProject(ctx *context.Context) {
 | 
					func NewProject(ctx *context.Context) {
 | 
				
			||||||
	ctx.Data["Title"] = ctx.Tr("repo.projects.new")
 | 
						ctx.Data["Title"] = ctx.Tr("repo.projects.new")
 | 
				
			||||||
	ctx.Data["ProjectTypes"] = models.GetProjectsConfig()
 | 
						ctx.Data["ProjectTypes"] = project_model.GetProjectsConfig()
 | 
				
			||||||
	ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
 | 
						ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
 | 
				
			||||||
	ctx.HTML(http.StatusOK, tplProjectsNew)
 | 
						ctx.HTML(http.StatusOK, tplProjectsNew)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -134,18 +135,18 @@ func NewProjectPost(ctx *context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if ctx.HasError() {
 | 
						if ctx.HasError() {
 | 
				
			||||||
		ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
 | 
							ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
 | 
				
			||||||
		ctx.Data["ProjectTypes"] = models.GetProjectsConfig()
 | 
							ctx.Data["ProjectTypes"] = project_model.GetProjectsConfig()
 | 
				
			||||||
		ctx.HTML(http.StatusOK, tplProjectsNew)
 | 
							ctx.HTML(http.StatusOK, tplProjectsNew)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := models.NewProject(&models.Project{
 | 
						if err := project_model.NewProject(&project_model.Project{
 | 
				
			||||||
		RepoID:      ctx.Repo.Repository.ID,
 | 
							RepoID:      ctx.Repo.Repository.ID,
 | 
				
			||||||
		Title:       form.Title,
 | 
							Title:       form.Title,
 | 
				
			||||||
		Description: form.Content,
 | 
							Description: form.Content,
 | 
				
			||||||
		CreatorID:   ctx.Doer.ID,
 | 
							CreatorID:   ctx.Doer.ID,
 | 
				
			||||||
		BoardType:   form.BoardType,
 | 
							BoardType:   form.BoardType,
 | 
				
			||||||
		Type:        models.ProjectTypeRepository,
 | 
							Type:        project_model.TypeRepository,
 | 
				
			||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
		ctx.ServerError("NewProject", err)
 | 
							ctx.ServerError("NewProject", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@@ -168,8 +169,8 @@ func ChangeProjectStatus(ctx *context.Context) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	id := ctx.ParamsInt64(":id")
 | 
						id := ctx.ParamsInt64(":id")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := models.ChangeProjectStatusByRepoIDAndID(ctx.Repo.Repository.ID, id, toClose); err != nil {
 | 
						if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx.Repo.Repository.ID, id, toClose); err != nil {
 | 
				
			||||||
		if models.IsErrProjectNotExist(err) {
 | 
							if project_model.IsErrProjectNotExist(err) {
 | 
				
			||||||
			ctx.NotFound("", err)
 | 
								ctx.NotFound("", err)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ctx.ServerError("ChangeProjectStatusByIDAndRepoID", err)
 | 
								ctx.ServerError("ChangeProjectStatusByIDAndRepoID", err)
 | 
				
			||||||
@@ -181,9 +182,9 @@ func ChangeProjectStatus(ctx *context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// DeleteProject delete a project
 | 
					// DeleteProject delete a project
 | 
				
			||||||
func DeleteProject(ctx *context.Context) {
 | 
					func DeleteProject(ctx *context.Context) {
 | 
				
			||||||
	p, err := models.GetProjectByID(ctx.ParamsInt64(":id"))
 | 
						p, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if models.IsErrProjectNotExist(err) {
 | 
							if project_model.IsErrProjectNotExist(err) {
 | 
				
			||||||
			ctx.NotFound("", nil)
 | 
								ctx.NotFound("", nil)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ctx.ServerError("GetProjectByID", err)
 | 
								ctx.ServerError("GetProjectByID", err)
 | 
				
			||||||
@@ -195,7 +196,7 @@ func DeleteProject(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := models.DeleteProjectByID(p.ID); err != nil {
 | 
						if err := project_model.DeleteProjectByID(p.ID); err != nil {
 | 
				
			||||||
		ctx.Flash.Error("DeleteProjectByID: " + err.Error())
 | 
							ctx.Flash.Error("DeleteProjectByID: " + err.Error())
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		ctx.Flash.Success(ctx.Tr("repo.projects.deletion_success"))
 | 
							ctx.Flash.Success(ctx.Tr("repo.projects.deletion_success"))
 | 
				
			||||||
@@ -212,9 +213,9 @@ func EditProject(ctx *context.Context) {
 | 
				
			|||||||
	ctx.Data["PageIsEditProjects"] = true
 | 
						ctx.Data["PageIsEditProjects"] = true
 | 
				
			||||||
	ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
 | 
						ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	p, err := models.GetProjectByID(ctx.ParamsInt64(":id"))
 | 
						p, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if models.IsErrProjectNotExist(err) {
 | 
							if project_model.IsErrProjectNotExist(err) {
 | 
				
			||||||
			ctx.NotFound("", nil)
 | 
								ctx.NotFound("", nil)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ctx.ServerError("GetProjectByID", err)
 | 
								ctx.ServerError("GetProjectByID", err)
 | 
				
			||||||
@@ -244,9 +245,9 @@ func EditProjectPost(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	p, err := models.GetProjectByID(ctx.ParamsInt64(":id"))
 | 
						p, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if models.IsErrProjectNotExist(err) {
 | 
							if project_model.IsErrProjectNotExist(err) {
 | 
				
			||||||
			ctx.NotFound("", nil)
 | 
								ctx.NotFound("", nil)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ctx.ServerError("GetProjectByID", err)
 | 
								ctx.ServerError("GetProjectByID", err)
 | 
				
			||||||
@@ -260,7 +261,7 @@ func EditProjectPost(ctx *context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	p.Title = form.Title
 | 
						p.Title = form.Title
 | 
				
			||||||
	p.Description = form.Content
 | 
						p.Description = form.Content
 | 
				
			||||||
	if err = models.UpdateProject(p); err != nil {
 | 
						if err = project_model.UpdateProject(p); err != nil {
 | 
				
			||||||
		ctx.ServerError("UpdateProjects", err)
 | 
							ctx.ServerError("UpdateProjects", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -271,9 +272,9 @@ func EditProjectPost(ctx *context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// ViewProject renders the project board for a project
 | 
					// ViewProject renders the project board for a project
 | 
				
			||||||
func ViewProject(ctx *context.Context) {
 | 
					func ViewProject(ctx *context.Context) {
 | 
				
			||||||
	project, err := models.GetProjectByID(ctx.ParamsInt64(":id"))
 | 
						project, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if models.IsErrProjectNotExist(err) {
 | 
							if project_model.IsErrProjectNotExist(err) {
 | 
				
			||||||
			ctx.NotFound("", nil)
 | 
								ctx.NotFound("", nil)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ctx.ServerError("GetProjectByID", err)
 | 
								ctx.ServerError("GetProjectByID", err)
 | 
				
			||||||
@@ -285,7 +286,7 @@ func ViewProject(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	boards, err := models.GetProjectBoards(project.ID)
 | 
						boards, err := project_model.GetBoards(project.ID)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("GetProjectBoards", err)
 | 
							ctx.ServerError("GetProjectBoards", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@@ -295,14 +296,15 @@ func ViewProject(ctx *context.Context) {
 | 
				
			|||||||
		boards[0].Title = ctx.Tr("repo.projects.type.uncategorized")
 | 
							boards[0].Title = ctx.Tr("repo.projects.type.uncategorized")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	issueList, err := boards.LoadIssues()
 | 
						issuesMap, err := models.LoadIssuesFromBoardList(boards)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("LoadIssuesOfBoards", err)
 | 
							ctx.ServerError("LoadIssuesOfBoards", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	linkedPrsMap := make(map[int64][]*models.Issue)
 | 
						linkedPrsMap := make(map[int64][]*models.Issue)
 | 
				
			||||||
	for _, issue := range issueList {
 | 
						for _, issuesList := range issuesMap {
 | 
				
			||||||
 | 
							for _, issue := range issuesList {
 | 
				
			||||||
			var referencedIds []int64
 | 
								var referencedIds []int64
 | 
				
			||||||
			for _, comment := range issue.Comments {
 | 
								for _, comment := range issue.Comments {
 | 
				
			||||||
				if comment.RefIssueID != 0 && comment.RefIsPull {
 | 
									if comment.RefIssueID != 0 && comment.RefIsPull {
 | 
				
			||||||
@@ -319,6 +321,7 @@ func ViewProject(ctx *context.Context) {
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	ctx.Data["LinkedPRs"] = linkedPrsMap
 | 
						ctx.Data["LinkedPRs"] = linkedPrsMap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	project.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
 | 
						project.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
 | 
				
			||||||
@@ -335,6 +338,7 @@ func ViewProject(ctx *context.Context) {
 | 
				
			|||||||
	ctx.Data["IsProjectsPage"] = true
 | 
						ctx.Data["IsProjectsPage"] = true
 | 
				
			||||||
	ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
 | 
						ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
 | 
				
			||||||
	ctx.Data["Project"] = project
 | 
						ctx.Data["Project"] = project
 | 
				
			||||||
 | 
						ctx.Data["IssuesMap"] = issuesMap
 | 
				
			||||||
	ctx.Data["Boards"] = boards
 | 
						ctx.Data["Boards"] = boards
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.HTML(http.StatusOK, tplProjectsView)
 | 
						ctx.HTML(http.StatusOK, tplProjectsView)
 | 
				
			||||||
@@ -381,9 +385,9 @@ func DeleteProjectBoard(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	project, err := models.GetProjectByID(ctx.ParamsInt64(":id"))
 | 
						project, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if models.IsErrProjectNotExist(err) {
 | 
							if project_model.IsErrProjectNotExist(err) {
 | 
				
			||||||
			ctx.NotFound("", nil)
 | 
								ctx.NotFound("", nil)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ctx.ServerError("GetProjectByID", err)
 | 
								ctx.ServerError("GetProjectByID", err)
 | 
				
			||||||
@@ -391,7 +395,7 @@ func DeleteProjectBoard(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pb, err := models.GetProjectBoard(ctx.ParamsInt64(":boardID"))
 | 
						pb, err := project_model.GetBoard(ctx.ParamsInt64(":boardID"))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("GetProjectBoard", err)
 | 
							ctx.ServerError("GetProjectBoard", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@@ -410,7 +414,7 @@ func DeleteProjectBoard(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := models.DeleteProjectBoardByID(ctx.ParamsInt64(":boardID")); err != nil {
 | 
						if err := project_model.DeleteBoardByID(ctx.ParamsInt64(":boardID")); err != nil {
 | 
				
			||||||
		ctx.ServerError("DeleteProjectBoardByID", err)
 | 
							ctx.ServerError("DeleteProjectBoardByID", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -430,9 +434,9 @@ func AddBoardToProjectPost(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	project, err := models.GetProjectByID(ctx.ParamsInt64(":id"))
 | 
						project, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if models.IsErrProjectNotExist(err) {
 | 
							if project_model.IsErrProjectNotExist(err) {
 | 
				
			||||||
			ctx.NotFound("", nil)
 | 
								ctx.NotFound("", nil)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ctx.ServerError("GetProjectByID", err)
 | 
								ctx.ServerError("GetProjectByID", err)
 | 
				
			||||||
@@ -440,7 +444,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := models.NewProjectBoard(&models.ProjectBoard{
 | 
						if err := project_model.NewBoard(&project_model.Board{
 | 
				
			||||||
		ProjectID: project.ID,
 | 
							ProjectID: project.ID,
 | 
				
			||||||
		Title:     form.Title,
 | 
							Title:     form.Title,
 | 
				
			||||||
		Color:     form.Color,
 | 
							Color:     form.Color,
 | 
				
			||||||
@@ -455,7 +459,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func checkProjectBoardChangePermissions(ctx *context.Context) (*models.Project, *models.ProjectBoard) {
 | 
					func checkProjectBoardChangePermissions(ctx *context.Context) (*project_model.Project, *project_model.Board) {
 | 
				
			||||||
	if ctx.Doer == nil {
 | 
						if ctx.Doer == nil {
 | 
				
			||||||
		ctx.JSON(http.StatusForbidden, map[string]string{
 | 
							ctx.JSON(http.StatusForbidden, map[string]string{
 | 
				
			||||||
			"message": "Only signed in users are allowed to perform this action.",
 | 
								"message": "Only signed in users are allowed to perform this action.",
 | 
				
			||||||
@@ -470,9 +474,9 @@ func checkProjectBoardChangePermissions(ctx *context.Context) (*models.Project,
 | 
				
			|||||||
		return nil, nil
 | 
							return nil, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	project, err := models.GetProjectByID(ctx.ParamsInt64(":id"))
 | 
						project, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if models.IsErrProjectNotExist(err) {
 | 
							if project_model.IsErrProjectNotExist(err) {
 | 
				
			||||||
			ctx.NotFound("", nil)
 | 
								ctx.NotFound("", nil)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ctx.ServerError("GetProjectByID", err)
 | 
								ctx.ServerError("GetProjectByID", err)
 | 
				
			||||||
@@ -480,7 +484,7 @@ func checkProjectBoardChangePermissions(ctx *context.Context) (*models.Project,
 | 
				
			|||||||
		return nil, nil
 | 
							return nil, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	board, err := models.GetProjectBoard(ctx.ParamsInt64(":boardID"))
 | 
						board, err := project_model.GetBoard(ctx.ParamsInt64(":boardID"))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("GetProjectBoard", err)
 | 
							ctx.ServerError("GetProjectBoard", err)
 | 
				
			||||||
		return nil, nil
 | 
							return nil, nil
 | 
				
			||||||
@@ -519,7 +523,7 @@ func EditProjectBoard(ctx *context.Context) {
 | 
				
			|||||||
		board.Sorting = form.Sorting
 | 
							board.Sorting = form.Sorting
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := models.UpdateProjectBoard(board); err != nil {
 | 
						if err := project_model.UpdateBoard(board); err != nil {
 | 
				
			||||||
		ctx.ServerError("UpdateProjectBoard", err)
 | 
							ctx.ServerError("UpdateProjectBoard", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -536,7 +540,7 @@ func SetDefaultProjectBoard(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := models.SetDefaultBoard(project.ID, board.ID); err != nil {
 | 
						if err := project_model.SetDefaultBoard(project.ID, board.ID); err != nil {
 | 
				
			||||||
		ctx.ServerError("SetDefaultBoard", err)
 | 
							ctx.ServerError("SetDefaultBoard", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -562,9 +566,9 @@ func MoveIssues(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	project, err := models.GetProjectByID(ctx.ParamsInt64(":id"))
 | 
						project, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if models.IsErrProjectNotExist(err) {
 | 
							if project_model.IsErrProjectNotExist(err) {
 | 
				
			||||||
			ctx.NotFound("ProjectNotExist", nil)
 | 
								ctx.NotFound("ProjectNotExist", nil)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ctx.ServerError("GetProjectByID", err)
 | 
								ctx.ServerError("GetProjectByID", err)
 | 
				
			||||||
@@ -576,19 +580,18 @@ func MoveIssues(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var board *models.ProjectBoard
 | 
						var board *project_model.Board
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if ctx.ParamsInt64(":boardID") == 0 {
 | 
						if ctx.ParamsInt64(":boardID") == 0 {
 | 
				
			||||||
		board = &models.ProjectBoard{
 | 
							board = &project_model.Board{
 | 
				
			||||||
			ID:        0,
 | 
								ID:        0,
 | 
				
			||||||
			ProjectID: project.ID,
 | 
								ProjectID: project.ID,
 | 
				
			||||||
			Title:     ctx.Tr("repo.projects.type.uncategorized"),
 | 
								Title:     ctx.Tr("repo.projects.type.uncategorized"),
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		// column
 | 
							board, err = project_model.GetBoard(ctx.ParamsInt64(":boardID"))
 | 
				
			||||||
		board, err = models.GetProjectBoard(ctx.ParamsInt64(":boardID"))
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			if models.IsErrProjectBoardNotExist(err) {
 | 
								if project_model.IsErrProjectBoardNotExist(err) {
 | 
				
			||||||
				ctx.NotFound("ProjectBoardNotExist", nil)
 | 
									ctx.NotFound("ProjectBoardNotExist", nil)
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				ctx.ServerError("GetProjectBoard", err)
 | 
									ctx.ServerError("GetProjectBoard", err)
 | 
				
			||||||
@@ -634,7 +637,7 @@ func MoveIssues(ctx *context.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err = models.MoveIssuesOnProjectBoard(board, sortedIssueIDs); err != nil {
 | 
						if err = project_model.MoveIssuesOnProjectBoard(board, sortedIssueIDs); err != nil {
 | 
				
			||||||
		ctx.ServerError("MoveIssuesOnProjectBoard", err)
 | 
							ctx.ServerError("MoveIssuesOnProjectBoard", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -647,8 +650,43 @@ func MoveIssues(ctx *context.Context) {
 | 
				
			|||||||
// CreateProject renders the generic project creation page
 | 
					// CreateProject renders the generic project creation page
 | 
				
			||||||
func CreateProject(ctx *context.Context) {
 | 
					func CreateProject(ctx *context.Context) {
 | 
				
			||||||
	ctx.Data["Title"] = ctx.Tr("repo.projects.new")
 | 
						ctx.Data["Title"] = ctx.Tr("repo.projects.new")
 | 
				
			||||||
	ctx.Data["ProjectTypes"] = models.GetProjectsConfig()
 | 
						ctx.Data["ProjectTypes"] = project_model.GetProjectsConfig()
 | 
				
			||||||
	ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
 | 
						ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.HTML(http.StatusOK, tplGenericProjectsNew)
 | 
						ctx.HTML(http.StatusOK, tplGenericProjectsNew)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CreateProjectPost creates an individual and/or organization project
 | 
				
			||||||
 | 
					func CreateProjectPost(ctx *context.Context, form forms.UserCreateProjectForm) {
 | 
				
			||||||
 | 
						user := checkContextUser(ctx, form.UID)
 | 
				
			||||||
 | 
						if ctx.Written() {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.Data["ContextUser"] = user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ctx.HasError() {
 | 
				
			||||||
 | 
							ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
 | 
				
			||||||
 | 
							ctx.HTML(http.StatusOK, tplGenericProjectsNew)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						projectType := project_model.TypeIndividual
 | 
				
			||||||
 | 
						if user.IsOrganization() {
 | 
				
			||||||
 | 
							projectType = project_model.TypeOrganization
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := project_model.NewProject(&project_model.Project{
 | 
				
			||||||
 | 
							Title:       form.Title,
 | 
				
			||||||
 | 
							Description: form.Content,
 | 
				
			||||||
 | 
							CreatorID:   user.ID,
 | 
				
			||||||
 | 
							BoardType:   form.BoardType,
 | 
				
			||||||
 | 
							Type:        projectType,
 | 
				
			||||||
 | 
						}); err != nil {
 | 
				
			||||||
 | 
							ctx.ServerError("NewProject", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.Flash.Success(ctx.Tr("repo.projects.create_success", form.Title))
 | 
				
			||||||
 | 
						ctx.Redirect(setting.AppSubURL + "/")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/models/db"
 | 
						"code.gitea.io/gitea/models/db"
 | 
				
			||||||
	"code.gitea.io/gitea/models/organization"
 | 
						"code.gitea.io/gitea/models/organization"
 | 
				
			||||||
 | 
						project_model "code.gitea.io/gitea/models/project"
 | 
				
			||||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
						repo_model "code.gitea.io/gitea/models/repo"
 | 
				
			||||||
	user_model "code.gitea.io/gitea/models/user"
 | 
						user_model "code.gitea.io/gitea/models/user"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
@@ -216,10 +217,10 @@ func Profile(ctx *context.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		total = int(count)
 | 
							total = int(count)
 | 
				
			||||||
	case "projects":
 | 
						case "projects":
 | 
				
			||||||
		ctx.Data["OpenProjects"], _, err = models.GetProjects(models.ProjectSearchOptions{
 | 
							ctx.Data["OpenProjects"], _, err = project_model.GetProjects(project_model.SearchOptions{
 | 
				
			||||||
			Page:     -1,
 | 
								Page:     -1,
 | 
				
			||||||
			IsClosed: util.OptionalBoolFalse,
 | 
								IsClosed: util.OptionalBoolFalse,
 | 
				
			||||||
			Type:     models.ProjectTypeIndividual,
 | 
								Type:     project_model.TypeIndividual,
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			ctx.ServerError("GetProjects", err)
 | 
								ctx.ServerError("GetProjects", err)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,7 @@ import (
 | 
				
			|||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
						project_model "code.gitea.io/gitea/models/project"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/structs"
 | 
						"code.gitea.io/gitea/modules/structs"
 | 
				
			||||||
@@ -499,7 +500,7 @@ func (i IssueLockForm) HasValidReason() bool {
 | 
				
			|||||||
type CreateProjectForm struct {
 | 
					type CreateProjectForm struct {
 | 
				
			||||||
	Title     string `binding:"Required;MaxSize(100)"`
 | 
						Title     string `binding:"Required;MaxSize(100)"`
 | 
				
			||||||
	Content   string
 | 
						Content   string
 | 
				
			||||||
	BoardType models.ProjectBoardType
 | 
						BoardType project_model.BoardType
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UserCreateProjectForm is a from for creating an individual or organization
 | 
					// UserCreateProjectForm is a from for creating an individual or organization
 | 
				
			||||||
@@ -507,7 +508,7 @@ type CreateProjectForm struct {
 | 
				
			|||||||
type UserCreateProjectForm struct {
 | 
					type UserCreateProjectForm struct {
 | 
				
			||||||
	Title     string `binding:"Required;MaxSize(100)"`
 | 
						Title     string `binding:"Required;MaxSize(100)"`
 | 
				
			||||||
	Content   string
 | 
						Content   string
 | 
				
			||||||
	BoardType models.ProjectBoardType
 | 
						BoardType project_model.BoardType
 | 
				
			||||||
	UID       int64 `binding:"Required"`
 | 
						UID       int64 `binding:"Required"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -84,7 +84,7 @@
 | 
				
			|||||||
				<div class="board-column-header df ac sb">
 | 
									<div class="board-column-header df ac sb">
 | 
				
			||||||
					<div class="ui large label board-label py-2">
 | 
										<div class="ui large label board-label py-2">
 | 
				
			||||||
						<div class="ui small circular grey label board-card-cnt">
 | 
											<div class="ui small circular grey label board-card-cnt">
 | 
				
			||||||
							{{len .Issues}}
 | 
												{{.NumIssues}}
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
						{{.Title}}
 | 
											{{.Title}}
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
@@ -175,7 +175,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
				<div class="ui cards board" data-url="{{$.RepoLink}}/projects/{{$.Project.ID}}/{{.ID}}" data-project="{{$.Project.ID}}" data-board="{{.ID}}" id="board_{{.ID}}">
 | 
									<div class="ui cards board" data-url="{{$.RepoLink}}/projects/{{$.Project.ID}}/{{.ID}}" data-project="{{$.Project.ID}}" data-board="{{.ID}}" id="board_{{.ID}}">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					{{ range .Issues }}
 | 
										{{ range (index $.IssuesMap .ID) }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					<!-- start issue card -->
 | 
										<!-- start issue card -->
 | 
				
			||||||
					<div class="card board-card" data-issue="{{.ID}}">
 | 
										<div class="card board-card" data-issue="{{.ID}}">
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user