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>
tokarchuk/v1.17
Lunny Xiao 3 years ago committed by GitHub
parent ea6efba9b3
commit bd97736b9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 38
      models/error.go
  2. 7
      models/issue.go
  3. 9
      models/issue_comment.go
  4. 181
      models/issue_project.go
  5. 289
      models/project/board.go
  6. 100
      models/project/issue.go
  7. 23
      models/project/main_test.go
  8. 112
      models/project/project.go
  9. 22
      models/project/project_test.go
  10. 321
      models/project_board.go
  11. 218
      models/project_issue.go
  12. 5
      models/repo.go
  13. 5
      models/statistic.go
  14. 19
      routers/web/repo/issue.go
  15. 124
      routers/web/repo/projects.go
  16. 5
      routers/web/user/profile.go
  17. 5
      services/forms/repo_form.go
  18. 4
      templates/repo/projects/view.tmpl

@ -1063,44 +1063,6 @@ func (err ErrLabelNotExist) Error() string {
return fmt.Sprintf("label does not exist [label_id: %d]", err.LabelID) return fmt.Sprintf("label does not exist [label_id: %d]", err.LabelID)
} }
// __________ __ __
// \______ \_______ ____ |__| ____ _____/ |_ ______
// | ___/\_ __ \/ _ \ | |/ __ \_/ ___\ __\/ ___/
// | | | | \( <_> ) | \ ___/\ \___| | \___ \
// |____| |__| \____/\__| |\___ >\___ >__| /____ >
// \______| \/ \/ \/
// ErrProjectNotExist represents a "ProjectNotExist" kind of error.
type ErrProjectNotExist struct {
ID int64
RepoID int64
}
// IsErrProjectNotExist checks if an error is a ErrProjectNotExist
func IsErrProjectNotExist(err error) bool {
_, ok := err.(ErrProjectNotExist)
return ok
}
func (err ErrProjectNotExist) Error() string {
return fmt.Sprintf("projects does not exist [id: %d]", err.ID)
}
// ErrProjectBoardNotExist represents a "ProjectBoardNotExist" kind of error.
type ErrProjectBoardNotExist struct {
BoardID int64
}
// IsErrProjectBoardNotExist checks if an error is a ErrProjectBoardNotExist
func IsErrProjectBoardNotExist(err error) bool {
_, ok := err.(ErrProjectBoardNotExist)
return ok
}
func (err ErrProjectBoardNotExist) Error() string {
return fmt.Sprintf("project board does not exist [id: %d]", err.BoardID)
}
// _____ .__.__ __ // _____ .__.__ __
// / \ |__| | ____ _______/ |_ ____ ____ ____ // / \ |__| | ____ _______/ |_ ____ ____ ____
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \ // / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \

