2020-08-17 06:07:38 +03:00
// 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 repo
import (
"fmt"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
2021-01-26 18:36:53 +03:00
auth "code.gitea.io/gitea/modules/forms"
2020-08-17 06:07:38 +03:00
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
2021-01-26 18:36:53 +03:00
"code.gitea.io/gitea/modules/web"
2020-08-17 06:07:38 +03:00
)
const (
tplProjects base . TplName = "repo/projects/list"
tplProjectsNew base . TplName = "repo/projects/new"
tplProjectsView base . TplName = "repo/projects/view"
tplGenericProjectsNew base . TplName = "user/project"
)
// MustEnableProjects check if projects are enabled in settings
func MustEnableProjects ( ctx * context . Context ) {
if models . UnitTypeProjects . UnitGlobalDisabled ( ) {
ctx . NotFound ( "EnableKanbanBoard" , nil )
return
}
if ctx . Repo . Repository != nil {
if ! ctx . Repo . CanRead ( models . UnitTypeProjects ) {
ctx . NotFound ( "MustEnableProjects" , nil )
return
}
}
}
// Projects renders the home page of projects
func Projects ( ctx * context . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "repo.project_board" )
sortType := ctx . QueryTrim ( "sort" )
isShowClosed := strings . ToLower ( ctx . QueryTrim ( "state" ) ) == "closed"
repo := ctx . Repo . Repository
page := ctx . QueryInt ( "page" )
if page <= 1 {
page = 1
}
ctx . Data [ "OpenCount" ] = repo . NumOpenProjects
ctx . Data [ "ClosedCount" ] = repo . NumClosedProjects
var total int
if ! isShowClosed {
total = repo . NumOpenProjects
} else {
total = repo . NumClosedProjects
}
projects , count , err := models . GetProjects ( models . ProjectSearchOptions {
RepoID : repo . ID ,
Page : page ,
IsClosed : util . OptionalBoolOf ( isShowClosed ) ,
SortType : sortType ,
Type : models . ProjectTypeRepository ,
} )
if err != nil {
ctx . ServerError ( "GetProjects" , err )
return
}
for i := range projects {
projects [ i ] . RenderedContent = string ( markdown . Render ( [ ] byte ( projects [ i ] . Description ) , ctx . Repo . RepoLink , ctx . Repo . Repository . ComposeMetas ( ) ) )
}
ctx . Data [ "Projects" ] = projects
if isShowClosed {
ctx . Data [ "State" ] = "closed"
} else {
ctx . Data [ "State" ] = "open"
}
numPages := 0
if count > 0 {
numPages = int ( ( int ( count ) - 1 ) / setting . UI . IssuePagingNum )
}
pager := context . NewPagination ( total , setting . UI . IssuePagingNum , page , numPages )
pager . AddParam ( ctx , "state" , "State" )
ctx . Data [ "Page" ] = pager
2020-08-22 09:58:59 +03:00
ctx . Data [ "CanWriteProjects" ] = ctx . Repo . Permission . CanWrite ( models . UnitTypeProjects )
2020-08-17 06:07:38 +03:00
ctx . Data [ "IsShowClosed" ] = isShowClosed
ctx . Data [ "IsProjectsPage" ] = true
ctx . Data [ "SortType" ] = sortType
ctx . HTML ( 200 , tplProjects )
}
// NewProject render creating a project page
func NewProject ( ctx * context . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "repo.projects.new" )
ctx . Data [ "ProjectTypes" ] = models . GetProjectsConfig ( )
2020-08-22 09:58:59 +03:00
ctx . Data [ "CanWriteProjects" ] = ctx . Repo . Permission . CanWrite ( models . UnitTypeProjects )
2020-08-17 06:07:38 +03:00
ctx . HTML ( 200 , tplProjectsNew )
}
2020-08-22 09:58:59 +03:00
// NewProjectPost creates a new project
2021-01-26 18:36:53 +03:00
func NewProjectPost ( ctx * context . Context ) {
form := web . GetForm ( ctx ) . ( * auth . CreateProjectForm )
2020-08-17 06:07:38 +03:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.projects.new" )
if ctx . HasError ( ) {
2020-08-22 09:58:59 +03:00
ctx . Data [ "CanWriteProjects" ] = ctx . Repo . Permission . CanWrite ( models . UnitTypeProjects )
ctx . Data [ "ProjectTypes" ] = models . GetProjectsConfig ( )
2020-08-17 06:07:38 +03:00
ctx . HTML ( 200 , tplProjectsNew )
return
}
if err := models . NewProject ( & models . Project {
RepoID : ctx . Repo . Repository . ID ,
Title : form . Title ,
Description : form . Content ,
CreatorID : ctx . User . ID ,
BoardType : form . BoardType ,
Type : models . ProjectTypeRepository ,
} ) ; err != nil {
ctx . ServerError ( "NewProject" , err )
return
}
ctx . Flash . Success ( ctx . Tr ( "repo.projects.create_success" , form . Title ) )
ctx . Redirect ( ctx . Repo . RepoLink + "/projects" )
}
// ChangeProjectStatus updates the status of a project between "open" and "close"
func ChangeProjectStatus ( ctx * context . Context ) {
toClose := false
switch ctx . Params ( ":action" ) {
case "open" :
toClose = false
case "close" :
toClose = true
default :
ctx . Redirect ( ctx . Repo . RepoLink + "/projects" )
}
id := ctx . ParamsInt64 ( ":id" )
if err := models . ChangeProjectStatusByRepoIDAndID ( ctx . Repo . Repository . ID , id , toClose ) ; err != nil {
if models . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "" , err )
} else {
ctx . ServerError ( "ChangeProjectStatusByIDAndRepoID" , err )
}
return
}
ctx . Redirect ( ctx . Repo . RepoLink + "/projects?state=" + ctx . Params ( ":action" ) )
}
// DeleteProject delete a project
func DeleteProject ( ctx * context . Context ) {
p , err := models . GetProjectByID ( ctx . ParamsInt64 ( ":id" ) )
if err != nil {
if models . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetProjectByID" , err )
}
return
}
if p . RepoID != ctx . Repo . Repository . ID {
ctx . NotFound ( "" , nil )
return
}
if err := models . DeleteProjectByID ( p . ID ) ; err != nil {
ctx . Flash . Error ( "DeleteProjectByID: " + err . Error ( ) )
} else {
ctx . Flash . Success ( ctx . Tr ( "repo.projects.deletion_success" ) )
}
ctx . JSON ( 200 , map [ string ] interface { } {
"redirect" : ctx . Repo . RepoLink + "/projects" ,
} )
}
// EditProject allows a project to be edited
func EditProject ( ctx * context . Context ) {
ctx . Data [ "Title" ] = ctx . Tr ( "repo.projects.edit" )
ctx . Data [ "PageIsProjects" ] = true
ctx . Data [ "PageIsEditProjects" ] = true
2020-08-22 09:58:59 +03:00
ctx . Data [ "CanWriteProjects" ] = ctx . Repo . Permission . CanWrite ( models . UnitTypeProjects )
2020-08-17 06:07:38 +03:00
p , err := models . GetProjectByID ( ctx . ParamsInt64 ( ":id" ) )
if err != nil {
if models . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetProjectByID" , err )
}
return
}
if p . RepoID != ctx . Repo . Repository . ID {
ctx . NotFound ( "" , nil )
return
}
ctx . Data [ "title" ] = p . Title
ctx . Data [ "content" ] = p . Description
ctx . HTML ( 200 , tplProjectsNew )
}
// EditProjectPost response for editing a project
2021-01-26 18:36:53 +03:00
func EditProjectPost ( ctx * context . Context ) {
form := web . GetForm ( ctx ) . ( * auth . CreateProjectForm )
2020-08-17 06:07:38 +03:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.projects.edit" )
ctx . Data [ "PageIsProjects" ] = true
ctx . Data [ "PageIsEditProjects" ] = true
2020-08-22 09:58:59 +03:00
ctx . Data [ "CanWriteProjects" ] = ctx . Repo . Permission . CanWrite ( models . UnitTypeProjects )
2020-08-17 06:07:38 +03:00
if ctx . HasError ( ) {
2020-08-22 09:58:59 +03:00
ctx . HTML ( 200 , tplProjectsNew )
2020-08-17 06:07:38 +03:00
return
}
p , err := models . GetProjectByID ( ctx . ParamsInt64 ( ":id" ) )
if err != nil {
if models . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetProjectByID" , err )
}
return
}
if p . RepoID != ctx . Repo . Repository . ID {
ctx . NotFound ( "" , nil )
return
}
p . Title = form . Title
p . Description = form . Content
if err = models . UpdateProject ( p ) ; err != nil {
ctx . ServerError ( "UpdateProjects" , err )
return
}
ctx . Flash . Success ( ctx . Tr ( "repo.projects.edit_success" , p . Title ) )
ctx . Redirect ( ctx . Repo . RepoLink + "/projects" )
}
// ViewProject renders the project board for a project
func ViewProject ( ctx * context . Context ) {
project , err := models . GetProjectByID ( ctx . ParamsInt64 ( ":id" ) )
if err != nil {
if models . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetProjectByID" , err )
}
return
}
if project . RepoID != ctx . Repo . Repository . ID {
ctx . NotFound ( "" , nil )
return
}
boards , err := models . GetProjectBoards ( project . ID )
if err != nil {
ctx . ServerError ( "GetProjectBoards" , err )
return
}
2021-01-15 23:29:32 +03:00
if boards [ 0 ] . ID == 0 {
boards [ 0 ] . Title = ctx . Tr ( "repo.projects.type.uncategorized" )
}
2020-08-17 06:07:38 +03:00
2021-01-20 22:53:48 +03:00
issueList , err := boards . LoadIssues ( )
if err != nil {
2020-08-17 06:07:38 +03:00
ctx . ServerError ( "LoadIssuesOfBoards" , err )
return
}
2021-01-20 22:53:48 +03:00
ctx . Data [ "Issues" ] = issueList
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 )
}
}
if len ( referencedIds ) > 0 {
if linkedPrs , err := models . Issues ( & models . IssuesOptions {
IssueIDs : referencedIds ,
IsPull : util . OptionalBoolTrue ,
} ) ; err == nil {
linkedPrsMap [ issue . ID ] = linkedPrs
}
}
}
ctx . Data [ "LinkedPRs" ] = linkedPrsMap
2020-08-17 06:07:38 +03:00
2020-11-10 06:46:19 +03:00
project . RenderedContent = string ( markdown . Render ( [ ] byte ( project . Description ) , ctx . Repo . RepoLink , ctx . Repo . Repository . ComposeMetas ( ) ) )
2020-08-22 09:58:59 +03:00
ctx . Data [ "CanWriteProjects" ] = ctx . Repo . Permission . CanWrite ( models . UnitTypeProjects )
2020-08-17 06:07:38 +03:00
ctx . Data [ "Project" ] = project
2021-01-15 23:29:32 +03:00
ctx . Data [ "Boards" ] = boards
2020-08-17 06:07:38 +03:00
ctx . Data [ "PageIsProjects" ] = true
ctx . Data [ "RequiresDraggable" ] = true
ctx . HTML ( 200 , tplProjectsView )
}
// UpdateIssueProject change an issue's project
func UpdateIssueProject ( ctx * context . Context ) {
issues := getActionIssues ( ctx )
if ctx . Written ( ) {
return
}
projectID := ctx . QueryInt64 ( "id" )
for _ , issue := range issues {
oldProjectID := issue . ProjectID ( )
if oldProjectID == projectID {
continue
}
if err := models . ChangeProjectAssign ( issue , ctx . User , projectID ) ; err != nil {
ctx . ServerError ( "ChangeProjectAssign" , err )
return
}
}
ctx . JSON ( 200 , map [ string ] interface { } {
"ok" : true ,
} )
}
// DeleteProjectBoard allows for the deletion of a project board
func DeleteProjectBoard ( ctx * context . Context ) {
if ctx . User == nil {
ctx . JSON ( 403 , map [ string ] string {
"message" : "Only signed in users are allowed to perform this action." ,
} )
return
}
if ! ctx . Repo . IsOwner ( ) && ! ctx . Repo . IsAdmin ( ) && ! ctx . Repo . CanAccess ( models . AccessModeWrite , models . UnitTypeProjects ) {
ctx . JSON ( 403 , map [ string ] string {
"message" : "Only authorized users are allowed to perform this action." ,
} )
return
}
project , err := models . GetProjectByID ( ctx . ParamsInt64 ( ":id" ) )
if err != nil {
if models . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetProjectByID" , err )
}
return
}
pb , err := models . GetProjectBoard ( ctx . ParamsInt64 ( ":boardID" ) )
if err != nil {
2021-01-14 23:27:22 +03:00
ctx . ServerError ( "GetProjectBoard" , err )
2020-08-17 06:07:38 +03:00
return
}
if pb . ProjectID != ctx . ParamsInt64 ( ":id" ) {
ctx . JSON ( 422 , map [ string ] string {
"message" : fmt . Sprintf ( "ProjectBoard[%d] is not in Project[%d] as expected" , pb . ID , project . ID ) ,
} )
return
}
if project . RepoID != ctx . Repo . Repository . ID {
ctx . JSON ( 422 , map [ string ] string {
"message" : fmt . Sprintf ( "ProjectBoard[%d] is not in Repository[%d] as expected" , pb . ID , ctx . Repo . Repository . ID ) ,
} )
return
}
if err := models . DeleteProjectBoardByID ( ctx . ParamsInt64 ( ":boardID" ) ) ; err != nil {
ctx . ServerError ( "DeleteProjectBoardByID" , err )
return
}
ctx . JSON ( 200 , map [ string ] interface { } {
"ok" : true ,
} )
}
// AddBoardToProjectPost allows a new board to be added to a project.
2021-01-26 18:36:53 +03:00
func AddBoardToProjectPost ( ctx * context . Context ) {
2021-02-11 19:32:27 +03:00
form := web . GetForm ( ctx ) . ( * auth . EditProjectBoardForm )
2020-08-17 06:07:38 +03:00
if ! ctx . Repo . IsOwner ( ) && ! ctx . Repo . IsAdmin ( ) && ! ctx . Repo . CanAccess ( models . AccessModeWrite , models . UnitTypeProjects ) {
ctx . JSON ( 403 , map [ string ] string {
"message" : "Only authorized users are allowed to perform this action." ,
} )
return
}
project , err := models . GetProjectByID ( ctx . ParamsInt64 ( ":id" ) )
if err != nil {
if models . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetProjectByID" , err )
}
return
}
if err := models . NewProjectBoard ( & models . ProjectBoard {
ProjectID : project . ID ,
Title : form . Title ,
CreatorID : ctx . User . ID ,
} ) ; err != nil {
ctx . ServerError ( "NewProjectBoard" , err )
return
}
ctx . JSON ( 200 , map [ string ] interface { } {
"ok" : true ,
} )
}
2021-01-15 23:29:32 +03:00
func checkProjectBoardChangePermissions ( ctx * context . Context ) ( * models . Project , * models . ProjectBoard ) {
2020-08-17 06:07:38 +03:00
if ctx . User == nil {
ctx . JSON ( 403 , map [ string ] string {
"message" : "Only signed in users are allowed to perform this action." ,
} )
2021-01-15 23:29:32 +03:00
return nil , nil
2020-08-17 06:07:38 +03:00
}
if ! ctx . Repo . IsOwner ( ) && ! ctx . Repo . IsAdmin ( ) && ! ctx . Repo . CanAccess ( models . AccessModeWrite , models . UnitTypeProjects ) {
ctx . JSON ( 403 , map [ string ] string {
"message" : "Only authorized users are allowed to perform this action." ,
} )
2021-01-15 23:29:32 +03:00
return nil , nil
2020-08-17 06:07:38 +03:00
}
project , err := models . GetProjectByID ( ctx . ParamsInt64 ( ":id" ) )
if err != nil {
if models . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetProjectByID" , err )
}
2021-01-15 23:29:32 +03:00
return nil , nil
2020-08-17 06:07:38 +03:00
}
board , err := models . GetProjectBoard ( ctx . ParamsInt64 ( ":boardID" ) )
if err != nil {
2021-01-14 23:27:22 +03:00
ctx . ServerError ( "GetProjectBoard" , err )
2021-01-15 23:29:32 +03:00
return nil , nil
2020-08-17 06:07:38 +03:00
}
if board . ProjectID != ctx . ParamsInt64 ( ":id" ) {
ctx . JSON ( 422 , map [ string ] string {
"message" : fmt . Sprintf ( "ProjectBoard[%d] is not in Project[%d] as expected" , board . ID , project . ID ) ,
} )
2021-01-15 23:29:32 +03:00
return nil , nil
2020-08-17 06:07:38 +03:00
}
if project . RepoID != ctx . Repo . Repository . ID {
ctx . JSON ( 422 , map [ string ] string {
"message" : fmt . Sprintf ( "ProjectBoard[%d] is not in Repository[%d] as expected" , board . ID , ctx . Repo . Repository . ID ) ,
} )
2021-01-15 23:29:32 +03:00
return nil , nil
}
return project , board
}
2021-02-11 19:32:27 +03:00
// EditProjectBoard allows a project board's to be updated
func EditProjectBoard ( ctx * context . Context ) {
form := web . GetForm ( ctx ) . ( * auth . EditProjectBoardForm )
2021-01-15 23:29:32 +03:00
_ , board := checkProjectBoardChangePermissions ( ctx )
if ctx . Written ( ) {
2020-08-17 06:07:38 +03:00
return
}
if form . Title != "" {
board . Title = form . Title
}
2021-02-11 19:32:27 +03:00
if form . Sorting != 0 {
board . Sorting = form . Sorting
}
2020-08-17 06:07:38 +03:00
if err := models . UpdateProjectBoard ( board ) ; err != nil {
ctx . ServerError ( "UpdateProjectBoard" , err )
2021-01-15 23:29:32 +03:00
return
}
ctx . JSON ( 200 , map [ string ] interface { } {
"ok" : true ,
} )
}
// SetDefaultProjectBoard set default board for uncategorized issues/pulls
func SetDefaultProjectBoard ( ctx * context . Context ) {
project , board := checkProjectBoardChangePermissions ( ctx )
if ctx . Written ( ) {
return
}
if err := models . SetDefaultBoard ( project . ID , board . ID ) ; err != nil {
ctx . ServerError ( "SetDefaultBoard" , err )
2020-08-17 06:07:38 +03:00
return
}
ctx . JSON ( 200 , map [ string ] interface { } {
"ok" : true ,
} )
}
// MoveIssueAcrossBoards move a card from one board to another in a project
func MoveIssueAcrossBoards ( ctx * context . Context ) {
if ctx . User == nil {
ctx . JSON ( 403 , map [ string ] string {
"message" : "Only signed in users are allowed to perform this action." ,
} )
return
}
if ! ctx . Repo . IsOwner ( ) && ! ctx . Repo . IsAdmin ( ) && ! ctx . Repo . CanAccess ( models . AccessModeWrite , models . UnitTypeProjects ) {
ctx . JSON ( 403 , map [ string ] string {
"message" : "Only authorized users are allowed to perform this action." ,
} )
return
}
p , err := models . GetProjectByID ( ctx . ParamsInt64 ( ":id" ) )
if err != nil {
if models . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetProjectByID" , err )
}
return
}
if p . RepoID != ctx . Repo . Repository . ID {
ctx . NotFound ( "" , nil )
return
}
var board * models . ProjectBoard
if ctx . ParamsInt64 ( ":boardID" ) == 0 {
board = & models . ProjectBoard {
ID : 0 ,
ProjectID : 0 ,
Title : ctx . Tr ( "repo.projects.type.uncategorized" ) ,
}
} else {
board , err = models . GetProjectBoard ( ctx . ParamsInt64 ( ":boardID" ) )
if err != nil {
if models . IsErrProjectBoardNotExist ( err ) {
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetProjectBoard" , err )
}
return
}
if board . ProjectID != p . ID {
ctx . NotFound ( "" , nil )
return
}
}
issue , err := models . GetIssueByID ( ctx . ParamsInt64 ( ":index" ) )
if err != nil {
if models . IsErrIssueNotExist ( err ) {
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetIssueByID" , err )
}
return
}
if err := models . MoveIssueAcrossProjectBoards ( issue , board ) ; err != nil {
ctx . ServerError ( "MoveIssueAcrossProjectBoards" , err )
return
}
ctx . JSON ( 200 , map [ string ] interface { } {
"ok" : true ,
} )
}
// 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 ( )
2020-08-22 09:58:59 +03:00
ctx . Data [ "CanWriteProjects" ] = ctx . Repo . Permission . CanWrite ( models . UnitTypeProjects )
2020-08-17 06:07:38 +03:00
ctx . HTML ( 200 , tplGenericProjectsNew )
}
// CreateProjectPost creates an individual and/or organization project
func CreateProjectPost ( ctx * context . Context , form auth . UserCreateProjectForm ) {
user := checkContextUser ( ctx , form . UID )
if ctx . Written ( ) {
return
}
ctx . Data [ "ContextUser" ] = user
if ctx . HasError ( ) {
2020-08-22 09:58:59 +03:00
ctx . Data [ "CanWriteProjects" ] = ctx . Repo . Permission . CanWrite ( models . UnitTypeProjects )
2020-08-17 06:07:38 +03:00
ctx . HTML ( 200 , tplGenericProjectsNew )
return
}
var projectType = models . ProjectTypeIndividual
if user . IsOrganization ( ) {
projectType = models . ProjectTypeOrganization
}
if err := models . NewProject ( & models . 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 + "/" )
}