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:
parent
ea6efba9b3
commit
bd97736b9c
18 changed files with 810 additions and 713 deletions
|
@ -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)
|
||||
}
|
||||
|
||||
// _____ .__.__ __
|
||||
// / \ |__| | ____ _______/ |_ ____ ____ ____
|
||||
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
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
|
||||
// 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
|
||||
}
|
||||
|
|
@ -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(),
|
|
@ -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/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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
|
|
|
@ -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 + "/")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
||||
|
|
|
@ -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}}">
|
||||
|
|
Loading…
Add table
Reference in a new issue