@ -19,6 +19,7 @@ import (
"code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -52,7 +53,7 @@ type Issue struct {
Labels []*Label `xorm:"-"` Labels []*Label `xorm:"-"`
MilestoneID int64 `xorm:"INDEX"` MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"` Milestone *Milestone `xorm:"-"`
Project *Project `xorm:"-"` Project *project_model.Project `xorm:"-"`
Priority int Priority int
AssigneeID int64 `xorm:"-"` AssigneeID int64 `xorm:"-"`
Assignee *user_model.User `xorm:"-"` Assignee *user_model.User `xorm:"-"`
@ -2135,7 +2136,7 @@ func deleteIssue(ctx context.Context, issue *Issue) error {
&IssueWatch{}, &IssueWatch{},
&Stopwatch{}, &Stopwatch{},
&TrackedTime{}, &TrackedTime{},
&ProjectIssue{}, &project_model.ProjectIssue{},
&repo_model.Attachment{}, &repo_model.Attachment{},
&PullRequest{}, &PullRequest{},
); err != nil { ); err != nil {
@ -2469,7 +2470,7 @@ func deleteIssuesByRepoID(sess db.Engine, repoID int64) (attachmentPaths []strin
} }
if _, err = sess.In("issue_id", deleteCond). if _, err = sess.In("issue_id", deleteCond).
Delete(&ProjectIssue{}); err != nil { Delete(&project_model.ProjectIssue{}); err != nil {
return return
} }

@ -17,6 +17,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
@ -204,8 +205,8 @@ type Comment struct {
RemovedLabels []*Label `xorm:"-"` RemovedLabels []*Label `xorm:"-"`
OldProjectID int64 OldProjectID int64
ProjectID int64 ProjectID int64
OldProject *Project `xorm:"-"` OldProject *project_model.Project `xorm:"-"`
Project *Project `xorm:"-"` Project *project_model.Project `xorm:"-"`
OldMilestoneID int64 OldMilestoneID int64
MilestoneID int64 MilestoneID int64
OldMilestone *Milestone `xorm:"-"` OldMilestone *Milestone `xorm:"-"`
@ -469,7 +470,7 @@ func (c *Comment) LoadLabel() error {
// LoadProject if comment.Type is CommentTypeProject, then load project. // LoadProject if comment.Type is CommentTypeProject, then load project.
func (c *Comment) LoadProject() error { func (c *Comment) LoadProject() error {
if c.OldProjectID > 0 { if c.OldProjectID > 0 {
var oldProject Project var oldProject project_model.Project
has, err := db.GetEngine(db.DefaultContext).ID(c.OldProjectID).Get(&oldProject) has, err := db.GetEngine(db.DefaultContext).ID(c.OldProjectID).Get(&oldProject)
if err != nil { if err != nil {
return err return err
@ -479,7 +480,7 @@ func (c *Comment) LoadProject() error {
} }
if c.ProjectID > 0 { if c.ProjectID > 0 {
var project Project var project project_model.Project
has, err := db.GetEngine(db.DefaultContext).ID(c.ProjectID).Get(&project) has, err := db.GetEngine(db.DefaultContext).ID(c.ProjectID).Get(&project)
if err != nil { if err != nil {
return err return err

@ -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()
}

@ -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
}

@ -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
}

@ -0,0 +1,23 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package project
import (
"path/filepath"
"testing"
"code.gitea.io/gitea/models/unittest"
_ "code.gitea.io/gitea/models/repo"
)
func TestMain(m *testing.M) {
unittest.MainTest(m, filepath.Join("..", ".."),
"project.yml",
"project_board.yml",
"project_issue.yml",
"repository.yml",
)
}

@ -2,9 +2,10 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package models package project
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
@ -19,25 +20,56 @@ import (
type ( type (
// ProjectsConfig is used to identify the type of board that is being created // ProjectsConfig is used to identify the type of board that is being created
ProjectsConfig struct { ProjectsConfig struct {
BoardType ProjectBoardType BoardType BoardType
Translation string Translation string
} }
// ProjectType is used to identify the type of project in question and ownership // Type is used to identify the type of project in question and ownership
ProjectType uint8 Type uint8
) )
const ( const (
// ProjectTypeIndividual is a type of project board that is owned by an individual // TypeIndividual is a type of project board that is owned by an individual
ProjectTypeIndividual ProjectType = iota + 1 TypeIndividual Type = iota + 1
// ProjectTypeRepository is a project that is tied to a repository // TypeRepository is a project that is tied to a repository
ProjectTypeRepository TypeRepository
// ProjectTypeOrganization is a project that is tied to an organisation // TypeOrganization is a project that is tied to an organisation
ProjectTypeOrganization TypeOrganization
) )
// ErrProjectNotExist represents a "ProjectNotExist" kind of error.
type ErrProjectNotExist struct {
ID int64
RepoID int64
}
// IsErrProjectNotExist checks if an error is a ErrProjectNotExist
func IsErrProjectNotExist(err error) bool {
_, ok := err.(ErrProjectNotExist)
return ok
}
func (err ErrProjectNotExist) Error() string {
return fmt.Sprintf("projects does not exist [id: %d]", err.ID)
}
// ErrProjectBoardNotExist represents a "ProjectBoardNotExist" kind of error.
type ErrProjectBoardNotExist struct {
BoardID int64
}
// IsErrProjectBoardNotExist checks if an error is a ErrProjectBoardNotExist
func IsErrProjectBoardNotExist(err error) bool {
_, ok := err.(ErrProjectBoardNotExist)
return ok
}
func (err ErrProjectBoardNotExist) Error() string {
return fmt.Sprintf("project board does not exist [id: %d]", err.BoardID)
}
// Project represents a project board // Project represents a project board
type Project struct { type Project struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
@ -46,8 +78,8 @@ type Project struct {
RepoID int64 `xorm:"INDEX"` RepoID int64 `xorm:"INDEX"`
CreatorID int64 `xorm:"NOT NULL"` CreatorID int64 `xorm:"NOT NULL"`
IsClosed bool `xorm:"INDEX"` IsClosed bool `xorm:"INDEX"`
BoardType ProjectBoardType BoardType BoardType
Type ProjectType Type Type
RenderedContent string `xorm:"-"` RenderedContent string `xorm:"-"`
@ -63,37 +95,39 @@ func init() {
// GetProjectsConfig retrieves the types of configurations projects could have // GetProjectsConfig retrieves the types of configurations projects could have
func GetProjectsConfig() []ProjectsConfig { func GetProjectsConfig() []ProjectsConfig {
return []ProjectsConfig{ return []ProjectsConfig{
{ProjectBoardTypeNone, "repo.projects.type.none"}, {BoardTypeNone, "repo.projects.type.none"},
{ProjectBoardTypeBasicKanban, "repo.projects.type.basic_kanban"}, {BoardTypeBasicKanban, "repo.projects.type.basic_kanban"},
{ProjectBoardTypeBugTriage, "repo.projects.type.bug_triage"}, {BoardTypeBugTriage, "repo.projects.type.bug_triage"},
} }
} }
// IsProjectTypeValid checks if a project type is valid // IsTypeValid checks if a project type is valid
func IsProjectTypeValid(p ProjectType) bool { func IsTypeValid(p Type) bool {
switch p { switch p {
case ProjectTypeRepository: case TypeRepository:
return true return true
default: default:
return false return false
} }
} }
// ProjectSearchOptions are options for GetProjects // SearchOptions are options for GetProjects
type ProjectSearchOptions struct { type SearchOptions struct {
RepoID int64 RepoID int64
Page int Page int
IsClosed util.OptionalBool IsClosed util.OptionalBool
SortType string SortType string
Type ProjectType Type Type
} }
// GetProjects returns a list of all projects that have been created in the repository // GetProjects returns a list of all projects that have been created in the repository
func GetProjects(opts ProjectSearchOptions) ([]*Project, int64, error) { func GetProjects(opts SearchOptions) ([]*Project, int64, error) {
return getProjects(db.GetEngine(db.DefaultContext), opts) return GetProjectsCtx(db.DefaultContext, opts)
} }
func getProjects(e db.Engine, opts ProjectSearchOptions) ([]*Project, int64, error) { // GetProjectsCtx returns a list of all projects that have been created in the repository
func GetProjectsCtx(ctx context.Context, opts SearchOptions) ([]*Project, int64, error) {
e := db.GetEngine(ctx)
projects := make([]*Project, 0, setting.UI.IssuePagingNum) projects := make([]*Project, 0, setting.UI.IssuePagingNum)
var cond builder.Cond = builder.Eq{"repo_id": opts.RepoID} var cond builder.Cond = builder.Eq{"repo_id": opts.RepoID}
@ -135,11 +169,11 @@ func getProjects(e db.Engine, opts ProjectSearchOptions) ([]*Project, int64, err
// NewProject creates a new Project // NewProject creates a new Project
func NewProject(p *Project) error { func NewProject(p *Project) error {
if !IsProjectBoardTypeValid(p.BoardType) { if !IsBoardTypeValid(p.BoardType) {
p.BoardType = ProjectBoardTypeNone p.BoardType = BoardTypeNone
} }
if !IsProjectTypeValid(p.Type) { if !IsTypeValid(p.Type) {
return errors.New("project type is not valid") return errors.New("project type is not valid")
} }
@ -157,7 +191,7 @@ func NewProject(p *Project) error {
return err return err
} }
if err := createBoardsForProjectsType(db.GetEngine(ctx), p); err != nil { if err := createBoardsForProjectsType(ctx, p); err != nil {
return err return err
} }
@ -200,7 +234,7 @@ func updateRepositoryProjectCount(e db.Engine, repoID int64) error {
builder.Eq{ builder.Eq{
"`num_projects`": builder.Select("count(*)").From("`project`"). "`num_projects`": builder.Select("count(*)").From("`project`").
Where(builder.Eq{"`project`.`repo_id`": repoID}. Where(builder.Eq{"`project`.`repo_id`": repoID}.
And(builder.Eq{"`project`.`type`": ProjectTypeRepository})), And(builder.Eq{"`project`.`type`": TypeRepository})),
}).From("`repository`").Where(builder.Eq{"id": repoID})); err != nil { }).From("`repository`").Where(builder.Eq{"id": repoID})); err != nil {
return err return err
} }
@ -209,7 +243,7 @@ func updateRepositoryProjectCount(e db.Engine, repoID int64) error {
builder.Eq{ builder.Eq{
"`num_closed_projects`": builder.Select("count(*)").From("`project`"). "`num_closed_projects`": builder.Select("count(*)").From("`project`").
Where(builder.Eq{"`project`.`repo_id`": repoID}. Where(builder.Eq{"`project`.`repo_id`": repoID}.
And(builder.Eq{"`project`.`type`": ProjectTypeRepository}). And(builder.Eq{"`project`.`type`": TypeRepository}).
And(builder.Eq{"`project`.`is_closed`": true})), And(builder.Eq{"`project`.`is_closed`": true})),
}).From("`repository`").Where(builder.Eq{"id": repoID})); err != nil { }).From("`repository`").Where(builder.Eq{"id": repoID})); err != nil {
return err return err
@ -224,18 +258,17 @@ func ChangeProjectStatusByRepoIDAndID(repoID, projectID int64, isClosed bool) er
return err return err
} }
defer committer.Close() defer committer.Close()
sess := db.GetEngine(ctx)
p := new(Project) p := new(Project)
has, err := sess.ID(projectID).Where("repo_id = ?", repoID).Get(p) has, err := db.GetEngine(ctx).ID(projectID).Where("repo_id = ?", repoID).Get(p)
if err != nil { if err != nil {
return err return err
} else if !has { } else if !has {
return ErrProjectNotExist{ID: projectID, RepoID: repoID} return ErrProjectNotExist{ID: projectID, RepoID: repoID}
} }
if err := changeProjectStatus(sess, p, isClosed); err != nil { if err := changeProjectStatus(ctx, p, isClosed); err != nil {
return err return err
} }
@ -250,16 +283,17 @@ func ChangeProjectStatus(p *Project, isClosed bool) error {
} }
defer committer.Close() defer committer.Close()
if err := changeProjectStatus(db.GetEngine(ctx), p, isClosed); err != nil { if err := changeProjectStatus(ctx, p, isClosed); err != nil {
return err return err
} }
return committer.Commit() return committer.Commit()
} }
func changeProjectStatus(e db.Engine, p *Project, isClosed bool) error { func changeProjectStatus(ctx context.Context, p *Project, isClosed bool) error {
p.IsClosed = isClosed p.IsClosed = isClosed
p.ClosedDateUnix = timeutil.TimeStampNow() p.ClosedDateUnix = timeutil.TimeStampNow()
e := db.GetEngine(ctx)
count, err := e.ID(p.ID).Where("repo_id = ? AND is_closed = ?", p.RepoID, !isClosed).Cols("is_closed", "closed_date_unix").Update(p) count, err := e.ID(p.ID).Where("repo_id = ? AND is_closed = ?", p.RepoID, !isClosed).Cols("is_closed", "closed_date_unix").Update(p)
if err != nil { if err != nil {
return err return err
@ -279,14 +313,16 @@ func DeleteProjectByID(id int64) error {
} }
defer committer.Close() defer committer.Close()
if err := deleteProjectByID(db.GetEngine(ctx), id); err != nil { if err := DeleteProjectByIDCtx(ctx, id); err != nil {
return err return err
} }
return committer.Commit() return committer.Commit()
} }
func deleteProjectByID(e db.Engine, id int64) error { // DeleteProjectByIDCtx deletes a project from a repository.
func DeleteProjectByIDCtx(ctx context.Context, id int64) error {
e := db.GetEngine(ctx)
p, err := getProjectByID(e, id) p, err := getProjectByID(e, id)
if err != nil { if err != nil {
if IsErrProjectNotExist(err) { if IsErrProjectNotExist(err) {
@ -299,7 +335,7 @@ func deleteProjectByID(e db.Engine, id int64) error {
return err return err
} }
if err := deleteProjectBoardByProjectID(e, id); err != nil { if err := deleteBoardByProjectID(e, id); err != nil {
return err return err
} }

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package models package project
import ( import (
"testing" "testing"
@ -14,33 +14,33 @@ import (
) )
func TestIsProjectTypeValid(t *testing.T) { func TestIsProjectTypeValid(t *testing.T) {
const UnknownType ProjectType = 15 const UnknownType Type = 15
cases := []struct { cases := []struct {
typ ProjectType typ Type
valid bool valid bool
}{ }{
{ProjectTypeIndividual, false}, {TypeIndividual, false},
{ProjectTypeRepository, true}, {TypeRepository, true},
{ProjectTypeOrganization, false}, {TypeOrganization, false},
{UnknownType, false}, {UnknownType, false},
} }
for _, v := range cases { for _, v := range cases {
assert.Equal(t, v.valid, IsProjectTypeValid(v.typ)) assert.Equal(t, v.valid, IsTypeValid(v.typ))
} }
} }
func TestGetProjects(t *testing.T) { func TestGetProjects(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
projects, _, err := GetProjects(ProjectSearchOptions{RepoID: 1}) projects, _, err := GetProjects(SearchOptions{RepoID: 1})
assert.NoError(t, err) assert.NoError(t, err)
// 1 value for this repo exists in the fixtures // 1 value for this repo exists in the fixtures
assert.Len(t, projects, 1) assert.Len(t, projects, 1)
projects, _, err = GetProjects(ProjectSearchOptions{RepoID: 3}) projects, _, err = GetProjects(SearchOptions{RepoID: 3})
assert.NoError(t, err) assert.NoError(t, err)
// 1 value for this repo exists in the fixtures // 1 value for this repo exists in the fixtures
@ -51,8 +51,8 @@ func TestProject(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
project := &Project{ project := &Project{
Type: ProjectTypeRepository, Type: TypeRepository,
BoardType: ProjectBoardTypeBasicKanban, BoardType: BoardTypeBasicKanban,
Title: "New Project", Title: "New Project",
RepoID: 1, RepoID: 1,
CreatedUnix: timeutil.TimeStampNow(), CreatedUnix: timeutil.TimeStampNow(),

@ -1,321 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"fmt"
"regexp"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/builder"
)
type (
// ProjectBoardType is used to represent a project board type
ProjectBoardType uint8
// ProjectBoardList is a list of all project boards in a repository
ProjectBoardList []*ProjectBoard
)
const (
// ProjectBoardTypeNone is a project board type that has no predefined columns
ProjectBoardTypeNone ProjectBoardType = iota
// ProjectBoardTypeBasicKanban is a project board type that has basic predefined columns
ProjectBoardTypeBasicKanban
// ProjectBoardTypeBugTriage is a project board type that has predefined columns suited to hunting down bugs
ProjectBoardTypeBugTriage
)
// BoardColorPattern is a regexp witch can validate BoardColor
var BoardColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$")
// ProjectBoard is used to represent boards on a project
type ProjectBoard struct {
ID int64 `xorm:"pk autoincr"`
Title string
Default bool `xorm:"NOT NULL DEFAULT false"` // issues not assigned to a specific board will be assigned to this board
Sorting int8 `xorm:"NOT NULL DEFAULT 0"`
Color string `xorm:"VARCHAR(7)"`
ProjectID int64 `xorm:"INDEX NOT NULL"`
CreatorID int64 `xorm:"NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
Issues []*Issue `xorm:"-"`
}
func init() {
db.RegisterModel(new(ProjectBoard))
}
// IsProjectBoardTypeValid checks if the project board type is valid
func IsProjectBoardTypeValid(p ProjectBoardType) bool {
switch p {
case ProjectBoardTypeNone, ProjectBoardTypeBasicKanban, ProjectBoardTypeBugTriage:
return true
default:
return false
}
}
func createBoardsForProjectsType(sess db.Engine, project *Project) error {
var items []string
switch project.BoardType {
case ProjectBoardTypeBugTriage:
items = setting.Project.ProjectBoardBugTriageType
case ProjectBoardTypeBasicKanban:
items = setting.Project.ProjectBoardBasicKanbanType
case ProjectBoardTypeNone:
fallthrough
default:
return nil
}
if len(items) == 0 {
return nil
}
boards := make([]ProjectBoard, 0, len(items))
for _, v := range items {
boards = append(boards, ProjectBoard{
CreatedUnix: timeutil.TimeStampNow(),
CreatorID: project.CreatorID,
Title: v,
ProjectID: project.ID,
})
}
_, err := sess.Insert(boards)
return err
}
// NewProjectBoard adds a new project board to a given project
func NewProjectBoard(board *ProjectBoard) error {
if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) {
return fmt.Errorf("bad color code: %s", board.Color)
}
_, err := db.GetEngine(db.DefaultContext).Insert(board)
return err
}
// DeleteProjectBoardByID removes all issues references to the project board.
func DeleteProjectBoardByID(boardID int64) error {
ctx, committer, err := db.TxContext()
if err != nil {
return err
}
defer committer.Close()
if err := deleteProjectBoardByID(db.GetEngine(ctx), boardID); err != nil {
return err
}
return committer.Commit()
}
func deleteProjectBoardByID(e db.Engine, boardID int64) error {
board, err := getProjectBoard(e, boardID)
if err != nil {
if IsErrProjectBoardNotExist(err) {
return nil
}
return err
}
if err = board.removeIssues(e); err != nil {
return err
}
if _, err := e.ID(board.ID).Delete(board); err != nil {
return err
}
return nil
}
func deleteProjectBoardByProjectID(e db.Engine, projectID int64) error {
_, err := e.Where("project_id=?", projectID).Delete(&ProjectBoard{})
return err
}
// GetProjectBoard fetches the current board of a project
func GetProjectBoard(boardID int64) (*ProjectBoard, error) {
return getProjectBoard(db.GetEngine(db.DefaultContext), boardID)
}
func getProjectBoard(e db.Engine, boardID int64) (*ProjectBoard, error) {
board := new(ProjectBoard)
has, err := e.ID(boardID).Get(board)
if err != nil {
return nil, err
} else if !has {
return nil, ErrProjectBoardNotExist{BoardID: boardID}
}
return board, nil
}
// UpdateProjectBoard updates a project board
func UpdateProjectBoard(board *ProjectBoard) error {
return updateProjectBoard(db.GetEngine(db.DefaultContext), board)
}
func updateProjectBoard(e db.Engine, board *ProjectBoard) error {
var fieldToUpdate []string
if board.Sorting != 0 {
fieldToUpdate = append(fieldToUpdate, "sorting")
}
if board.Title != "" {
fieldToUpdate = append(fieldToUpdate, "title")
}
if len(board.Color) != 0 && !BoardColorPattern.MatchString(board.Color) {
return fmt.Errorf("bad color code: %s", board.Color)
}
fieldToUpdate = append(fieldToUpdate, "color")
_, err := e.ID(board.ID).Cols(fieldToUpdate...).Update(board)
return err
}
// GetProjectBoards fetches all boards related to a project
// if no default board set, first board is a temporary "Uncategorized" board
func GetProjectBoards(projectID int64) (ProjectBoardList, error) {
return getProjectBoards(db.GetEngine(db.DefaultContext), projectID)
}
func getProjectBoards(e db.Engine, projectID int64) ([]*ProjectBoard, error) {
boards := make([]*ProjectBoard, 0, 5)
if err := e.Where("project_id=? AND `default`=?", projectID, false).OrderBy("Sorting").Find(&boards); err != nil {
return nil, err
}
defaultB, err := getDefaultBoard(e, projectID)
if err != nil {
return nil, err
}
return append([]*ProjectBoard{defaultB}, boards...), nil
}
// getDefaultBoard return default board and create a dummy if none exist
func getDefaultBoard(e db.Engine, projectID int64) (*ProjectBoard, error) {
var board ProjectBoard
exist, err := e.Where("project_id=? AND `default`=?", projectID, true).Get(&board)
if err != nil {
return nil, err
}
if exist {
return &board, nil
}
// represents a board for issues not assigned to one
return &ProjectBoard{
ProjectID: projectID,
Title: "Uncategorized",
Default: true,
}, nil
}
// SetDefaultBoard represents a board for issues not assigned to one
// if boardID is 0 unset default
func SetDefaultBoard(projectID, boardID int64) error {
_, err := db.GetEngine(db.DefaultContext).Where(builder.Eq{
"project_id": projectID,
"`default`": true,
}).Cols("`default`").Update(&ProjectBoard{Default: false})
if err != nil {
return err
}
if boardID > 0 {
_, err = db.GetEngine(db.DefaultContext).ID(boardID).Where(builder.Eq{"project_id": projectID}).
Cols("`default`").Update(&ProjectBoard{Default: true})
}
return err
}
// LoadIssues load issues assigned to this board
func (b *ProjectBoard) LoadIssues() (IssueList, error) {
issueList := make([]*Issue, 0, 10)
if b.ID != 0 {
issues, err := Issues(&IssuesOptions{
ProjectBoardID: b.ID,
ProjectID: b.ProjectID,
SortType: "project-column-sorting",
})
if err != nil {
return nil, err
}
issueList = issues
}
if b.Default {
issues, err := Issues(&IssuesOptions{
ProjectBoardID: -1, // Issues without ProjectBoardID
ProjectID: b.ProjectID,
SortType: "project-column-sorting",
})
if err != nil {
return nil, err
}
issueList = append(issueList, issues...)
}
if err := IssueList(issueList).LoadComments(); err != nil {
return nil, err
}
b.Issues = issueList
return issueList, nil
}
// LoadIssues load issues assigned to the boards
func (bs ProjectBoardList) LoadIssues() (IssueList, error) {
issues := make(IssueList, 0, len(bs)*10)
for i := range bs {
il, err := bs[i].LoadIssues()
if err != nil {
return nil, err
}
bs[i].Issues = il
issues = append(issues, il...)
}
return issues, nil
}
// UpdateProjectBoardSorting update project board sorting
func UpdateProjectBoardSorting(bs ProjectBoardList) error {
for i := range bs {
_, err := db.GetEngine(db.DefaultContext).ID(bs[i].ID).Cols(
"sorting",
).Update(bs[i])
if err != nil {
return err
}
}
return nil
}

@ -1,218 +0,0 @@
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
)
// ProjectIssue saves relation from issue to a project
type ProjectIssue struct {
ID int64 `xorm:"pk autoincr"`
IssueID int64 `xorm:"INDEX"`
ProjectID int64 `xorm:"INDEX"`
// If 0, then it has not been added to a specific board in the project
ProjectBoardID int64 `xorm:"INDEX"`
Sorting int64 `xorm:"NOT NULL DEFAULT 0"`
}
func init() {
db.RegisterModel(new(ProjectIssue))
}
func deleteProjectIssuesByProjectID(e db.Engine, projectID int64) error {
_, err := e.Where("project_id=?", projectID).Delete(&ProjectIssue{})
return err
}
// ___
// |_ _|___ ___ _ _ ___
// | |/ __/ __| | | |/ _ \
// | |\__ \__ \ |_| | __/
// |___|___/___/\__,_|\___|
// LoadProject load the project the issue was assigned to
func (i *Issue) LoadProject() (err error) {
return i.loadProject(db.GetEngine(db.DefaultContext))
}
func (i *Issue) loadProject(e db.Engine) (err error) {
if i.Project == nil {
var p Project
if _, err = e.Table("project").
Join("INNER", "project_issue", "project.id=project_issue.project_id").
Where("project_issue.issue_id = ?", i.ID).
Get(&p); err != nil {
return err
}
i.Project = &p
}
return
}
// ProjectID return project id if issue was assigned to one
func (i *Issue) ProjectID() int64 {
return i.projectID(db.GetEngine(db.DefaultContext))
}
func (i *Issue) projectID(e db.Engine) int64 {
var ip ProjectIssue
has, err := e.Where("issue_id=?", i.ID).Get(&ip)
if err != nil || !has {
return 0
}
return ip.ProjectID
}
// ProjectBoardID return project board id if issue was assigned to one
func (i *Issue) ProjectBoardID() int64 {
return i.projectBoardID(db.GetEngine(db.DefaultContext))
}
func (i *Issue) projectBoardID(e db.Engine) int64 {
var ip ProjectIssue
has, err := e.Where("issue_id=?", i.ID).Get(&ip)
if err != nil || !has {
return 0
}
return ip.ProjectBoardID
}
// ____ _ _
// | _ \ _ __ ___ (_) ___ ___| |_
// | |_) | '__/ _ \| |/ _ \/ __| __|
// | __/| | | (_) | | __/ (__| |_
// |_| |_| \___// |\___|\___|\__|
// |__/
// NumIssues return counter of all issues assigned to a project
func (p *Project) NumIssues() int {
c, err := db.GetEngine(db.DefaultContext).Table("project_issue").
Where("project_id=?", p.ID).
GroupBy("issue_id").
Cols("issue_id").
Count()
if err != nil {
return 0
}
return int(c)
}
// NumClosedIssues return counter of closed issues assigned to a project
func (p *Project) NumClosedIssues() int {
c, err := db.GetEngine(db.DefaultContext).Table("project_issue").
Join("INNER", "issue", "project_issue.issue_id=issue.id").
Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, true).
Cols("issue_id").
Count()
if err != nil {
return 0
}
return int(c)
}
// NumOpenIssues return counter of open issues assigned to a project
func (p *Project) NumOpenIssues() int {
c, err := db.GetEngine(db.DefaultContext).Table("project_issue").
Join("INNER", "issue", "project_issue.issue_id=issue.id").
Where("project_issue.project_id=? AND issue.is_closed=?", p.ID, false).
Cols("issue_id").
Count()
if err != nil {
return 0
}
return int(c)
}
// ChangeProjectAssign changes the project associated with an issue
func ChangeProjectAssign(issue *Issue, doer *user_model.User, newProjectID int64) error {
ctx, committer, err := db.TxContext()
if err != nil {
return err
}
defer committer.Close()
if err := addUpdateIssueProject(ctx, issue, doer, newProjectID); err != nil {
return err
}
return committer.Commit()
}
func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
e := db.GetEngine(ctx)
oldProjectID := issue.projectID(e)
if _, err := e.Where("project_issue.issue_id=?", issue.ID).Delete(&ProjectIssue{}); err != nil {
return err
}
if err := issue.loadRepo(ctx); err != nil {
return err
}
if oldProjectID > 0 || newProjectID > 0 {
if _, err := createComment(ctx, &CreateCommentOptions{
Type: CommentTypeProject,
Doer: doer,
Repo: issue.Repo,
Issue: issue,
OldProjectID: oldProjectID,
ProjectID: newProjectID,
}); err != nil {
return err
}
}
_, err := e.Insert(&ProjectIssue{
IssueID: issue.ID,
ProjectID: newProjectID,
})
return err
}
// ____ _ _ ____ _
// | _ \ _ __ ___ (_) ___ ___| |_| __ ) ___ __ _ _ __ __| |
// | |_) | '__/ _ \| |/ _ \/ __| __| _ \ / _ \ / _` | '__/ _` |
// | __/| | | (_) | | __/ (__| |_| |_) | (_) | (_| | | | (_| |
// |_| |_| \___// |\___|\___|\__|____/ \___/ \__,_|_| \__,_|
// |__/
// MoveIssuesOnProjectBoard moves or keeps issues in a column and sorts them inside that column
func MoveIssuesOnProjectBoard(board *ProjectBoard, sortedIssueIDs map[int64]int64) error {
return db.WithTx(func(ctx context.Context) error {
sess := db.GetEngine(ctx)
issueIDs := make([]int64, 0, len(sortedIssueIDs))
for _, issueID := range sortedIssueIDs {
issueIDs = append(issueIDs, issueID)
}
count, err := sess.Table(new(ProjectIssue)).Where("project_id=?", board.ProjectID).In("issue_id", issueIDs).Count()
if err != nil {
return err
}
if int(count) != len(sortedIssueIDs) {
return fmt.Errorf("all issues have to be added to a project first")
}
for sorting, issueID := range sortedIssueIDs {
_, err = sess.Exec("UPDATE `project_issue` SET project_board_id=?, sorting=? WHERE issue_id=?", board.ID, sorting, issueID)
if err != nil {
return err
}
}
return nil
})
}
func (pb *ProjectBoard) removeIssues(e db.Engine) error {
_, err := e.Exec("UPDATE `project_issue` SET project_board_id = 0, sorting = 0 WHERE project_board_id = ? ", pb.ID)
return err
}

@ -21,6 +21,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -748,14 +749,14 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
} }
} }
projects, _, err := getProjects(sess, ProjectSearchOptions{ projects, _, err := project_model.GetProjectsCtx(ctx, project_model.SearchOptions{
RepoID: repoID, RepoID: repoID,
}) })
if err != nil { if err != nil {
return fmt.Errorf("get projects: %v", err) return fmt.Errorf("get projects: %v", err)
} }
for i := range projects { for i := range projects {
if err := deleteProjectByID(sess, projects[i].ID); err != nil { if err := project_model.DeleteProjectByIDCtx(ctx, projects[i].ID); err != nil {
return fmt.Errorf("delete project [%d]: %v", projects[i].ID, err) return fmt.Errorf("delete project [%d]: %v", projects[i].ID, err)
} }
} }

@ -9,6 +9,7 @@ import (
"code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/models/webhook"
@ -106,7 +107,7 @@ func GetStatistic() (stats Statistic) {
stats.Counter.HookTask, _ = e.Count(new(webhook.HookTask)) stats.Counter.HookTask, _ = e.Count(new(webhook.HookTask))
stats.Counter.Team, _ = e.Count(new(organization.Team)) stats.Counter.Team, _ = e.Count(new(organization.Team))
stats.Counter.Attachment, _ = e.Count(new(repo_model.Attachment)) stats.Counter.Attachment, _ = e.Count(new(repo_model.Attachment))
stats.Counter.Project, _ = e.Count(new(Project)) stats.Counter.Project, _ = e.Count(new(project_model.Project))
stats.Counter.ProjectBoard, _ = e.Count(new(ProjectBoard)) stats.Counter.ProjectBoard, _ = e.Count(new(project_model.Board))
return return
} }

@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
@ -336,9 +337,9 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
} }
if ctx.Repo.CanWriteIssuesOrPulls(ctx.Params(":type") == "pulls") { if ctx.Repo.CanWriteIssuesOrPulls(ctx.Params(":type") == "pulls") {
projects, _, err := models.GetProjects(models.ProjectSearchOptions{ projects, _, err := project_model.GetProjects(project_model.SearchOptions{
RepoID: repo.ID, RepoID: repo.ID,
Type: models.ProjectTypeRepository, Type: project_model.TypeRepository,
IsClosed: util.OptionalBoolOf(isShowClosed), IsClosed: util.OptionalBoolOf(isShowClosed),
}) })
if err != nil { if err != nil {
@ -446,22 +447,22 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *repo_model.R
func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) { func retrieveProjects(ctx *context.Context, repo *repo_model.Repository) {
var err error var err error
ctx.Data["OpenProjects"], _, err = models.GetProjects(models.ProjectSearchOptions{ ctx.Data["OpenProjects"], _, err = project_model.GetProjects(project_model.SearchOptions{
RepoID: repo.ID, RepoID: repo.ID,
Page: -1, Page: -1,
IsClosed: util.OptionalBoolFalse, IsClosed: util.OptionalBoolFalse,
Type: models.ProjectTypeRepository, Type: project_model.TypeRepository,
}) })
if err != nil { if err != nil {
ctx.ServerError("GetProjects", err) ctx.ServerError("GetProjects", err)
return return
} }
ctx.Data["ClosedProjects"], _, err = models.GetProjects(models.ProjectSearchOptions{ ctx.Data["ClosedProjects"], _, err = project_model.GetProjects(project_model.SearchOptions{
RepoID: repo.ID, RepoID: repo.ID,
Page: -1, Page: -1,
IsClosed: util.OptionalBoolTrue, IsClosed: util.OptionalBoolTrue,
Type: models.ProjectTypeRepository, Type: project_model.TypeRepository,
}) })
if err != nil { if err != nil {
ctx.ServerError("GetProjects", err) ctx.ServerError("GetProjects", err)
@ -814,7 +815,7 @@ func NewIssue(ctx *context.Context) {
projectID := ctx.FormInt64("project") projectID := ctx.FormInt64("project")
if projectID > 0 { if projectID > 0 {
project, err := models.GetProjectByID(projectID) project, err := project_model.GetProjectByID(projectID)
if err != nil { if err != nil {
log.Error("GetProjectByID: %d: %v", projectID, err) log.Error("GetProjectByID: %d: %v", projectID, err)
} else if project.RepoID != ctx.Repo.Repository.ID { } else if project.RepoID != ctx.Repo.Repository.ID {
@ -926,7 +927,7 @@ func ValidateRepoMetas(ctx *context.Context, form forms.CreateIssueForm, isPull
} }
if form.ProjectID > 0 { if form.ProjectID > 0 {
p, err := models.GetProjectByID(form.ProjectID) p, err := project_model.GetProjectByID(form.ProjectID)
if err != nil { if err != nil {
ctx.ServerError("GetProjectByID", err) ctx.ServerError("GetProjectByID", err)
return nil, nil, 0, 0 return nil, nil, 0, 0
@ -1413,7 +1414,7 @@ func ViewIssue(ctx *context.Context) {
return return
} }
ghostProject := &models.Project{ ghostProject := &project_model.Project{
ID: -1, ID: -1,
Title: ctx.Tr("repo.issues.deleted_project"), Title: ctx.Tr("repo.issues.deleted_project"),
} }

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
project_model "code.gitea.io/gitea/models/project"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
@ -69,12 +70,12 @@ func Projects(ctx *context.Context) {
total = repo.NumClosedProjects total = repo.NumClosedProjects
} }
projects, count, err := models.GetProjects(models.ProjectSearchOptions{ projects, count, err := project_model.GetProjects(project_model.SearchOptions{
RepoID: repo.ID, RepoID: repo.ID,
Page: page, Page: page,
IsClosed: util.OptionalBoolOf(isShowClosed), IsClosed: util.OptionalBoolOf(isShowClosed),
SortType: sortType, SortType: sortType,
Type: models.ProjectTypeRepository, Type: project_model.TypeRepository,
}) })
if err != nil { if err != nil {
ctx.ServerError("GetProjects", err) ctx.ServerError("GetProjects", err)
@ -122,7 +123,7 @@ func Projects(ctx *context.Context) {
// NewProject render creating a project page // NewProject render creating a project page
func NewProject(ctx *context.Context) { func NewProject(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.projects.new") ctx.Data["Title"] = ctx.Tr("repo.projects.new")
ctx.Data["ProjectTypes"] = models.GetProjectsConfig() ctx.Data["ProjectTypes"] = project_model.GetProjectsConfig()
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects) ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
ctx.HTML(http.StatusOK, tplProjectsNew) ctx.HTML(http.StatusOK, tplProjectsNew)
} }
@ -134,18 +135,18 @@ func NewProjectPost(ctx *context.Context) {
if ctx.HasError() { if ctx.HasError() {
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects) ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
ctx.Data["ProjectTypes"] = models.GetProjectsConfig() ctx.Data["ProjectTypes"] = project_model.GetProjectsConfig()
ctx.HTML(http.StatusOK, tplProjectsNew) ctx.HTML(http.StatusOK, tplProjectsNew)
return return
} }
if err := models.NewProject(&models.Project{ if err := project_model.NewProject(&project_model.Project{
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
Title: form.Title, Title: form.Title,
Description: form.Content, Description: form.Content,
CreatorID: ctx.Doer.ID, CreatorID: ctx.Doer.ID,
BoardType: form.BoardType, BoardType: form.BoardType,
Type: models.ProjectTypeRepository, Type: project_model.TypeRepository,
}); err != nil { }); err != nil {
ctx.ServerError("NewProject", err) ctx.ServerError("NewProject", err)
return return
@ -168,8 +169,8 @@ func ChangeProjectStatus(ctx *context.Context) {
} }
id := ctx.ParamsInt64(":id") id := ctx.ParamsInt64(":id")
if err := models.ChangeProjectStatusByRepoIDAndID(ctx.Repo.Repository.ID, id, toClose); err != nil { if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx.Repo.Repository.ID, id, toClose); err != nil {
if models.IsErrProjectNotExist(err) { if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", err) ctx.NotFound("", err)
} else { } else {
ctx.ServerError("ChangeProjectStatusByIDAndRepoID", err) ctx.ServerError("ChangeProjectStatusByIDAndRepoID", err)
@ -181,9 +182,9 @@ func ChangeProjectStatus(ctx *context.Context) {
// DeleteProject delete a project // DeleteProject delete a project
func DeleteProject(ctx *context.Context) { func DeleteProject(ctx *context.Context) {
p, err := models.GetProjectByID(ctx.ParamsInt64(":id")) p, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrProjectNotExist(err) { if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil) ctx.NotFound("", nil)
} else { } else {
ctx.ServerError("GetProjectByID", err) ctx.ServerError("GetProjectByID", err)
@ -195,7 +196,7 @@ func DeleteProject(ctx *context.Context) {
return return
} }
if err := models.DeleteProjectByID(p.ID); err != nil { if err := project_model.DeleteProjectByID(p.ID); err != nil {
ctx.Flash.Error("DeleteProjectByID: " + err.Error()) ctx.Flash.Error("DeleteProjectByID: " + err.Error())
} else { } else {
ctx.Flash.Success(ctx.Tr("repo.projects.deletion_success")) ctx.Flash.Success(ctx.Tr("repo.projects.deletion_success"))
@ -212,9 +213,9 @@ func EditProject(ctx *context.Context) {
ctx.Data["PageIsEditProjects"] = true ctx.Data["PageIsEditProjects"] = true
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects) ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
p, err := models.GetProjectByID(ctx.ParamsInt64(":id")) p, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrProjectNotExist(err) { if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil) ctx.NotFound("", nil)
} else { } else {
ctx.ServerError("GetProjectByID", err) ctx.ServerError("GetProjectByID", err)
@ -244,9 +245,9 @@ func EditProjectPost(ctx *context.Context) {
return return
} }
p, err := models.GetProjectByID(ctx.ParamsInt64(":id")) p, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrProjectNotExist(err) { if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil) ctx.NotFound("", nil)
} else { } else {
ctx.ServerError("GetProjectByID", err) ctx.ServerError("GetProjectByID", err)
@ -260,7 +261,7 @@ func EditProjectPost(ctx *context.Context) {
p.Title = form.Title p.Title = form.Title
p.Description = form.Content p.Description = form.Content
if err = models.UpdateProject(p); err != nil { if err = project_model.UpdateProject(p); err != nil {
ctx.ServerError("UpdateProjects", err) ctx.ServerError("UpdateProjects", err)
return return
} }
@ -271,9 +272,9 @@ func EditProjectPost(ctx *context.Context) {
// ViewProject renders the project board for a project // ViewProject renders the project board for a project
func ViewProject(ctx *context.Context) { func ViewProject(ctx *context.Context) {
project, err := models.GetProjectByID(ctx.ParamsInt64(":id")) project, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrProjectNotExist(err) { if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil) ctx.NotFound("", nil)
} else { } else {
ctx.ServerError("GetProjectByID", err) ctx.ServerError("GetProjectByID", err)
@ -285,7 +286,7 @@ func ViewProject(ctx *context.Context) {
return return
} }
boards, err := models.GetProjectBoards(project.ID) boards, err := project_model.GetBoards(project.ID)
if err != nil { if err != nil {
ctx.ServerError("GetProjectBoards", err) ctx.ServerError("GetProjectBoards", err)
return return
@ -295,14 +296,15 @@ func ViewProject(ctx *context.Context) {
boards[0].Title = ctx.Tr("repo.projects.type.uncategorized") boards[0].Title = ctx.Tr("repo.projects.type.uncategorized")
} }
issueList, err := boards.LoadIssues() issuesMap, err := models.LoadIssuesFromBoardList(boards)
if err != nil { if err != nil {
ctx.ServerError("LoadIssuesOfBoards", err) ctx.ServerError("LoadIssuesOfBoards", err)
return return
} }
linkedPrsMap := make(map[int64][]*models.Issue) linkedPrsMap := make(map[int64][]*models.Issue)
for _, issue := range issueList { for _, issuesList := range issuesMap {
for _, issue := range issuesList {
var referencedIds []int64 var referencedIds []int64
for _, comment := range issue.Comments { for _, comment := range issue.Comments {
if comment.RefIssueID != 0 && comment.RefIsPull { if comment.RefIssueID != 0 && comment.RefIsPull {
@ -319,6 +321,7 @@ func ViewProject(ctx *context.Context) {
} }
} }
} }
}
ctx.Data["LinkedPRs"] = linkedPrsMap ctx.Data["LinkedPRs"] = linkedPrsMap
project.RenderedContent, err = markdown.RenderString(&markup.RenderContext{ project.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
@ -335,6 +338,7 @@ func ViewProject(ctx *context.Context) {
ctx.Data["IsProjectsPage"] = true ctx.Data["IsProjectsPage"] = true
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects) ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
ctx.Data["Project"] = project ctx.Data["Project"] = project
ctx.Data["IssuesMap"] = issuesMap
ctx.Data["Boards"] = boards ctx.Data["Boards"] = boards
ctx.HTML(http.StatusOK, tplProjectsView) ctx.HTML(http.StatusOK, tplProjectsView)
@ -381,9 +385,9 @@ func DeleteProjectBoard(ctx *context.Context) {
return return
} }
project, err := models.GetProjectByID(ctx.ParamsInt64(":id")) project, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrProjectNotExist(err) { if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil) ctx.NotFound("", nil)
} else { } else {
ctx.ServerError("GetProjectByID", err) ctx.ServerError("GetProjectByID", err)
@ -391,7 +395,7 @@ func DeleteProjectBoard(ctx *context.Context) {
return return
} }
pb, err := models.GetProjectBoard(ctx.ParamsInt64(":boardID")) pb, err := project_model.GetBoard(ctx.ParamsInt64(":boardID"))
if err != nil { if err != nil {
ctx.ServerError("GetProjectBoard", err) ctx.ServerError("GetProjectBoard", err)
return return
@ -410,7 +414,7 @@ func DeleteProjectBoard(ctx *context.Context) {
return return
} }
if err := models.DeleteProjectBoardByID(ctx.ParamsInt64(":boardID")); err != nil { if err := project_model.DeleteBoardByID(ctx.ParamsInt64(":boardID")); err != nil {
ctx.ServerError("DeleteProjectBoardByID", err) ctx.ServerError("DeleteProjectBoardByID", err)
return return
} }
@ -430,9 +434,9 @@ func AddBoardToProjectPost(ctx *context.Context) {
return return
} }
project, err := models.GetProjectByID(ctx.ParamsInt64(":id")) project, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrProjectNotExist(err) { if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil) ctx.NotFound("", nil)
} else { } else {
ctx.ServerError("GetProjectByID", err) ctx.ServerError("GetProjectByID", err)
@ -440,7 +444,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
return return
} }
if err := models.NewProjectBoard(&models.ProjectBoard{ if err := project_model.NewBoard(&project_model.Board{
ProjectID: project.ID, ProjectID: project.ID,
Title: form.Title, Title: form.Title,
Color: form.Color, Color: form.Color,
@ -455,7 +459,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
}) })
} }
func checkProjectBoardChangePermissions(ctx *context.Context) (*models.Project, *models.ProjectBoard) { func checkProjectBoardChangePermissions(ctx *context.Context) (*project_model.Project, *project_model.Board) {
if ctx.Doer == nil { if ctx.Doer == nil {
ctx.JSON(http.StatusForbidden, map[string]string{ ctx.JSON(http.StatusForbidden, map[string]string{
"message": "Only signed in users are allowed to perform this action.", "message": "Only signed in users are allowed to perform this action.",
@ -470,9 +474,9 @@ func checkProjectBoardChangePermissions(ctx *context.Context) (*models.Project,
return nil, nil return nil, nil
} }
project, err := models.GetProjectByID(ctx.ParamsInt64(":id")) project, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrProjectNotExist(err) { if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("", nil) ctx.NotFound("", nil)
} else { } else {
ctx.ServerError("GetProjectByID", err) ctx.ServerError("GetProjectByID", err)
@ -480,7 +484,7 @@ func checkProjectBoardChangePermissions(ctx *context.Context) (*models.Project,
return nil, nil return nil, nil
} }
board, err := models.GetProjectBoard(ctx.ParamsInt64(":boardID")) board, err := project_model.GetBoard(ctx.ParamsInt64(":boardID"))
if err != nil { if err != nil {
ctx.ServerError("GetProjectBoard", err) ctx.ServerError("GetProjectBoard", err)
return nil, nil return nil, nil
@ -519,7 +523,7 @@ func EditProjectBoard(ctx *context.Context) {
board.Sorting = form.Sorting board.Sorting = form.Sorting
} }
if err := models.UpdateProjectBoard(board); err != nil { if err := project_model.UpdateBoard(board); err != nil {
ctx.ServerError("UpdateProjectBoard", err) ctx.ServerError("UpdateProjectBoard", err)
return return
} }
@ -536,7 +540,7 @@ func SetDefaultProjectBoard(ctx *context.Context) {
return return
} }
if err := models.SetDefaultBoard(project.ID, board.ID); err != nil { if err := project_model.SetDefaultBoard(project.ID, board.ID); err != nil {
ctx.ServerError("SetDefaultBoard", err) ctx.ServerError("SetDefaultBoard", err)
return return
} }
@ -562,9 +566,9 @@ func MoveIssues(ctx *context.Context) {
return return
} }
project, err := models.GetProjectByID(ctx.ParamsInt64(":id")) project, err := project_model.GetProjectByID(ctx.ParamsInt64(":id"))
if err != nil { if err != nil {
if models.IsErrProjectNotExist(err) { if project_model.IsErrProjectNotExist(err) {
ctx.NotFound("ProjectNotExist", nil) ctx.NotFound("ProjectNotExist", nil)
} else { } else {
ctx.ServerError("GetProjectByID", err) ctx.ServerError("GetProjectByID", err)
@ -576,19 +580,18 @@ func MoveIssues(ctx *context.Context) {
return return
} }
var board *models.ProjectBoard var board *project_model.Board
if ctx.ParamsInt64(":boardID") == 0 { if ctx.ParamsInt64(":boardID") == 0 {
board = &models.ProjectBoard{ board = &project_model.Board{
ID: 0, ID: 0,
ProjectID: project.ID, ProjectID: project.ID,
Title: ctx.Tr("repo.projects.type.uncategorized"), Title: ctx.Tr("repo.projects.type.uncategorized"),
} }
} else { } else {
// column board, err = project_model.GetBoard(ctx.ParamsInt64(":boardID"))
board, err = models.GetProjectBoard(ctx.ParamsInt64(":boardID"))
if err != nil { if err != nil {
if models.IsErrProjectBoardNotExist(err) { if project_model.IsErrProjectBoardNotExist(err) {
ctx.NotFound("ProjectBoardNotExist", nil) ctx.NotFound("ProjectBoardNotExist", nil)
} else { } else {
ctx.ServerError("GetProjectBoard", err) ctx.ServerError("GetProjectBoard", err)
@ -634,7 +637,7 @@ func MoveIssues(ctx *context.Context) {
return return
} }
if err = models.MoveIssuesOnProjectBoard(board, sortedIssueIDs); err != nil { if err = project_model.MoveIssuesOnProjectBoard(board, sortedIssueIDs); err != nil {
ctx.ServerError("MoveIssuesOnProjectBoard", err) ctx.ServerError("MoveIssuesOnProjectBoard", err)
return return
} }
@ -647,8 +650,43 @@ func MoveIssues(ctx *context.Context) {
// CreateProject renders the generic project creation page // CreateProject renders the generic project creation page
func CreateProject(ctx *context.Context) { func CreateProject(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.projects.new") ctx.Data["Title"] = ctx.Tr("repo.projects.new")
ctx.Data["ProjectTypes"] = models.GetProjectsConfig() ctx.Data["ProjectTypes"] = project_model.GetProjectsConfig()
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects) ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
ctx.HTML(http.StatusOK, tplGenericProjectsNew) ctx.HTML(http.StatusOK, tplGenericProjectsNew)
} }
// CreateProjectPost creates an individual and/or organization project
func CreateProjectPost(ctx *context.Context, form forms.UserCreateProjectForm) {
user := checkContextUser(ctx, form.UID)
if ctx.Written() {
return
}
ctx.Data["ContextUser"] = user
if ctx.HasError() {
ctx.Data["CanWriteProjects"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
ctx.HTML(http.StatusOK, tplGenericProjectsNew)
return
}
projectType := project_model.TypeIndividual
if user.IsOrganization() {
projectType = project_model.TypeOrganization
}
if err := project_model.NewProject(&project_model.Project{
Title: form.Title,
Description: form.Content,
CreatorID: user.ID,
BoardType: form.BoardType,
Type: projectType,
}); err != nil {
ctx.ServerError("NewProject", err)
return
}
ctx.Flash.Success(ctx.Tr("repo.projects.create_success", form.Title))
ctx.Redirect(setting.AppSubURL + "/")
}

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
@ -216,10 +217,10 @@ func Profile(ctx *context.Context) {
total = int(count) total = int(count)
case "projects": case "projects":
ctx.Data["OpenProjects"], _, err = models.GetProjects(models.ProjectSearchOptions{ ctx.Data["OpenProjects"], _, err = project_model.GetProjects(project_model.SearchOptions{
Page: -1, Page: -1,
IsClosed: util.OptionalBoolFalse, IsClosed: util.OptionalBoolFalse,
Type: models.ProjectTypeIndividual, Type: project_model.TypeIndividual,
}) })
if err != nil { if err != nil {
ctx.ServerError("GetProjects", err) ctx.ServerError("GetProjects", err)

@ -11,6 +11,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
project_model "code.gitea.io/gitea/models/project"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
@ -499,7 +500,7 @@ func (i IssueLockForm) HasValidReason() bool {
type CreateProjectForm struct { type CreateProjectForm struct {
Title string `binding:"Required;MaxSize(100)"` Title string `binding:"Required;MaxSize(100)"`
Content string Content string
BoardType models.ProjectBoardType BoardType project_model.BoardType
} }
// UserCreateProjectForm is a from for creating an individual or organization // UserCreateProjectForm is a from for creating an individual or organization
@ -507,7 +508,7 @@ type CreateProjectForm struct {
type UserCreateProjectForm struct { type UserCreateProjectForm struct {
Title string `binding:"Required;MaxSize(100)"` Title string `binding:"Required;MaxSize(100)"`
Content string Content string
BoardType models.ProjectBoardType BoardType project_model.BoardType
UID int64 `binding:"Required"` UID int64 `binding:"Required"`
} }

@ -84,7 +84,7 @@
<div class="board-column-header df ac sb"> <div class="board-column-header df ac sb">
<div class="ui large label board-label py-2"> <div class="ui large label board-label py-2">
<div class="ui small circular grey label board-card-cnt"> <div class="ui small circular grey label board-card-cnt">
{{len .Issues}} {{.NumIssues}}
</div> </div>
{{.Title}} {{.Title}}
</div> </div>
@ -175,7 +175,7 @@
<div class="ui cards board" data-url="{{$.RepoLink}}/projects/{{$.Project.ID}}/{{.ID}}" data-project="{{$.Project.ID}}" data-board="{{.ID}}" id="board_{{.ID}}"> <div class="ui cards board" data-url="{{$.RepoLink}}/projects/{{$.Project.ID}}/{{.ID}}" data-project="{{$.Project.ID}}" data-board="{{.ID}}" id="board_{{.ID}}">
{{ range .Issues }} {{ range (index $.IssuesMap .ID) }}
<!-- start issue card --> <!-- start issue card -->
<div class="card board-card" data-issue="{{.ID}}"> <div class="card board-card" data-issue="{{.ID}}">

Loading…
Cancel
Save