aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--models/error.go38
-rw-r--r--models/issue.go21
-rw-r--r--models/issue_comment.go9
-rw-r--r--models/issue_project.go181
-rw-r--r--models/project/board.go289
-rw-r--r--models/project/issue.go100
-rw-r--r--models/project/main_test.go23
-rw-r--r--models/project/project.go (renamed from models/project.go)112
-rw-r--r--models/project/project_test.go (renamed from models/project_test.go)22
-rw-r--r--models/project_board.go321
-rw-r--r--models/project_issue.go218
-rw-r--r--models/repo.go5
-rw-r--r--models/statistic.go5
-rw-r--r--routers/web/repo/issue.go19
-rw-r--r--routers/web/repo/projects.go146
-rw-r--r--routers/web/user/profile.go5
-rw-r--r--services/forms/repo_form.go5
-rw-r--r--templates/repo/projects/view.tmpl4
18 files changed, 810 insertions, 713 deletions
diff --git a/models/error.go b/models/error.go
index 50d7175725..8ea2f2f8af 100644
--- a/models/error.go
+++ b/models/error.go
@@ -1063,44 +1063,6 @@ func (err ErrLabelNotExist) Error() string {
return fmt.Sprintf("label does not exist [label_id: %d]", err.LabelID)
}
-// __________ __ __
-// \______ \_______ ____ |__| ____ _____/ |_ ______
-// | ___/\_ __ \/ _ \ | |/ __ \_/ ___\ __\/ ___/
-// | | | | \( <_> ) | \ ___/\ \___| | \___ \
-// |____| |__| \____/\__| |\___ >\___ >__| /____ >
-// \______| \/ \/ \/
-
-// ErrProjectNotExist represents a "ProjectNotExist" kind of error.
-type ErrProjectNotExist struct {
- ID int64
- RepoID int64
-}
-
-// IsErrProjectNotExist checks if an error is a ErrProjectNotExist
-func IsErrProjectNotExist(err error) bool {
- _, ok := err.(ErrProjectNotExist)
- return ok
-}
-
-func (err ErrProjectNotExist) Error() string {
- return fmt.Sprintf("projects does not exist [id: %d]", err.ID)
-}
-
-// ErrProjectBoardNotExist represents a "ProjectBoardNotExist" kind of error.
-type ErrProjectBoardNotExist struct {
- BoardID int64
-}
-
-// IsErrProjectBoardNotExist checks if an error is a ErrProjectBoardNotExist
-func IsErrProjectBoardNotExist(err error) bool {
- _, ok := err.(ErrProjectBoardNotExist)
- return ok
-}
-
-func (err ErrProjectBoardNotExist) Error() string {
- return fmt.Sprintf("project board does not exist [id: %d]", err.BoardID)
-}
-
// _____ .__.__ __
// / \ |__| | ____ _______/ |_ ____ ____ ____
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
diff --git a/models/issue.go b/models/issue.go
index 31f7a0edb0..d2a2b6a329 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -19,6 +19,7 @@ import (
"code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
+ project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
@@ -45,14 +46,14 @@ type Issue struct {
PosterID int64 `xorm:"INDEX"`
Poster *user_model.User `xorm:"-"`
OriginalAuthor string
- OriginalAuthorID int64 `xorm:"index"`
- Title string `xorm:"name"`
- Content string `xorm:"LONGTEXT"`
- RenderedContent string `xorm:"-"`
- Labels []*Label `xorm:"-"`
- MilestoneID int64 `xorm:"INDEX"`
- Milestone *Milestone `xorm:"-"`
- Project *Project `xorm:"-"`
+ OriginalAuthorID int64 `xorm:"index"`
+ Title string `xorm:"name"`
+ Content string `xorm:"LONGTEXT"`
+ RenderedContent string `xorm:"-"`
+ Labels []*Label `xorm:"-"`
+ MilestoneID int64 `xorm:"INDEX"`
+ Milestone *Milestone `xorm:"-"`
+ Project *project_model.Project `xorm:"-"`
Priority int
AssigneeID int64 `xorm:"-"`
Assignee *user_model.User `xorm:"-"`
@@ -2135,7 +2136,7 @@ func deleteIssue(ctx context.Context, issue *Issue) error {
&IssueWatch{},
&Stopwatch{},
&TrackedTime{},
- &ProjectIssue{},
+ &project_model.ProjectIssue{},
&repo_model.Attachment{},
&PullRequest{},
); err != nil {
@@ -2469,7 +2470,7 @@ func deleteIssuesByRepoID(sess db.Engine, repoID int64) (attachmentPaths []strin
}
if _, err = sess.In("issue_id", deleteCond).
- Delete(&ProjectIssue{}); err != nil {
+ Delete(&project_model.ProjectIssue{}); err != nil {
return
}
diff --git a/models/issue_comment.go b/models/issue_comment.go
index 7927b0037b..500ed6d038 100644
--- a/models/issue_comment.go
+++ b/models/issue_comment.go
@@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
+ project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
@@ -204,8 +205,8 @@ type Comment struct {
RemovedLabels []*Label `xorm:"-"`
OldProjectID int64
ProjectID int64
- OldProject *Project `xorm:"-"`
- Project *Project `xorm:"-"`
+ OldProject *project_model.Project `xorm:"-"`
+ Project *project_model.Project `xorm:"-"`
OldMilestoneID int64
MilestoneID int64
OldMilestone *Milestone `xorm:"-"`
@@ -469,7 +470,7 @@ func (c *Comment) LoadLabel() error {
// LoadProject if comment.Type is CommentTypeProject, then load project.
func (c *Comment) LoadProject() error {
if c.OldProjectID > 0 {
- var oldProject Project
+ var oldProject project_model.Project
has, err := db.GetEngine(db.DefaultContext).ID(c.OldProjectID).Get(&oldProject)
if err != nil {
return err
@@ -479,7 +480,7 @@ func (c *Comment) LoadProject() error {
}
if c.ProjectID > 0 {
- var project Project
+ var project project_model.Project
has, err := db.GetEngine(db.DefaultContext).ID(c.ProjectID).Get(&project)
if err != nil {
return err
diff --git a/models/issue_project.go b/models/issue_project.go
new file mode 100644
index 0000000000..526ac95152
--- /dev/null
+++ b/models/issue_project.go
@@ -0,0 +1,181 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package models
+
+import (
+ "context"
+ "fmt"
+
+ "code.gitea.io/gitea/models/db"
+ project_model "code.gitea.io/gitea/models/project"
+ user_model "code.gitea.io/gitea/models/user"
+)
+
+// LoadProject load the project the issue was assigned to
+func (i *Issue) LoadProject() (err error) {
+ return i.loadProject(db.GetEngine(db.DefaultContext))
+}
+
+func (i *Issue) loadProject(e db.Engine) (err error) {
+ if i.Project == nil {
+ var p project_model.Project
+ if _, err = e.Table("project").
+ Join("INNER", "project_issue", "project.id=project_issue.project_id").
+ Where("project_issue.issue_id = ?", i.ID).
+ Get(&p); err != nil {
+ return err
+ }
+ i.Project = &p
+ }
+ return
+}
+
+// ProjectID return project id if issue was assigned to one
+func (i *Issue) ProjectID() int64 {
+ return i.projectID(db.GetEngine(db.DefaultContext))
+}
+
+func (i *Issue) projectID(e db.Engine) int64 {
+ var ip project_model.ProjectIssue
+ has, err := e.Where("issue_id=?", i.ID).Get(&ip)
+ if err != nil || !has {
+ return 0
+ }
+ return ip.ProjectID
+}
+
+// ProjectBoardID return project board id if issue was assigned to one
+func (i *Issue) ProjectBoardID() int64 {
+ return i.projectBoardID(db.GetEngine(db.DefaultContext))
+}
+
+func (i *Issue) projectBoardID(e db.Engine) int64 {
+ var ip project_model.ProjectIssue
+ has, err := e.Where("issue_id=?", i.ID).Get(&ip)
+ if err != nil || !has {
+ return 0
+ }
+ return ip.ProjectBoardID
+}
+
+// LoadIssuesFromBoard load issues assigned to this board
+func LoadIssuesFromBoard(b *project_model.Board) (IssueList, error) {
+ issueList := make([]*Issue, 0, 10)
+
+ if b.ID != 0 {
+ issues, err := Issues(&IssuesOptions{
+ ProjectBoardID: b.ID,
+ ProjectID: b.ProjectID,
+ })
+ if err != nil {
+ return nil, err
+ }
+ issueList = issues
+ }
+
+ if b.Default {
+ issues, err := Issues(&IssuesOptions{
+ ProjectBoardID: -1, // Issues without ProjectBoardID
+ ProjectID: b.ProjectID,
+ })
+ if err != nil {
+ return nil, err
+ }
+ issueList = append(issueList, issues...)
+ }
+
+ if err := IssueList(issueList).LoadComments(); err != nil {
+ return nil, err
+ }
+
+ return issueList, nil
+}
+
+// LoadIssuesFromBoardList load issues assigned to the boards
+func LoadIssuesFromBoardList(bs project_model.BoardList) (map[int64]IssueList, error) {
+ issuesMap := make(map[int64]IssueList, len(bs))
+ for i := range bs {
+ il, err := LoadIssuesFromBoard(bs[i])
+ if err != nil {
+ return nil, err
+ }
+ issuesMap[bs[i].ID] = il
+ }
+ return issuesMap, nil
+}
+
+// ChangeProjectAssign changes the project associated with an issue
+func ChangeProjectAssign(issue *Issue, doer *user_model.User, newProjectID int64) error {
+ ctx, committer, err := db.TxContext()
+ if err != nil {
+ return err
+ }
+ defer committer.Close()
+
+ if err := addUpdateIssueProject(ctx, issue, doer, newProjectID); err != nil {
+ return err
+ }
+
+ return committer.Commit()
+}
+
+func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
+ e := db.GetEngine(ctx)
+ oldProjectID := issue.projectID(e)
+
+ if _, err := e.Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
+ return err
+ }
+
+ if err := issue.loadRepo(ctx); err != nil {
+ return err
+ }
+
+ if oldProjectID > 0 || newProjectID > 0 {
+ if _, err := createComment(ctx, &CreateCommentOptions{
+ Type: CommentTypeProject,
+ Doer: doer,
+ Repo: issue.Repo,
+ Issue: issue,
+ OldProjectID: oldProjectID,
+ ProjectID: newProjectID,
+ }); err != nil {
+ return err
+ }
+ }
+
+ _, err := e.Insert(&project_model.ProjectIssue{
+ IssueID: issue.ID,
+ ProjectID: newProjectID,
+ })
+ return err
+}
+
+// MoveIssueAcrossProjectBoards move a card from one board to another
+func MoveIssueAcrossProjectBoards(issue *Issue, board *project_model.Board) error {
+ ctx, committer, err := db.TxContext()
+ if err != nil {
+ return err
+ }
+ defer committer.Close()
+ sess := db.GetEngine(ctx)
+
+ var pis project_model.ProjectIssue
+ has, err := sess.Where("issue_id=?", issue.ID).Get(&pis)
+ if err != nil {
+ return err
+ }
+
+ if !has {
+ return fmt.Errorf("issue has to be added to a project first")
+ }
+
+ pis.ProjectBoardID = board.ID
+ if _, err := sess.ID(pis.ID).Cols("project_board_id").Update(&pis); err != nil {
+ return err
+ }
+
+ return committer.Commit()
+}
diff --git a/models/project/board.go b/models/project/board.go
new file mode 100644
index 0000000000..f770a18f59
--- /dev/null
+++ b/models/project/board.go
@@ -0,0 +1,289 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package project
+
+import (
+ "context"
+ "fmt"
+ "regexp"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/builder"
+)
+
+type (
+ // BoardType is used to represent a project board type
+ BoardType uint8
+
+ // BoardList is a list of all project boards in a repository
+ BoardList []*Board
+)
+
+const (
+ // BoardTypeNone is a project board type that has no predefined columns
+ BoardTypeNone BoardType = iota
+
+ // BoardTypeBasicKanban is a project board type that has basic predefined columns
+ BoardTypeBasicKanban
+
+ // BoardTypeBugTriage is a project board type that has predefined columns suited to hunting down bugs
+ BoardTypeBugTriage
+)
+
+// BoardColorPattern is a regexp witch can validate BoardColor
+var BoardColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$")
+
+// Board is used to represent boards on a project
+type Board struct {
+ ID int64 `xorm:"pk autoincr"`
+ Title string
+ Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board
+ Sorting int8 `xorm:"NOT NULL DEFAULT 0"`
+ Color string `xorm:"VARCHAR(7)"`
+
+ ProjectID int64 `xorm:"INDEX NOT NULL"`
+ CreatorID int64 `xorm:"NOT NULL"`
+
+ CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
+ UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
+}
+
+// TableName return the real table name
+func (Board) TableName() string {
+ return "project_board"
+}
+
+// NumIssues return counter of all issues assigned to the board
+func (b *Board) NumIssues() int {
+ c, err := db.GetEngine(db.DefaultContext).Table("project_issue").
+ Where("project_id=?", b.ProjectID).
+ And("project_board_id=?", b.ID).
+ GroupBy("issue_id").
+ Cols("issue_id").
+ Count()
+ if err != nil {
+ return 0
+ }
+ return int(c)
+}
+
+func init() {
+ db.RegisterModel(new(Board))
+}
+
+// IsBoardTypeValid checks if the project board type is valid
+func IsBoardTypeValid(p BoardType) bool {
+ switch p {
+ case BoardTypeNone, BoardTypeBasicKanban, BoardTypeBugTriage:
+ return true
+ default:
+ return false
+ }
+}
+
+func createBoardsForProjectsType(ctx context.Context, project *Project) error {
+ var items []string
+
+ switch project.BoardType {
+
+ case BoardTypeBugTriage:
+ items = setting.Project.ProjectBoardBugTriageType
+
+ case BoardTypeBasicKanban:
+ items = setting.Project.ProjectBoardBasicKanbanType
+
+ case BoardTypeNone:
+ fallthrough
+ default:
+ return nil
+ }
+
+ if len(items) == 0 {
+ return nil
+ }
+
+ boards := make([]Board, 0, len(items))
+
+ for _, v := range items {
+ boards = append(boards, Board{
+ CreatedUnix: timeutil.TimeStampNow(),
+ CreatorID: project.CreatorID,
+ Title: v,
+ ProjectID: project.ID,
+ })
+ }
+
+ return db.Insert(ctx, boards)
+}
+
+// NewBoard adds a new project board to a given project
+func NewBoard(board *Board) error {
+ if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) {
+ return fmt.Errorf("bad color code: %s", board.Color)
+ }
+
+ _, err := db.GetEngine(db.DefaultContext).Insert(board)
+ return err
+}
+
+// DeleteBoardByID removes all issues references to the project board.
+func DeleteBoardByID(boardID int64) error {
+ ctx, committer, err := db.TxContext()
+ if err != nil {
+ return err
+ }
+ defer committer.Close()
+
+ if err := deleteBoardByID(ctx, boardID); err != nil {
+ return err
+ }
+
+ return committer.Commit()
+}
+
+func deleteBoardByID(ctx context.Context, boardID int64) error {
+ e := db.GetEngine(ctx)
+ board, err := getBoard(e, boardID)
+ if err != nil {
+ if IsErrProjectBoardNotExist(err) {
+ return nil
+ }
+
+ return err
+ }
+
+ if err = board.removeIssues(e); err != nil {
+ return err
+ }
+
+ if _, err := e.ID(board.ID).Delete(board); err != nil {
+ return err
+ }
+ return nil
+}
+
+func deleteBoardByProjectID(e db.Engine, projectID int64) error {
+ _, err := e.Where("project_id=?", projectID).Delete(&Board{})
+ return err
+}
+
+// GetBoard fetches the current board of a project
+func GetBoard(boardID int64) (*Board, error) {
+ return getBoard(db.GetEngine(db.DefaultContext), boardID)
+}
+
+func getBoard(e db.Engine, boardID int64) (*Board, error) {
+ board := new(Board)
+
+ has, err := e.ID(boardID).Get(board)
+ if err != nil {
+ return nil, err
+ } else if !has {
+ return nil, ErrProjectBoardNotExist{BoardID: boardID}
+ }
+
+ return board, nil
+}
+
+// UpdateBoard updates a project board
+func UpdateBoard(board *Board) error {
+ return updateBoard(db.GetEngine(db.DefaultContext), board)
+}
+
+func updateBoard(e db.Engine, board *Board) error {
+ var fieldToUpdate []string
+
+ if board.Sorting != 0 {
+ fieldToUpdate = append(fieldToUpdate, "sorting")
+ }
+
+ if board.Title != "" {
+ fieldToUpdate = append(fieldToUpdate, "title")
+ }
+
+ if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) {
+ return fmt.Errorf("bad color code: %s", board.Color)
+ }
+ fieldToUpdate = append(fieldToUpdate, "color")
+
+ _, err := e.ID(board.ID).Cols(fieldToUpdate...).Update(board)
+
+ return err
+}
+
+// GetBoards fetches all boards related to a project
+// if no default board set, first board is a temporary "Uncategorized" board
+func GetBoards(projectID int64) (BoardList, error) {
+ return getBoards(db.GetEngine(db.DefaultContext), projectID)
+}
+
+func getBoards(e db.Engine, projectID int64) ([]*Board, error) {
+ boards := make([]*Board, 0, 5)
+
+ if err := e.Where("project_id=? AND `default`=?", projectID, false).OrderBy("Sorting").Find(&boards); err != nil {
+ return nil, err
+ }
+
+ defaultB, err := getDefaultBoard(e, projectID)
+ if err != nil {
+ return nil, err
+ }
+
+ return append([]*Board{defaultB}, boards...), nil
+}
+
+// getDefaultBoard return default board and create a dummy if none exist
+func getDefaultBoard(e db.Engine, projectID int64) (*Board, error) {
+ var board Board
+ exist, err := e.Where("project_id=? AND `default`=?", projectID, true).Get(&board)
+ if err != nil {
+ return nil, err
+ }
+ if exist {
+ return &board, nil
+ }
+
+ // represents a board for issues not assigned to one
+ return &Board{
+ ProjectID: projectID,
+ Title: "Uncategorized",
+ Default: true,
+ }, nil
+}
+
+// SetDefaultBoard represents a board for issues not assigned to one
+// if boardID is 0 unset default
+func SetDefaultBoard(projectID, boardID int64) error {
+ _, err := db.GetEngine(db.DefaultContext).Where(builder.Eq{
+ "project_id": projectID,
+ "`default`": true,
+ }).Cols("`default`").Update(&Board{Default: false})
+ if err != nil {
+ return err
+ }
+
+ if boardID > 0 {
+ _, err = db.GetEngine(db.DefaultContext).ID(boardID).Where(builder.Eq{"project_id": projectID}).
+ Cols("`default`").Update(&Board{Default: true})
+ }
+
+ return err
+}
+
+// UpdateBoardSorting update project board sorting
+func UpdateBoardSorting(bs BoardList) error {
+ for i := range bs {
+ _, err := db.GetEngine(db.DefaultContext).ID(bs[i].ID).Cols(
+ "sorting",
+ ).Update(bs[i])
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/models/project/issue.go b/models/project/issue.go
new file mode 100644
index 0000000000..0976185c49
--- /dev/null
+++ b/models/project/issue.go
@@ -0,0 +1,100 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package project
+
+import (
+ "context"
+ "fmt"
+
+ "code.gitea.io/gitea/models/db"
+)
+
+// ProjectIssue saves relation from issue to a project
+type ProjectIssue struct { //revive:disable-line:exported
+ ID int64 `xorm:"pk autoincr"`
+ IssueID int64 `xorm:"INDEX"`
+ ProjectID int64 `xorm:"INDEX"`
+
+ // If 0, then it has not been added to a specific board in the project
+ ProjectBoardID int64 `xorm:"INDEX"`
+}
+
+func init() {
+ db.RegisterModel(new(ProjectIssue))
+}
+
+func deleteProjectIssuesByProjectID(e db.Engine, projectID int64) error {
+ _, err := e.Where("project_id=?", projectID).Delete(&ProjectIssue{})
+ return err
+}
+
+// NumIssues return counter of all issues assigned to a project
+func (p *Project) NumIssues() int {
+ c, err := db.GetEngine(db.DefaultContext).Table("project_issue").
+ Where("project_id=?", p.ID).
+ GroupBy("issue_id").
+ Cols("issue_id").
+ Count()
+ if err != nil {
+ return 0
+ }
+ return int(c)
+}
+
+// NumClosedIssues return counter of closed issues assigned to a project
+func (p *Project) NumClosedIssues() int {
+ c, err := db.GetEngine(db.DefaultContext).Table("project_issue").
+ Join("INNER", "issue", "project_issue.issue_id=issue.id").
+ Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true).
+ Cols("issue_id").
+ Count()
+ if err != nil {
+ return 0
+ }
+ return int(c)
+}
+
+// NumOpenIssues return counter of open issues assigned to a project
+func (p *Project) NumOpenIssues() int {
+ c, err := db.GetEngine(db.DefaultContext).Table("project_issue").
+ Join("INNER", "issue", "project_issue.issue_id=issue.id").
+ Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false).Count("issue.id")
+ if err != nil {
+ return 0
+ }
+ return int(c)
+}
+
+// MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column
+func MoveIssuesOnProjectBoard(board *Board, sortedIssueIDs map[int64]int64) error {
+ return db.WithTx(func(ctx context.Context) error {
+ sess := db.GetEngine(ctx)
+
+ issueIDs := make([]int64, 0, len(sortedIssueIDs))
+ for _, issueID := range sortedIssueIDs {
+ issueIDs = append(issueIDs, issueID)
+ }
+ count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", board.ProjectID).In("issue_id", issueIDs).Count()
+ if err != nil {
+ return err
+ }
+ if int(count) != len(sortedIssueIDs) {
+ return fmt.Errorf("all issues have to be added to a project first")
+ }
+
+ for sorting, issueID := range sortedIssueIDs {
+ _, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", board.ID, sorting, issueID)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+}
+
+func (pb *Board) removeIssues(e db.Engine) error {
+ _, err := e.Exec("UPDATE `project_issue` SET project_board_id = 0 WHERE project_board_id = ? ", pb.ID)
+ return err
+}
diff --git a/models/project/main_test.go b/models/project/main_test.go
new file mode 100644
index 0000000000..5296a0f40e
--- /dev/null
+++ b/models/project/main_test.go
@@ -0,0 +1,23 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package project
+
+import (
+ "path/filepath"
+ "testing"
+
+ "code.gitea.io/gitea/models/unittest"
+
+ _ "code.gitea.io/gitea/models/repo"
+)
+
+func TestMain(m *testing.M) {
+ unittest.MainTest(m, filepath.Join("..", ".."),
+ "project.yml",
+ "project_board.yml",
+ "project_issue.yml",
+ "repository.yml",
+ )
+}
diff --git a/models/project.go b/models/project/project.go
index e6a650674b..a639879e78 100644
--- a/models/project.go
+++ b/models/project/project.go
@@ -2,9 +2,10 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
-package models
+package project
import (
+ "context"
"errors"
"fmt"
@@ -19,25 +20,56 @@ import (
type (
// ProjectsConfig is used to identify the type of board that is being created
ProjectsConfig struct {
- BoardType ProjectBoardType
+ BoardType BoardType
Translation string
}
- // ProjectType is used to identify the type of project in question and ownership
- ProjectType uint8
+ // Type is used to identify the type of project in question and ownership
+ Type uint8
)
const (
- // ProjectTypeIndividual is a type of project board that is owned by an individual
- ProjectTypeIndividual ProjectType = iota + 1
+ // TypeIndividual is a type of project board that is owned by an individual
+ TypeIndividual Type = iota + 1
- // ProjectTypeRepository is a project that is tied to a repository
- ProjectTypeRepository
+ // TypeRepository is a project that is tied to a repository
+ TypeRepository
- // ProjectTypeOrganization is a project that is tied to an organisation
- ProjectTypeOrganization
+ // TypeOrganization is a project that is tied to an organisation
+ TypeOrganization
)
+// ErrProjectNotExist represents a "ProjectNotExist" kind of error.
+type ErrProjectNotExist struct {
+ ID int64
+ RepoID int64
+}
+
+// IsErrProjectNotExist checks if an error is a ErrProjectNotExist
+func IsErrProjectNotExist(err error) bool {
+ _, ok := err.(ErrProjectNotExist)
+ return ok
+}
+
+func (err ErrProjectNotExist) Error() string {
+ return fmt.Sprintf("projects does not exist [id: %d]", err.ID)
+}
+
+// ErrProjectBoardNotExist represents a "ProjectBoardNotExist" kind of error.
+type ErrProjectBoardNotExist struct {
+ BoardID int64
+}
+
+// IsErrProjectBoardNotExist checks if an error is a ErrProjectBoardNotExist
+func IsErrProjectBoardNotExist(err error) bool {
+ _, ok := err.(ErrProjectBoardNotExist)
+ return ok
+}
+
+func (err ErrProjectBoardNotExist) Error() string {
+ return fmt.Sprintf("project board does not exist [id: %d]", err.BoardID)
+}
+
// Project represents a project board
type Project struct {
ID int64 `xorm:"pk autoincr"`
@@ -46,8 +78,8 @@ type Project struct {
RepoID int64 `xorm:"INDEX"`
CreatorID int64 `xorm:"NOT NULL"`
IsClosed bool `xorm:"INDEX"`
- BoardType ProjectBoardType
- Type ProjectType
+ BoardType BoardType
+ Type Type
RenderedContent string `xorm:"-"`
@@ -63,37 +95,39 @@ func init() {
// GetProjectsConfig retrieves the types of configurations projects could have
func GetProjectsConfig() []ProjectsConfig {
return []ProjectsConfig{
- {ProjectBoardTypeNone, "repo.projects.type.none"},
- {ProjectBoardTypeBasicKanban, "repo.projects.type.basic_kanban"},
- {ProjectBoardTypeBugTriage, "repo.projects.type.bug_triage"},
+ {BoardTypeNone, "repo.projects.type.none"},
+ {BoardTypeBasicKanban, "repo.projects.type.basic_kanban"},
+ {BoardTypeBugTriage, "repo.projects.type.bug_triage"},
}
}
-// IsProjectTypeValid checks if a project type is valid
-func IsProjectTypeValid(p ProjectType) bool {
+// IsTypeValid checks if a project type is valid
+func IsTypeValid(p Type) bool {
switch p {
- case ProjectTypeRepository:
+ case TypeRepository:
return true
default:
return false
}
}
-// ProjectSearchOptions are options for GetProjects
-type ProjectSearchOptions struct {
+// SearchOptions are options for GetProjects
+type SearchOptions struct {
RepoID int64
Page int
IsClosed util.OptionalBool
SortType string
- Type ProjectType
+ Type Type
}
// GetProjects returns a list of all projects that have been created in the repository
-func GetProjects(opts ProjectSearchOptions) ([]*Project, int64, error) {
- return getProjects(db.GetEngine(db.DefaultContext), opts)
+func GetProjects(opts SearchOptions) ([]*Project, int64, error) {
+ return GetProjectsCtx(db.DefaultContext, opts)
}
-func getProjects(e db.Engine, opts ProjectSearchOptions) ([]*Project, int64, error) {
+// GetProjectsCtx returns a list of all projects that have been created in the repository
+func GetProjectsCtx(ctx context.Context, opts SearchOptions) ([]*Project, int64, error) {
+ e := db.GetEngine(ctx)
projects := make([]*Project, 0, setting.UI.IssuePagingNum)
var cond builder.Cond = builder.Eq{"repo_id": opts.RepoID}
@@ -135,11 +169,11 @@ func getProjects(e db.Engine, opts ProjectSearchOptions) ([]*Project, int64, err
// NewProject creates a new Project
func NewProject(p *Project) error {
- if !IsProjectBoardTypeValid(p.BoardType) {
- p.BoardType = ProjectBoardTypeNone
+ if !IsBoardTypeValid(p.BoardType) {
+ p.BoardType = BoardTypeNone
}
- if !IsProjectTypeValid(p.Type) {
+ if !IsTypeValid(p.Type) {
return errors.New("project type is not valid")
}
@@ -157,7 +191,7 @@ func NewProject(p *Project) error {
return err
}
- if err := createBoardsForProjectsType(db.GetEngine(ctx), p); err != nil {
+ if err := createBoardsForProjectsType(ctx, p); err != nil {
return err
}
@@ -200,7 +234,7 @@ func updateRepositoryProjectCount(e db.Engine, repoID int64) error {
builder.Eq{
"`num_projects`": builder.Select("count(*)").From("`project`").
Where(builder.Eq{"`project`.`repo_id`": repoID}.
- And(builder.Eq{"`project`.`type`": ProjectTypeRepository})),
+ And(builder.Eq{"`project`.`type`": TypeRepository})),
}).From("`repository`").Where(builder.Eq{"id": repoID})); err != nil {
return err
}
@@ -209,7 +243,7 @@ func updateRepositoryProjectCount(e db.Engine, repoID int64) error {
builder.Eq{
"`num_closed_projects`": builder.Select("count(*)").From("`project`").
Where(builder.Eq{"`project`.`repo_id`": repoID}.
- And(builder.Eq{"`project`.`type`": ProjectTypeRepository}).
+ And(builder.Eq{"`project`.`type`": TypeRepository}).
And(builder.Eq{"`project`.`is_closed`": true})),
}).From("`repository`").Where(builder.Eq{"id": repoID})); err != nil {
return err
@@ -224,18 +258,17 @@ func ChangeProjectStatusByRepoIDAndID(repoID, projectID int64, isClosed bool) er
return err
}
defer committer.Close()
- sess := db.GetEngine(ctx)
p := new(Project)
- has, err := sess.ID(projectID).Where("repo_id = ?", repoID).Get(p)
+ has, err := db.GetEngine(ctx).ID(projectID).Where("repo_id = ?", repoID).Get(p)
if err != nil {
return err
} else if !has {
return ErrProjectNotExist{ID: projectID, RepoID: repoID}
}
- if err := changeProjectStatus(sess, p, isClosed); err != nil {
+ if err := changeProjectStatus(ctx, p, isClosed); err != nil {
return err
}
@@ -250,16 +283,17 @@ func ChangeProjectStatus(p *Project, isClosed bool) error {
}
defer committer.Close()
- if err := changeProjectStatus(db.GetEngine(ctx), p, isClosed); err != nil {
+ if err := changeProjectStatus(ctx, p, isClosed); err != nil {
return err
}
return committer.Commit()
}
-func changeProjectStatus(e db.Engine, p *Project, isClosed bool) error {
+func changeProjectStatus(ctx context.Context, p *Project, isClosed bool) error {
p.IsClosed = isClosed
p.ClosedDateUnix = timeutil.TimeStampNow()
+ e := db.GetEngine(ctx)
count, err := e.ID(p.ID).Where("repo_id = ? AND is_closed = ?", p.RepoID, !isClosed).Cols("is_closed", "closed_date_unix").Update(p)
if err != nil {
return err
@@ -279,14 +313,16 @@ func DeleteProjectByID(id int64) error {
}
defer committer.Close()
- if err := deleteProjectByID(db.GetEngine(ctx), id); err != nil {
+ if err := DeleteProjectByIDCtx(ctx, id); err != nil {
return err
}
return committer.Commit()
}
-func deleteProjectByID(e db.Engine, id int64) error {
+// DeleteProjectByIDCtx deletes a project from a repository.
+func DeleteProjectByIDCtx(ctx context.Context, id int64) error {
+ e := db.GetEngine(ctx)
p, err := getProjectByID(e, id)
if err != nil {
if IsErrProjectNotExist(err) {
@@ -299,7 +335,7 @@ func deleteProjectByID(e db.Engine, id int64) error {
return err
}
- if err := deleteProjectBoardByProjectID(e, id); err != nil {
+ if err := deleteBoardByProjectID(e, id); err != nil {
return err
}
diff --git a/models/project_test.go b/models/project/project_test.go
index 70dabb7674..211a890874 100644
--- a/models/project_test.go
+++ b/models/project/project_test.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
-package models
+package project
import (
"testing"
@@ -14,33 +14,33 @@ import (
)
func TestIsProjectTypeValid(t *testing.T) {
- const UnknownType ProjectType = 15
+ const UnknownType Type = 15
cases := []struct {
- typ ProjectType
+ typ Type
valid bool
}{
- {ProjectTypeIndividual, false},
- {ProjectTypeRepository, true},
- {ProjectTypeOrganization, false},
+ {TypeIndividual, false},
+ {TypeRepository, true},
+ {TypeOrganization, false},
{UnknownType, false},
}
for _, v := range cases {
- assert.Equal(t, v.valid, IsProjectTypeValid(v.typ))
+ assert.Equal(t, v.valid, IsTypeValid(v.typ))
}
}
func TestGetProjects(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
- projects, _, err := GetProjects(ProjectSearchOptions{RepoID: 1})
+ projects, _, err := GetProjects(SearchOptions{RepoID: 1})
assert.NoError(t, err)
// 1 value for this repo exists in the fixtures
assert.Len(t, projects, 1)
- projects, _, err = GetProjects(ProjectSearchOptions{RepoID: 3})
+ projects, _, err = GetProjects(SearchOptions{RepoID: 3})
assert.NoError(t, err)
// 1 value for this repo exists in the fixtures
@@ -51,8 +51,8 @@ func TestProject(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
project := &Project{
- Type: ProjectTypeRepository,
- BoardType: ProjectBoardTypeBasicKanban,
+ Type: TypeRepository,
+ BoardType: BoardTypeBasicKanban,
Title: "New Project",
RepoID: 1,
CreatedUnix: timeutil.TimeStampNow(),
diff --git a/models/project_board.go b/models/project_board.go
deleted file mode 100644
index d40cfd06f0..0000000000
--- a/models/project_board.go
+++ /dev/null
@@ -1,321 +0,0 @@
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package models
-
-import (
- "fmt"
- "regexp"
-
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/timeutil"
-
- "xorm.io/builder"
-)
-
-type (
- // ProjectBoardType is used to represent a project board type
- ProjectBoardType uint8
-
- // ProjectBoardList is a list of all project boards in a repository
- ProjectBoardList []*ProjectBoard
-)
-
-const (
- // ProjectBoardTypeNone is a project board type that has no predefined columns
- ProjectBoardTypeNone ProjectBoardType = iota
-
- // ProjectBoardTypeBasicKanban is a project board type that has basic predefined columns
- ProjectBoardTypeBasicKanban
-
- // ProjectBoardTypeBugTriage is a project board type that has predefined columns suited to hunting down bugs
- ProjectBoardTypeBugTriage
-)
-
-// BoardColorPattern is a regexp witch can validate BoardColor
-var BoardColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$")
-
-// ProjectBoard is used to represent boards on a project
-type ProjectBoard struct {
- ID int64 `xorm:"pk autoincr"`
- Title string
- Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board
- Sorting int8 `xorm:"NOT NULL DEFAULT 0"`
- Color string `xorm:"VARCHAR(7)"`
-
- ProjectID int64 `xorm:"INDEX NOT NULL"`
- CreatorID int64 `xorm:"NOT NULL"`
-
- CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
- UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
-
- Issues []*Issue `xorm:"-"`
-}
-
-func init() {
- db.RegisterModel(new(ProjectBoard))
-}
-
-// IsProjectBoardTypeValid checks if the project board type is valid
-func IsProjectBoardTypeValid(p ProjectBoardType) bool {
- switch p {
- case ProjectBoardTypeNone, ProjectBoardTypeBasicKanban, ProjectBoardTypeBugTriage:
- return true
- default:
- return false
- }
-}
-
-func createBoardsForProjectsType(sess db.Engine, project *Project) error {
- var items []string
-
- switch project.BoardType {
-
- case ProjectBoardTypeBugTriage:
- items = setting.Project.ProjectBoardBugTriageType
-
- case ProjectBoardTypeBasicKanban:
- items = setting.Project.ProjectBoardBasicKanbanType
-
- case ProjectBoardTypeNone:
- fallthrough
- default:
- return nil
- }
-
- if len(items) == 0 {
- return nil
- }
-
- boards := make([]ProjectBoard, 0, len(items))
-
- for _, v := range items {
- boards = append(boards, ProjectBoard{
- CreatedUnix: timeutil.TimeStampNow(),
- CreatorID: project.CreatorID,
- Title: v,
- ProjectID: project.ID,
- })
- }
-
- _, err := sess.Insert(boards)
- return err
-}
-
-// NewProjectBoard adds a new project board to a given project
-func NewProjectBoard(board *ProjectBoard) error {
- if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) {
- return fmt.Errorf("bad color code: %s", board.Color)
- }
-
- _, err := db.GetEngine(db.DefaultContext).Insert(board)
- return err
-}
-
-// DeleteProjectBoardByID removes all issues references to the project board.
-func DeleteProjectBoardByID(boardID int64) error {
- ctx, committer, err := db.TxContext()
- if err != nil {
- return err
- }
- defer committer.Close()
-
- if err := deleteProjectBoardByID(db.GetEngine(ctx), boardID); err != nil {
- return err
- }
-
- return committer.Commit()
-}
-
-func deleteProjectBoardByID(e db.Engine, boardID int64) error {
- board, err := getProjectBoard(e, boardID)
- if err != nil {
- if IsErrProjectBoardNotExist(err) {
- return nil
- }
-
- return err
- }
-
- if err = board.removeIssues(e); err != nil {
- return err
- }
-
- if _, err := e.ID(board.ID).Delete(board); err != nil {
- return err
- }
- return nil
-}
-
-func deleteProjectBoardByProjectID(e db.Engine, projectID int64) error {
- _, err := e.Where("project_id=?", projectID).Delete(&ProjectBoard{})
- return err
-}
-
-// GetProjectBoard fetches the current board of a project
-func GetProjectBoard(boardID int64) (*ProjectBoard, error) {
- return getProjectBoard(db.GetEngine(db.DefaultContext), boardID)
-}
-
-func getProjectBoard(e db.Engine, boardID int64) (*ProjectBoard, error) {
- board := new(ProjectBoard)
-
- has, err := e.ID(boardID).Get(board)
- if err != nil {
- return nil, err
- } else if !has {
- return nil, ErrProjectBoardNotExist{BoardID: boardID}
- }
-
- return board, nil
-}
-
-// UpdateProjectBoard updates a project board
-func UpdateProjectBoard(board *ProjectBoard) error {
- return updateProjectBoard(db.GetEngine(db.DefaultContext), board)
-}
-
-func updateProjectBoard(e db.Engine, board *ProjectBoard) error {
- var fieldToUpdate []string
-
- if board.Sorting != 0 {
- fieldToUpdate = append(fieldToUpdate, "sorting")
- }
-
- if board.Title != "" {
- fieldToUpdate = append(fieldToUpdate, "title")
- }
-
- if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) {
- return fmt.Errorf("bad color code: %s", board.Color)
- }
- fieldToUpdate = append(fieldToUpdate, "color")
-
- _, err := e.ID(board.ID).Cols(fieldToUpdate...).Update(board)
-
- return err
-}
-
-// GetProjectBoards fetches all boards related to a project
-// if no default board set, first board is a temporary "Uncategorized" board
-func GetProjectBoards(projectID int64) (ProjectBoardList, error) {
- return getProjectBoards(db.GetEngine(db.DefaultContext), projectID)
-}
-
-func getProjectBoards(e db.Engine, projectID int64) ([]*ProjectBoard, error) {
- boards := make([]*ProjectBoard, 0, 5)
-
- if err := e.Where("project_id=? AND `default`=?", projectID, false).OrderBy("Sorting").Find(&boards); err != nil {
- return nil, err
- }
-
- defaultB, err := getDefaultBoard(e, projectID)
- if err != nil {
- return nil, err
- }
-
- return append([]*ProjectBoard{defaultB}, boards...), nil
-}
-
-// getDefaultBoard return default board and create a dummy if none exist
-func getDefaultBoard(e db.Engine, projectID int64) (*ProjectBoard, error) {
- var board ProjectBoard
- exist, err := e.Where("project_id=? AND `default`=?", projectID, true).Get(&board)
- if err != nil {
- return nil, err
- }
- if exist {
- return &board, nil
- }
-
- // represents a board for issues not assigned to one
- return &ProjectBoard{
- ProjectID: projectID,
- Title: "Uncategorized",
- Default: true,
- }, nil
-}
-
-// SetDefaultBoard represents a board for issues not assigned to one
-// if boardID is 0 unset default
-func SetDefaultBoard(projectID, boardID int64) error {
- _, err := db.GetEngine(db.DefaultContext).Where(builder.Eq{
- "project_id": projectID,
- "`default`": true,
- }).Cols("`default`").Update(&ProjectBoard{Default: false})
- if err != nil {
- return err
- }
-
- if boardID > 0 {
- _, err = db.GetEngine(db.DefaultContext).ID(boardID).Where(builder.Eq{"project_id": projectID}).
- Cols("`default`").Update(&ProjectBoard{Default: true})
- }
-
- return err
-}
-
-// LoadIssues load issues assigned to this board
-func (b *ProjectBoard) LoadIssues() (IssueList, error) {
- issueList := make([]*Issue, 0, 10)
-
- if b.ID != 0 {
- issues, err := Issues(&IssuesOptions{
- ProjectBoardID: b.ID,
- ProjectID: b.ProjectID,
- SortType: "project-column-sorting",
- })
- if err != nil {
- return nil, err
- }
- issueList = issues
- }
-
- if b.Default {
- issues, err := Issues(&IssuesOptions{
- ProjectBoardID: -1, // Issues without ProjectBoardID
- ProjectID: b.ProjectID,
- SortType: "project-column-sorting",
- })
- if err != nil {
- return nil, err
- }
- issueList = append(issueList, issues...)
- }
-
- if err := IssueList(issueList).LoadComments(); err != nil {
- return nil, err
- }
-
- b.Issues = issueList
- return issueList, nil
-}
-
-// LoadIssues load issues assigned to the boards
-func (bs ProjectBoardList) LoadIssues() (IssueList, error) {
- issues := make(IssueList, 0, len(bs)*10)
- for i := range bs {
- il, err := bs[i].LoadIssues()
- if err != nil {
- return nil, err
- }
- bs[i].Issues = il
- issues = append(issues, il...)
- }
- return issues, nil
-}
-
-// UpdateProjectBoardSorting update project board sorting
-func UpdateProjectBoardSorting(bs ProjectBoardList) error {
- for i := range bs {
- _, err := db.GetEngine(db.DefaultContext).ID(bs[i].ID).Cols(
- "sorting",
- ).Update(bs[i])
- if err != nil {
- return err
- }
- }
- return nil
-}
diff --git a/models/project_issue.go b/models/project_issue.go
deleted file mode 100644
index c7735addcc..0000000000
--- a/models/project_issue.go
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// Use of this source code is governed by a MIT-style
-// license that can be found in the LICENSE file.
-
-package models
-
-import (
- "context"
- "fmt"
-
- "code.gitea.io/gitea/models/db"
- user_model "code.gitea.io/gitea/models/user"
-)
-
-// ProjectIssue saves relation from issue to a project
-type ProjectIssue struct {
- ID int64 `xorm:"pk autoincr"`
- IssueID int64 `xorm:"INDEX"`
- ProjectID int64 `xorm:"INDEX"`
-
- // If 0, then it has not been added to a specific board in the project
- ProjectBoardID int64 `xorm:"INDEX"`
- Sorting int64 `xorm:"NOT NULL DEFAULT 0"`
-}
-
-func init() {
- db.RegisterModel(new(ProjectIssue))
-}
-
-func deleteProjectIssuesByProjectID(e db.Engine, projectID int64) error {
- _, err := e.Where("project_id=?", projectID).Delete(&ProjectIssue{})
- return err
-}
-
-// ___
-// |_ _|___ ___ _ _ ___
-// | |/ __/ __| | | |/ _ \
-// | |\__ \__ \ |_| | __/
-// |___|___/___/\__,_|\___|
-
-// LoadProject load the project the issue was assigned to
-func (i *Issue) LoadProject() (err error) {
- return i.loadProject(db.GetEngine(db.DefaultContext))
-}
-
-func (i *Issue) loadProject(e db.Engine) (err error) {
- if i.Project == nil {
- var p Project
- if _, err = e.Table("project").
- Join("INNER", "project_issue", "project.id=project_issue.project_id").
- Where("project_issue.issue_id = ?", i.ID).
- Get(&p); err != nil {
- return err
- }
- i.Project = &p
- }
- return
-}
-
-// ProjectID return project id if issue was assigned to one
-func (i *Issue) ProjectID() int64 {
- return i.projectID(db.GetEngine(db.DefaultContext))
-}
-
-func (i *Issue) projectID(e db.Engine) int64 {
- var ip ProjectIssue
- has, err := e.Where("issue_id=?", i.ID).Get(&ip)
- if err != nil || !has {
- return 0
- }
- return ip.ProjectID
-}
-
-// ProjectBoardID return project board id if issue was assigned to one
-func (i *Issue) ProjectBoardID() int64 {
- return i.projectBoardID(db.GetEngine(db.DefaultContext))
-}
-
-func (i *Issue) projectBoardID(e db.Engine) int64 {
- var ip ProjectIssue
- has, err := e.Where("issue_id=?", i.ID).Get(&ip)
- if err != nil || !has {
- return 0
- }
- return ip.ProjectBoardID
-}
-
-// ____ _ _
-// | _ \ _ __ ___ (_) ___ ___| |_
-// | |_) | '__/ _ \| |/ _ \/ __| __|
-// | __/| | | (_) | | __/ (__| |_
-// |_| |_| \___// |\___|\___|\__|
-// |__/
-
-// NumIssues return counter of all issues assigned to a project
-func (p *Project) NumIssues() int {
- c, err := db.GetEngine(db.DefaultContext).Table("project_issue").
- Where("project_id=?", p.ID).
- GroupBy("issue_id").
- Cols("issue_id").
- Count()
- if err != nil {
- return 0
- }
- return int(c)
-}
-
-// NumClosedIssues return counter of closed issues assigned to a project
-func (p *Project) NumClosedIssues() int {
- c, err := db.GetEngine(db.DefaultContext).Table("project_issue").
- Join("INNER", "issue", "project_issue.issue_id=issue.id").
- Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true).
- Cols("issue_id").
- Count()
- if err != nil {
- return 0
- }
- return int(c)
-}
-
-// NumOpenIssues return counter of open issues assigned to a project
-func (p *Project) NumOpenIssues() int {
- c, err := db.GetEngine(db.DefaultContext).Table("project_issue").
- Join("INNER", "issue", "project_issue.issue_id=issue.id").
- Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false).
- Cols("issue_id").
- Count()
- if err != nil {
- return 0
- }
- return int(c)
-}
-
-// ChangeProjectAssign changes the project associated with an issue
-func ChangeProjectAssign(issue *Issue, doer *user_model.User, newProjectID int64) error {
- ctx, committer, err := db.TxContext()
- if err != nil {
- return err
- }
- defer committer.Close()
-
- if err := addUpdateIssueProject(ctx, issue, doer, newProjectID); err != nil {
- return err
- }
-
- return committer.Commit()
-}
-
-func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
- e := db.GetEngine(ctx)
- oldProjectID := issue.projectID(e)
-
- if _, err := e.Where("project_issue.issue_id=?", issue.ID).Delete(&ProjectIssue{}); err != nil {
- return err
- }
-
- if err := issue.loadRepo(ctx); err != nil {
- return err
- }
-
- if oldProjectID > 0 || newProjectID > 0 {
- if _, err := createComment(ctx, &CreateCommentOptions{
- Type: CommentTypeProject,
- Doer: doer,
- Repo: issue.Repo,
- Issue: issue,
- OldProjectID: oldProjectID,
- ProjectID: newProjectID,
- }); err != nil {
- return err
- }
- }
-
- _, err := e.Insert(&ProjectIssue{
- IssueID: issue.ID,
- ProjectID: newProjectID,
- })
- return err
-}
-
-// ____ _ _ ____ _
-// | _ \ _ __ ___ (_) ___ ___| |_| __ ) ___ __ _ _ __ __| |
-// | |_) | '__/ _ \| |/ _ \/ __| __| _ \ / _ \ / _` | '__/ _` |
-// | __/| | | (_) | | __/ (__| |_| |_) | (_) | (_| | | | (_| |
-// |_| |_| \___// |\___|\___|\__|____/ \___/ \__,_|_| \__,_|
-// |__/
-
-// MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column
-func MoveIssuesOnProjectBoard(board *ProjectBoard, sortedIssueIDs map[int64]int64) error {
- return db.WithTx(func(ctx context.Context) error {
- sess := db.GetEngine(ctx)
-
- issueIDs := make([]int64, 0, len(sortedIssueIDs))
- for _, issueID := range sortedIssueIDs {
- issueIDs = append(issueIDs, issueID)
- }
- count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", board.ProjectID).In("issue_id", issueIDs).Count()
- if err != nil {
- return err
- }
- if int(count) != len(sortedIssueIDs) {
- return fmt.Errorf("all issues have to be added to a project first")
- }
-
- for sorting, issueID := range sortedIssueIDs {
- _, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", board.ID, sorting, issueID)
- if err != nil {
- return err
- }
- }
- return nil
- })
-}
-
-func (pb *ProjectBoard) removeIssues(e db.Engine) error {
- _, err := e.Exec("UPDATE `project_issue` SET project_board_id = 0, sorting = 0 WHERE project_board_id = ? ", pb.ID)
- return err
-}
diff --git a/models/repo.go b/models/repo.go
index 5a6a6b1a31..70539e849b 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -21,6 +21,7 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
+ project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
@@ -748,14 +749,14 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
}
}
- projects, _, err := getProjects(sess, ProjectSearchOptions{
+ projects, _, err := project_model.GetProjectsCtx(ctx, project_model.SearchOptions{
RepoID: repoID,
})
if err != nil {
return fmt.Errorf("get projects: %v", err)
}
for i := range projects {
- if err := deleteProjectByID(sess, projects[i].ID); err != nil {
+ if err := project_model.DeleteProjectByIDCtx(ctx, projects[i].ID); err != nil {
return fmt.Errorf("delete project [%d]: %v", projects[i].ID, err)
}
}
diff --git a/models/statistic.go b/models/statistic.go
index 67be4c5d42..a63cc2878b 100644
--- a/models/statistic.go
+++ b/models/statistic.go
@@ -9,6 +9,7 @@ import (
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
+ project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
@@ -106,7 +107,7 @@ func GetStatistic() (stats Statistic) {
stats.Counter.HookTask, _ = e.Count(new(webhook.HookTask))
stats.Counter.Team, _ = e.Count(new(organization.Team))
stats.Counter.Attachment, _ = e.Count(new(repo_model.Attachment))
- stats.Counter.Project, _ = e.Count(new(Project))
- stats.Counter.ProjectBoard, _ = e.Count(new(ProjectBoard))
+ stats.Counter.Project, _ = e.Count(new(project_model.Project))
+ stats.Counter.ProjectBoard, _ = e.Count(new(project_model.Board))
return
}
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index ee4738c970..5af91c8e5f 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
+ project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
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") {
- projects, _, err := models.GetProjects(models.ProjectSearchOptions{
+ projects, _, err := project_model.GetProjects(project_model.SearchOptions{
RepoID: repo.ID,
- Type: models.ProjectTypeRepository,
+ Type: project_model.TypeRepository,
IsClosed: util.OptionalBoolOf(isShowClosed),
})
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) {
var err error
- ctx.Data["OpenProjects"], _, err = models.GetProjects(models.ProjectSearchOptions{
+ ctx.Data["OpenProjects"], _, err = project_model.GetProjects(project_model.SearchOptions{
RepoID: repo.ID,
Page: -1,
IsClosed: util.OptionalBoolFalse,
- Type: models.ProjectTypeRepository,
+ Type: project_model.TypeRepository,
})
if err != nil {
ctx.ServerError("GetProjects", err)
return
}
- ctx.Data["ClosedProjects"], _, err = models.GetProjects(models.ProjectSearchOptions{
+ ctx.Data["ClosedProjects"], _, err = project_model.GetProjects(project_model.SearchOptions{
RepoID: repo.ID,
Page: -1,
IsClosed: util.OptionalBoolTrue,
- Type: models.ProjectTypeRepository,
+ Type: project_model.TypeRepository,
})
if err != nil {
ctx.ServerError("GetProjects", err)
@@ -814,7 +815,7 @@ func NewIssue(ctx *context.Context) {
projectID := ctx.FormInt64("project")
if projectID > 0 {
- project, err := models.GetProjectByID(projectID)
+ project, err := project_model.GetProjectByID(projectID)
if err != nil {
log.Error("GetProjectByID: %d: %v", projectID, err)
} 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 {
- p, err := models.GetProjectByID(form.ProjectID)
+ p, err := project_model.GetProjectByID(form.ProjectID)
if err != nil {
ctx.ServerError("GetProjectByID", err)
return nil, nil, 0, 0
@@ -1413,7 +1414,7 @@ func ViewIssue(ctx *context.Context) {
return
}
- ghostProject := &models.Project{
+ ghostProject := &project_model.Project{
ID: -1,
Title: ctx.Tr("repo.issues.deleted_project"),
}
diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go
index 9df2520a52..a6f843d848 100644
--- a/routers/web/repo/projects.go
+++ b/routers/web/repo/projects.go
@@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/perm"
+ project_model "code.gitea.io/gitea/models/project"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
@@ -69,12 +70,12 @@ func Projects(ctx *context.Context) {
total = repo.NumClosedProjects
}
- projects, count, err := models.GetProjects(models.ProjectSearchOptions{
+ projects, count, err := project_model.GetProjects(project_model.SearchOptions{
RepoID: repo.ID,
Page: page,
IsClosed: util.OptionalBoolOf(isShowClosed),
SortType: sortType,
- Type: models.ProjectTypeRepository,
+ Type: project_model.TypeRepository,
})
if err != nil {
ctx.ServerError("GetProjects", err)
@@ -122,7 +123,7 @@ func Projects(ctx *context.Context) {
// NewProject render creating a project page
func NewProject(ctx *context.Context) {
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.HTML(http.StatusOK, tplProjectsNew)
}
@@ -134,18 +135,18 @@ func NewProjectPost(ctx *context.Context) {
if ctx.HasError() {
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)
return
}
- if err := models.NewProject(&models.Project{
+ if err := project_model.NewProject(&project_model.Project{
RepoID: ctx.Repo.Repository.ID,
Title: form.Title,
Description: form.Content,
CreatorID: ctx.Doer.ID,
BoardType: form.BoardType,
- Type: models.ProjectTypeRepository,
+ Type: project_model.TypeRepository,
}); err != nil {
ctx.ServerError("NewProject", err)
return
@@ -168,8 +169,8 @@ func ChangeProjectStatus(ctx *context.Context) {
}
id := ctx.ParamsInt64(":id")
- if err := models.ChangeProjectStatusByRepoIDAndID(ctx.Repo.Repository.ID, id, toClose); err != nil {
- if models.IsErrProjectNotExist(err) {
+ if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx.Repo.Repository.ID, id, toClose); err != nil {
+ if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", err)
} else {
ctx.ServerError("ChangeProjectStatusByIDAndRepoID", err)
@@ -181,9 +182,9 @@ func ChangeProjectStatus(ctx *context.Context) {
// DeleteProject delete a project
func DeleteProject(ctx *context.Context) {
- p, err := models.GetProjectByID(ctx.ParamsInt64(":id"))
+ p, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
if err != nil {
- if models.IsErrProjectNotExist(err) {
+ if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
@@ -195,7 +196,7 @@ func DeleteProject(ctx *context.Context) {
return
}
- if err := models.DeleteProjectByID(p.ID); err != nil {
+ if err := project_model.DeleteProjectByID(p.ID); err != nil {
ctx.Flash.Error("DeleteProjectByID: " + err.Error())
} else {
ctx.Flash.Success(ctx.Tr("repo.projects.deletion_success"))
@@ -212,9 +213,9 @@ func EditProject(ctx *context.Context) {
ctx.Data["PageIsEditProjects"] = true
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 models.IsErrProjectNotExist(err) {
+ if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
@@ -244,9 +245,9 @@ func EditProjectPost(ctx *context.Context) {
return
}
- p, err := models.GetProjectByID(ctx.ParamsInt64(":id"))
+ p, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
if err != nil {
- if models.IsErrProjectNotExist(err) {
+ if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
@@ -260,7 +261,7 @@ func EditProjectPost(ctx *context.Context) {
p.Title = form.Title
p.Description = form.Content
- if err = models.UpdateProject(p); err != nil {
+ if err = project_model.UpdateProject(p); err != nil {
ctx.ServerError("UpdateProjects", err)
return
}
@@ -271,9 +272,9 @@ func EditProjectPost(ctx *context.Context) {
// ViewProject renders the project board for a project
func ViewProject(ctx *context.Context) {
- project, err := models.GetProjectByID(ctx.ParamsInt64(":id"))
+ project, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
if err != nil {
- if models.IsErrProjectNotExist(err) {
+ if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
@@ -285,7 +286,7 @@ func ViewProject(ctx *context.Context) {
return
}
- boards, err := models.GetProjectBoards(project.ID)
+ boards, err := project_model.GetBoards(project.ID)
if err != nil {
ctx.ServerError("GetProjectBoards", err)
return
@@ -295,27 +296,29 @@ func ViewProject(ctx *context.Context) {
boards[0].Title = ctx.Tr("repo.projects.type.uncategorized")
}
- issueList, err := boards.LoadIssues()
+ issuesMap, err := models.LoadIssuesFromBoardList(boards)
if err != nil {
ctx.ServerError("LoadIssuesOfBoards", err)
return
}
linkedPrsMap := make(map[int64][]*models.Issue)
- for _, issue := range issueList {
- var referencedIds []int64
- for _, comment := range issue.Comments {
- if comment.RefIssueID != 0 && comment.RefIsPull {
- referencedIds = append(referencedIds, comment.RefIssueID)
+ for _, issuesList := range issuesMap {
+ for _, issue := range issuesList {
+ var referencedIds []int64
+ for _, comment := range issue.Comments {
+ if comment.RefIssueID != 0 && comment.RefIsPull {
+ referencedIds = append(referencedIds, comment.RefIssueID)
+ }
}
- }
- if len(referencedIds) > 0 {
- if linkedPrs, err := models.Issues(&models.IssuesOptions{
- IssueIDs: referencedIds,
- IsPull: util.OptionalBoolTrue,
- }); err == nil {
- linkedPrsMap[issue.ID] = linkedPrs
+ if len(referencedIds) > 0 {
+ if linkedPrs, err := models.Issues(&models.IssuesOptions{
+ IssueIDs: referencedIds,
+ IsPull: util.OptionalBoolTrue,
+ }); err == nil {
+ linkedPrsMap[issue.ID] = linkedPrs
+ }
}
}
}
@@ -335,6 +338,7 @@ func ViewProject(ctx *context.Context) {
ctx.Data["IsProjectsPage"] = true
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
ctx.Data["Project"] = project
+ ctx.Data["IssuesMap"] = issuesMap
ctx.Data["Boards"] = boards
ctx.HTML(http.StatusOK, tplProjectsView)
@@ -381,9 +385,9 @@ func DeleteProjectBoard(ctx *context.Context) {
return
}
- project, err := models.GetProjectByID(ctx.ParamsInt64(":id"))
+ project, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
if err != nil {
- if models.IsErrProjectNotExist(err) {
+ if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
@@ -391,7 +395,7 @@ func DeleteProjectBoard(ctx *context.Context) {
return
}
- pb, err := models.GetProjectBoard(ctx.ParamsInt64(":boardID"))
+ pb, err := project_model.GetBoard(ctx.ParamsInt64(":boardID"))
if err != nil {
ctx.ServerError("GetProjectBoard", err)
return
@@ -410,7 +414,7 @@ func DeleteProjectBoard(ctx *context.Context) {
return
}
- if err := models.DeleteProjectBoardByID(ctx.ParamsInt64(":boardID")); err != nil {
+ if err := project_model.DeleteBoardByID(ctx.ParamsInt64(":boardID")); err != nil {
ctx.ServerError("DeleteProjectBoardByID", err)
return
}
@@ -430,9 +434,9 @@ func AddBoardToProjectPost(ctx *context.Context) {
return
}
- project, err := models.GetProjectByID(ctx.ParamsInt64(":id"))
+ project, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
if err != nil {
- if models.IsErrProjectNotExist(err) {
+ if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
@@ -440,7 +444,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
return
}
- if err := models.NewProjectBoard(&models.ProjectBoard{
+ if err := project_model.NewBoard(&project_model.Board{
ProjectID: project.ID,
Title: form.Title,
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 {
ctx.JSON(http.StatusForbidden, map[string]string{
"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
}
- project, err := models.GetProjectByID(ctx.ParamsInt64(":id"))
+ project, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
if err != nil {
- if models.IsErrProjectNotExist(err) {
+ if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil)
} else {
ctx.ServerError("GetProjectByID", err)
@@ -480,7 +484,7 @@ func checkProjectBoardChangePermissions(ctx *context.Context) (*models.Project,
return nil, nil
}
- board, err := models.GetProjectBoard(ctx.ParamsInt64(":boardID"))
+ board, err := project_model.GetBoard(ctx.ParamsInt64(":boardID"))
if err != nil {
ctx.ServerError("GetProjectBoard", err)
return nil, nil
@@ -519,7 +523,7 @@ func EditProjectBoard(ctx *context.Context) {
board.Sorting = form.Sorting
}
- if err := models.UpdateProjectBoard(board); err != nil {
+ if err := project_model.UpdateBoard(board); err != nil {
ctx.ServerError("UpdateProjectBoard", err)
return
}
@@ -536,7 +540,7 @@ func SetDefaultProjectBoard(ctx *context.Context) {
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)
return
}
@@ -562,9 +566,9 @@ func MoveIssues(ctx *context.Context) {
return
}
- project, err := models.GetProjectByID(ctx.ParamsInt64(":id"))
+ project, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
if err != nil {
- if models.IsErrProjectNotExist(err) {
+ if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("ProjectNotExist", nil)
} else {
ctx.ServerError("GetProjectByID", err)
@@ -576,19 +580,18 @@ func MoveIssues(ctx *context.Context) {
return
}
- var board *models.ProjectBoard
+ var board *project_model.Board
if ctx.ParamsInt64(":boardID") == 0 {
- board = &models.ProjectBoard{
+ board = &project_model.Board{
ID: 0,
ProjectID: project.ID,
Title: ctx.Tr("repo.projects.type.uncategorized"),
}
} else {
- // column
- board, err = models.GetProjectBoard(ctx.ParamsInt64(":boardID"))
+ board, err = project_model.GetBoard(ctx.ParamsInt64(":boardID"))
if err != nil {
- if models.IsErrProjectBoardNotExist(err) {
+ if project_model.IsErrProjectBoardNotExist(err) {
ctx.NotFound("ProjectBoardNotExist", nil)
} else {
ctx.ServerError("GetProjectBoard", err)
@@ -634,7 +637,7 @@ func MoveIssues(ctx *context.Context) {
return
}
- if err = models.MoveIssuesOnProjectBoard(board, sortedIssueIDs); err != nil {
+ if err = project_model.MoveIssuesOnProjectBoard(board, sortedIssueIDs); err != nil {
ctx.ServerError("MoveIssuesOnProjectBoard", err)
return
}
@@ -647,8 +650,43 @@ func MoveIssues(ctx *context.Context) {
// CreateProject renders the generic project creation page
func CreateProject(ctx *context.Context) {
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.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 + "/")
+}
diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go
index 99198e8866..f628840375 100644
--- a/routers/web/user/profile.go
+++ b/routers/web/user/profile.go
@@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
+ project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
@@ -216,10 +217,10 @@ func Profile(ctx *context.Context) {
total = int(count)
case "projects":
- ctx.Data["OpenProjects"], _, err = models.GetProjects(models.ProjectSearchOptions{
+ ctx.Data["OpenProjects"], _, err = project_model.GetProjects(project_model.SearchOptions{
Page: -1,
IsClosed: util.OptionalBoolFalse,
- Type: models.ProjectTypeIndividual,
+ Type: project_model.TypeIndividual,
})
if err != nil {
ctx.ServerError("GetProjects", err)
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index 3760c71f2a..e968ac55ea 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -11,6 +11,7 @@ import (
"strings"
"code.gitea.io/gitea/models"
+ project_model "code.gitea.io/gitea/models/project"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
@@ -499,7 +500,7 @@ func (i IssueLockForm) HasValidReason() bool {
type CreateProjectForm struct {
Title string `binding:"Required;MaxSize(100)"`
Content string
- BoardType models.ProjectBoardType
+ BoardType project_model.BoardType
}
// UserCreateProjectForm is a from for creating an individual or organization
@@ -507,7 +508,7 @@ type CreateProjectForm struct {
type UserCreateProjectForm struct {
Title string `binding:"Required;MaxSize(100)"`
Content string
- BoardType models.ProjectBoardType
+ BoardType project_model.BoardType
UID int64 `binding:"Required"`
}
diff --git a/templates/repo/projects/view.tmpl b/templates/repo/projects/view.tmpl
index 4ca21cc38e..7d4d21f8fe 100644
--- a/templates/repo/projects/view.tmpl
+++ b/templates/repo/projects/view.tmpl
@@ -84,7 +84,7 @@
<div class="board-column-header df ac sb">
<div class="ui large label board-label py-2">
<div class="ui small circular grey label board-card-cnt">
- {{len .Issues}}
+ {{.NumIssues}}
</div>
{{.Title}}
</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}}">
- {{ range .Issues }}
+ {{ range (index $.IssuesMap .ID) }}
<!-- start issue card -->
<div class="card board-card" data-issue="{{.ID}}">