2023-01-20 14:42:33 +03:00
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package org
import (
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
2023-11-24 06:49:41 +03:00
"code.gitea.io/gitea/models/db"
2023-01-20 14:42:33 +03:00
issues_model "code.gitea.io/gitea/models/issues"
project_model "code.gitea.io/gitea/models/project"
2023-04-11 21:28:40 +03:00
attachment_model "code.gitea.io/gitea/models/repo"
2023-01-20 14:42:33 +03:00
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
"code.gitea.io/gitea/services/forms"
)
const (
2023-05-31 09:50:18 +03:00
tplProjects base . TplName = "org/projects/list"
tplProjectsNew base . TplName = "org/projects/new"
tplProjectsView base . TplName = "org/projects/view"
2023-01-20 14:42:33 +03:00
)
// MustEnableProjects check if projects are enabled in settings
func MustEnableProjects ( ctx * context . Context ) {
if unit . TypeProjects . UnitGlobalDisabled ( ) {
ctx . NotFound ( "EnableKanbanBoard" , nil )
return
}
}
// Projects renders the home page of projects
func Projects ( ctx * context . Context ) {
2023-07-06 21:59:24 +03:00
shared_user . PrepareContextForProfileBigAvatar ( ctx )
2023-01-20 14:42:33 +03:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.project_board" )
sortType := ctx . FormTrim ( "sort" )
isShowClosed := strings . ToLower ( ctx . FormTrim ( "state" ) ) == "closed"
2023-08-12 13:30:28 +03:00
keyword := ctx . FormTrim ( "q" )
2023-01-20 14:42:33 +03:00
page := ctx . FormInt ( "page" )
if page <= 1 {
page = 1
}
2023-03-17 16:07:23 +03:00
var projectType project_model . Type
if ctx . ContextUser . IsOrganization ( ) {
projectType = project_model . TypeOrganization
} else {
projectType = project_model . TypeIndividual
}
2023-11-24 06:49:41 +03:00
projects , total , err := db . FindAndCount [ project_model . Project ] ( ctx , project_model . SearchOptions {
ListOptions : db . ListOptions {
Page : page ,
PageSize : setting . UI . IssuePagingNum ,
} ,
2023-01-20 14:42:33 +03:00
OwnerID : ctx . ContextUser . ID ,
IsClosed : util . OptionalBoolOf ( isShowClosed ) ,
2023-07-11 21:47:50 +03:00
OrderBy : project_model . GetSearchOrderByBySortType ( sortType ) ,
2023-03-17 16:07:23 +03:00
Type : projectType ,
2023-08-12 13:30:28 +03:00
Title : keyword ,
2023-01-20 14:42:33 +03:00
} )
if err != nil {
ctx . ServerError ( "FindProjects" , err )
return
}
2023-11-24 06:49:41 +03:00
opTotal , err := db . Count [ project_model . Project ] ( ctx , project_model . SearchOptions {
2023-01-20 14:42:33 +03:00
OwnerID : ctx . ContextUser . ID ,
IsClosed : util . OptionalBoolOf ( ! isShowClosed ) ,
2023-03-17 16:07:23 +03:00
Type : projectType ,
2023-01-20 14:42:33 +03:00
} )
if err != nil {
ctx . ServerError ( "CountProjects" , err )
return
}
if isShowClosed {
ctx . Data [ "OpenCount" ] = opTotal
ctx . Data [ "ClosedCount" ] = total
} else {
ctx . Data [ "OpenCount" ] = total
ctx . Data [ "ClosedCount" ] = opTotal
}
ctx . Data [ "Projects" ] = projects
shared_user . RenderUserHeader ( ctx )
if isShowClosed {
ctx . Data [ "State" ] = "closed"
} else {
ctx . Data [ "State" ] = "open"
}
for _ , project := range projects {
project . RenderedContent = project . Description
}
2023-08-11 20:08:05 +03:00
err = shared_user . LoadHeaderCount ( ctx )
if err != nil {
ctx . ServerError ( "LoadHeaderCount" , err )
return
}
2023-01-20 14:42:33 +03:00
numPages := 0
if total > 0 {
numPages = ( int ( total ) - 1 / setting . UI . IssuePagingNum )
}
pager := context . NewPagination ( int ( total ) , setting . UI . IssuePagingNum , page , numPages )
pager . AddParam ( ctx , "state" , "State" )
ctx . Data [ "Page" ] = pager
2023-03-09 22:57:20 +03:00
ctx . Data [ "CanWriteProjects" ] = canWriteProjects ( ctx )
2023-01-20 14:42:33 +03:00
ctx . Data [ "IsShowClosed" ] = isShowClosed
ctx . Data [ "PageIsViewProjects" ] = true
ctx . Data [ "SortType" ] = sortType
ctx . HTML ( http . StatusOK , tplProjects )
}
2023-03-09 22:57:20 +03:00
func canWriteProjects ( ctx * context . Context ) bool {
2023-01-20 14:42:33 +03:00
if ctx . ContextUser . IsOrganization ( ) {
return ctx . Org . CanWriteUnit ( ctx , unit . TypeProjects )
}
return ctx . Doer != nil && ctx . ContextUser . ID == ctx . Doer . ID
}
2023-05-31 09:50:18 +03:00
// RenderNewProject render creating a project page
func RenderNewProject ( ctx * context . Context ) {
2023-01-20 14:42:33 +03:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.projects.new" )
2023-02-11 11:12:41 +03:00
ctx . Data [ "BoardTypes" ] = project_model . GetBoardConfig ( )
2023-04-11 21:28:40 +03:00
ctx . Data [ "CardTypes" ] = project_model . GetCardConfig ( )
2023-03-09 22:57:20 +03:00
ctx . Data [ "CanWriteProjects" ] = canWriteProjects ( ctx )
2023-03-10 18:18:20 +03:00
ctx . Data [ "PageIsViewProjects" ] = true
2023-01-20 14:42:33 +03:00
ctx . Data [ "HomeLink" ] = ctx . ContextUser . HomeLink ( )
2023-05-31 09:50:18 +03:00
ctx . Data [ "CancelLink" ] = ctx . ContextUser . HomeLink ( ) + "/-/projects"
2023-01-20 14:42:33 +03:00
shared_user . RenderUserHeader ( ctx )
2023-08-11 20:08:05 +03:00
err := shared_user . LoadHeaderCount ( ctx )
if err != nil {
ctx . ServerError ( "LoadHeaderCount" , err )
return
}
2023-01-20 14:42:33 +03:00
ctx . HTML ( http . StatusOK , tplProjectsNew )
}
// NewProjectPost creates a new project
func NewProjectPost ( ctx * context . Context ) {
form := web . GetForm ( ctx ) . ( * forms . CreateProjectForm )
ctx . Data [ "Title" ] = ctx . Tr ( "repo.projects.new" )
shared_user . RenderUserHeader ( ctx )
if ctx . HasError ( ) {
2023-05-31 09:50:18 +03:00
RenderNewProject ( ctx )
2023-01-20 14:42:33 +03:00
return
}
2023-03-17 16:07:23 +03:00
newProject := project_model . Project {
2023-01-20 14:42:33 +03:00
OwnerID : ctx . ContextUser . ID ,
Title : form . Title ,
Description : form . Content ,
CreatorID : ctx . Doer . ID ,
BoardType : form . BoardType ,
2023-04-11 21:28:40 +03:00
CardType : form . CardType ,
2023-03-17 16:07:23 +03:00
}
if ctx . ContextUser . IsOrganization ( ) {
newProject . Type = project_model . TypeOrganization
} else {
newProject . Type = project_model . TypeIndividual
}
2023-09-29 15:12:54 +03:00
if err := project_model . NewProject ( ctx , & newProject ) ; err != nil {
2023-01-20 14:42:33 +03:00
ctx . ServerError ( "NewProject" , err )
return
}
ctx . Flash . Success ( ctx . Tr ( "repo.projects.create_success" , form . Title ) )
ctx . Redirect ( ctx . ContextUser . HomeLink ( ) + "/-/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 :
2023-05-08 15:19:27 +03:00
ctx . Redirect ( ctx . ContextUser . HomeLink ( ) + "/-/projects" )
2023-01-20 14:42:33 +03:00
}
id := ctx . ParamsInt64 ( ":id" )
2023-09-29 15:12:54 +03:00
if err := project_model . ChangeProjectStatusByRepoIDAndID ( ctx , 0 , id , toClose ) ; err != nil {
2023-01-20 14:42:33 +03:00
if project_model . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "" , err )
} else {
2023-05-08 15:19:27 +03:00
ctx . ServerError ( "ChangeProjectStatusByRepoIDAndID" , err )
2023-01-20 14:42:33 +03:00
}
return
}
2023-05-08 15:19:27 +03:00
ctx . Redirect ( ctx . ContextUser . HomeLink ( ) + "/-/projects?state=" + url . QueryEscape ( ctx . Params ( ":action" ) ) )
2023-01-20 14:42:33 +03:00
}
// DeleteProject delete a project
func DeleteProject ( ctx * context . Context ) {
p , err := project_model . GetProjectByID ( ctx , ctx . ParamsInt64 ( ":id" ) )
if err != nil {
if project_model . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetProjectByID" , err )
}
return
}
2023-03-09 19:59:50 +03:00
if p . OwnerID != ctx . ContextUser . ID {
2023-01-20 14:42:33 +03:00
ctx . NotFound ( "" , nil )
return
}
if err := project_model . DeleteProjectByID ( ctx , p . ID ) ; err != nil {
ctx . Flash . Error ( "DeleteProjectByID: " + err . Error ( ) )
} else {
ctx . Flash . Success ( ctx . Tr ( "repo.projects.deletion_success" ) )
}
2023-07-26 09:04:01 +03:00
ctx . JSONRedirect ( ctx . ContextUser . HomeLink ( ) + "/-/projects" )
2023-01-20 14:42:33 +03:00
}
2023-05-31 09:50:18 +03:00
// RenderEditProject allows a project to be edited
func RenderEditProject ( ctx * context . Context ) {
2023-01-20 14:42:33 +03:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.projects.edit" )
ctx . Data [ "PageIsEditProjects" ] = true
ctx . Data [ "PageIsViewProjects" ] = true
2023-03-09 22:57:20 +03:00
ctx . Data [ "CanWriteProjects" ] = canWriteProjects ( ctx )
2023-04-11 21:28:40 +03:00
ctx . Data [ "CardTypes" ] = project_model . GetCardConfig ( )
2023-01-20 14:42:33 +03:00
shared_user . RenderUserHeader ( ctx )
p , err := project_model . GetProjectByID ( ctx , ctx . ParamsInt64 ( ":id" ) )
if err != nil {
if project_model . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetProjectByID" , err )
}
return
}
2023-03-09 19:59:50 +03:00
if p . OwnerID != ctx . ContextUser . ID {
2023-01-20 14:42:33 +03:00
ctx . NotFound ( "" , nil )
return
}
2023-03-24 11:37:56 +03:00
ctx . Data [ "projectID" ] = p . ID
2023-01-20 14:42:33 +03:00
ctx . Data [ "title" ] = p . Title
ctx . Data [ "content" ] = p . Description
2023-03-09 17:38:29 +03:00
ctx . Data [ "redirect" ] = ctx . FormString ( "redirect" )
2023-03-24 11:37:56 +03:00
ctx . Data [ "HomeLink" ] = ctx . ContextUser . HomeLink ( )
2023-04-11 21:28:40 +03:00
ctx . Data [ "card_type" ] = p . CardType
2023-05-31 09:50:18 +03:00
ctx . Data [ "CancelLink" ] = fmt . Sprintf ( "%s/-/projects/%d" , ctx . ContextUser . HomeLink ( ) , p . ID )
2023-01-20 14:42:33 +03:00
ctx . HTML ( http . StatusOK , tplProjectsNew )
}
// EditProjectPost response for editing a project
func EditProjectPost ( ctx * context . Context ) {
form := web . GetForm ( ctx ) . ( * forms . CreateProjectForm )
2023-05-31 09:50:18 +03:00
projectID := ctx . ParamsInt64 ( ":id" )
2023-01-20 14:42:33 +03:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.projects.edit" )
ctx . Data [ "PageIsEditProjects" ] = true
ctx . Data [ "PageIsViewProjects" ] = true
2023-03-09 22:57:20 +03:00
ctx . Data [ "CanWriteProjects" ] = canWriteProjects ( ctx )
2023-04-11 21:28:40 +03:00
ctx . Data [ "CardTypes" ] = project_model . GetCardConfig ( )
2023-05-31 09:50:18 +03:00
ctx . Data [ "CancelLink" ] = fmt . Sprintf ( "%s/-/projects/%d" , ctx . ContextUser . HomeLink ( ) , projectID )
2023-04-11 21:28:40 +03:00
2023-01-20 14:42:33 +03:00
shared_user . RenderUserHeader ( ctx )
2023-08-11 20:08:05 +03:00
err := shared_user . LoadHeaderCount ( ctx )
if err != nil {
ctx . ServerError ( "LoadHeaderCount" , err )
return
}
2023-01-20 14:42:33 +03:00
if ctx . HasError ( ) {
ctx . HTML ( http . StatusOK , tplProjectsNew )
return
}
2023-05-31 09:50:18 +03:00
p , err := project_model . GetProjectByID ( ctx , projectID )
2023-01-20 14:42:33 +03:00
if err != nil {
if project_model . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetProjectByID" , err )
}
return
}
2023-03-09 19:59:50 +03:00
if p . OwnerID != ctx . ContextUser . ID {
2023-01-20 14:42:33 +03:00
ctx . NotFound ( "" , nil )
return
}
p . Title = form . Title
p . Description = form . Content
2023-04-11 21:28:40 +03:00
p . CardType = form . CardType
2023-01-20 14:42:33 +03:00
if err = project_model . UpdateProject ( ctx , p ) ; err != nil {
ctx . ServerError ( "UpdateProjects" , err )
return
}
ctx . Flash . Success ( ctx . Tr ( "repo.projects.edit_success" , p . Title ) )
2023-03-09 17:38:29 +03:00
if ctx . FormString ( "redirect" ) == "project" {
2023-09-29 15:12:54 +03:00
ctx . Redirect ( p . Link ( ctx ) )
2023-03-09 17:38:29 +03:00
} else {
ctx . Redirect ( ctx . ContextUser . HomeLink ( ) + "/-/projects" )
}
2023-01-20 14:42:33 +03:00
}
// ViewProject renders the project board for a project
func ViewProject ( ctx * context . Context ) {
project , err := project_model . GetProjectByID ( ctx , ctx . ParamsInt64 ( ":id" ) )
if err != nil {
if project_model . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetProjectByID" , err )
}
return
}
if project . OwnerID != ctx . ContextUser . ID {
ctx . NotFound ( "" , nil )
return
}
2023-04-09 17:07:23 +03:00
boards , err := project . GetBoards ( ctx )
2023-01-20 14:42:33 +03:00
if err != nil {
ctx . ServerError ( "GetProjectBoards" , err )
return
}
if boards [ 0 ] . ID == 0 {
2024-02-15 00:48:45 +03:00
boards [ 0 ] . Title = ctx . Locale . TrString ( "repo.projects.type.uncategorized" )
2023-01-20 14:42:33 +03:00
}
issuesMap , err := issues_model . LoadIssuesFromBoardList ( ctx , boards )
if err != nil {
ctx . ServerError ( "LoadIssuesOfBoards" , err )
return
}
2023-04-11 21:28:40 +03:00
if project . CardType != project_model . CardTypeTextOnly {
issuesAttachmentMap := make ( map [ int64 ] [ ] * attachment_model . Attachment )
for _ , issuesList := range issuesMap {
for _ , issue := range issuesList {
if issueAttachment , err := attachment_model . GetAttachmentsByIssueIDImagesLatest ( ctx , issue . ID ) ; err == nil {
issuesAttachmentMap [ issue . ID ] = issueAttachment
}
}
}
ctx . Data [ "issuesAttachmentMap" ] = issuesAttachmentMap
}
2023-01-20 14:42:33 +03:00
linkedPrsMap := make ( map [ int64 ] [ ] * issues_model . Issue )
for _ , issuesList := range issuesMap {
for _ , issue := range issuesList {
2024-02-14 21:19:57 +03:00
var referencedIDs [ ] int64
2023-01-20 14:42:33 +03:00
for _ , comment := range issue . Comments {
if comment . RefIssueID != 0 && comment . RefIsPull {
2024-02-14 21:19:57 +03:00
referencedIDs = append ( referencedIDs , comment . RefIssueID )
2023-01-20 14:42:33 +03:00
}
}
2024-02-14 21:19:57 +03:00
if len ( referencedIDs ) > 0 {
2023-01-20 14:42:33 +03:00
if linkedPrs , err := issues_model . Issues ( ctx , & issues_model . IssuesOptions {
2024-02-14 21:19:57 +03:00
IssueIDs : referencedIDs ,
2023-01-20 14:42:33 +03:00
IsPull : util . OptionalBoolTrue ,
} ) ; err == nil {
linkedPrsMap [ issue . ID ] = linkedPrs
}
}
}
}
project . RenderedContent = project . Description
ctx . Data [ "LinkedPRs" ] = linkedPrsMap
ctx . Data [ "PageIsViewProjects" ] = true
2023-03-09 22:57:20 +03:00
ctx . Data [ "CanWriteProjects" ] = canWriteProjects ( ctx )
2023-01-20 14:42:33 +03:00
ctx . Data [ "Project" ] = project
ctx . Data [ "IssuesMap" ] = issuesMap
2023-08-12 13:30:28 +03:00
ctx . Data [ "Columns" ] = boards // TODO: rename boards to columns in backend
2023-01-20 14:42:33 +03:00
shared_user . RenderUserHeader ( ctx )
2023-08-11 20:08:05 +03:00
err = shared_user . LoadHeaderCount ( ctx )
if err != nil {
ctx . ServerError ( "LoadHeaderCount" , err )
return
}
2023-01-20 14:42:33 +03:00
ctx . HTML ( http . StatusOK , tplProjectsView )
}
2023-06-24 18:31:28 +03:00
func getActionIssues ( ctx * context . Context ) issues_model . IssueList {
2023-01-20 14:42:33 +03:00
commaSeparatedIssueIDs := ctx . FormString ( "issue_ids" )
if len ( commaSeparatedIssueIDs ) == 0 {
return nil
}
issueIDs := make ( [ ] int64 , 0 , 10 )
for _ , stringIssueID := range strings . Split ( commaSeparatedIssueIDs , "," ) {
issueID , err := strconv . ParseInt ( stringIssueID , 10 , 64 )
if err != nil {
ctx . ServerError ( "ParseInt" , err )
return nil
}
issueIDs = append ( issueIDs , issueID )
}
issues , err := issues_model . GetIssuesByIDs ( ctx , issueIDs )
if err != nil {
ctx . ServerError ( "GetIssuesByIDs" , err )
return nil
}
// Check access rights for all issues
issueUnitEnabled := ctx . Repo . CanRead ( unit . TypeIssues )
prUnitEnabled := ctx . Repo . CanRead ( unit . TypePullRequests )
for _ , issue := range issues {
if issue . RepoID != ctx . Repo . Repository . ID {
ctx . NotFound ( "some issue's RepoID is incorrect" , errors . New ( "some issue's RepoID is incorrect" ) )
return nil
}
if issue . IsPull && ! prUnitEnabled || ! issue . IsPull && ! issueUnitEnabled {
ctx . NotFound ( "IssueOrPullRequestUnitNotAllowed" , nil )
return nil
}
if err = issue . LoadAttributes ( ctx ) ; err != nil {
ctx . ServerError ( "LoadAttributes" , err )
return nil
}
}
return issues
}
// UpdateIssueProject change an issue's project
func UpdateIssueProject ( ctx * context . Context ) {
issues := getActionIssues ( ctx )
if ctx . Written ( ) {
return
}
2023-06-24 18:31:28 +03:00
if err := issues . LoadProjects ( ctx ) ; err != nil {
ctx . ServerError ( "LoadProjects" , err )
return
}
2023-01-20 14:42:33 +03:00
projectID := ctx . FormInt64 ( "id" )
for _ , issue := range issues {
2023-07-04 13:26:24 +03:00
if issue . Project != nil {
2023-07-12 17:10:35 +03:00
if issue . Project . ID == projectID {
2023-07-04 13:26:24 +03:00
continue
}
2023-01-20 14:42:33 +03:00
}
2023-10-11 07:24:07 +03:00
if err := issues_model . ChangeProjectAssign ( ctx , issue , ctx . Doer , projectID ) ; err != nil {
2023-01-20 14:42:33 +03:00
ctx . ServerError ( "ChangeProjectAssign" , err )
return
}
}
2023-07-26 09:04:01 +03:00
ctx . JSONOK ( )
2023-01-20 14:42:33 +03:00
}
// DeleteProjectBoard allows for the deletion of a project board
func DeleteProjectBoard ( ctx * context . Context ) {
if ctx . Doer == nil {
ctx . JSON ( http . StatusForbidden , map [ string ] string {
"message" : "Only signed in users are allowed to perform this action." ,
} )
return
}
project , err := project_model . GetProjectByID ( ctx , ctx . ParamsInt64 ( ":id" ) )
if err != nil {
if project_model . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetProjectByID" , err )
}
return
}
pb , err := project_model . GetBoard ( ctx , ctx . ParamsInt64 ( ":boardID" ) )
if err != nil {
ctx . ServerError ( "GetProjectBoard" , err )
return
}
if pb . ProjectID != ctx . ParamsInt64 ( ":id" ) {
ctx . JSON ( http . StatusUnprocessableEntity , map [ string ] string {
"message" : fmt . Sprintf ( "ProjectBoard[%d] is not in Project[%d] as expected" , pb . ID , project . ID ) ,
} )
return
}
if project . OwnerID != ctx . ContextUser . ID {
ctx . JSON ( http . StatusUnprocessableEntity , map [ string ] string {
"message" : fmt . Sprintf ( "ProjectBoard[%d] is not in Owner[%d] as expected" , pb . ID , ctx . ContextUser . ID ) ,
} )
return
}
2023-09-29 15:12:54 +03:00
if err := project_model . DeleteBoardByID ( ctx , ctx . ParamsInt64 ( ":boardID" ) ) ; err != nil {
2023-01-20 14:42:33 +03:00
ctx . ServerError ( "DeleteProjectBoardByID" , err )
return
}
2023-07-26 09:04:01 +03:00
ctx . JSONOK ( )
2023-01-20 14:42:33 +03:00
}
// AddBoardToProjectPost allows a new board to be added to a project.
func AddBoardToProjectPost ( ctx * context . Context ) {
form := web . GetForm ( ctx ) . ( * forms . EditProjectBoardForm )
project , err := project_model . GetProjectByID ( ctx , ctx . ParamsInt64 ( ":id" ) )
if err != nil {
if project_model . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetProjectByID" , err )
}
return
}
2023-09-29 15:12:54 +03:00
if err := project_model . NewBoard ( ctx , & project_model . Board {
2023-01-20 14:42:33 +03:00
ProjectID : project . ID ,
Title : form . Title ,
Color : form . Color ,
CreatorID : ctx . Doer . ID ,
} ) ; err != nil {
ctx . ServerError ( "NewProjectBoard" , err )
return
}
2023-07-26 09:04:01 +03:00
ctx . JSONOK ( )
2023-01-20 14:42:33 +03:00
}
// CheckProjectBoardChangePermissions check permission
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." ,
} )
return nil , nil
}
project , err := project_model . GetProjectByID ( ctx , ctx . ParamsInt64 ( ":id" ) )
if err != nil {
if project_model . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "" , nil )
} else {
ctx . ServerError ( "GetProjectByID" , err )
}
return nil , nil
}
board , err := project_model . GetBoard ( ctx , ctx . ParamsInt64 ( ":boardID" ) )
if err != nil {
ctx . ServerError ( "GetProjectBoard" , err )
return nil , nil
}
if board . ProjectID != ctx . ParamsInt64 ( ":id" ) {
ctx . JSON ( http . StatusUnprocessableEntity , map [ string ] string {
"message" : fmt . Sprintf ( "ProjectBoard[%d] is not in Project[%d] as expected" , board . ID , project . ID ) ,
} )
return nil , nil
}
if project . OwnerID != ctx . ContextUser . ID {
ctx . JSON ( http . StatusUnprocessableEntity , map [ string ] string {
"message" : fmt . Sprintf ( "ProjectBoard[%d] is not in Repository[%d] as expected" , board . ID , project . ID ) ,
} )
return nil , nil
}
return project , board
}
// EditProjectBoard allows a project board's to be updated
func EditProjectBoard ( ctx * context . Context ) {
form := web . GetForm ( ctx ) . ( * forms . EditProjectBoardForm )
_ , board := CheckProjectBoardChangePermissions ( ctx )
if ctx . Written ( ) {
return
}
if form . Title != "" {
board . Title = form . Title
}
board . Color = form . Color
if form . Sorting != 0 {
board . Sorting = form . Sorting
}
if err := project_model . UpdateBoard ( ctx , board ) ; err != nil {
ctx . ServerError ( "UpdateProjectBoard" , err )
return
}
2023-07-26 09:04:01 +03:00
ctx . JSONOK ( )
2023-01-20 14:42:33 +03:00
}
// SetDefaultProjectBoard set default board for uncategorized issues/pulls
func SetDefaultProjectBoard ( ctx * context . Context ) {
project , board := CheckProjectBoardChangePermissions ( ctx )
if ctx . Written ( ) {
return
}
2023-09-29 15:12:54 +03:00
if err := project_model . SetDefaultBoard ( ctx , project . ID , board . ID ) ; err != nil {
2023-01-20 14:42:33 +03:00
ctx . ServerError ( "SetDefaultBoard" , err )
return
}
2023-07-26 09:04:01 +03:00
ctx . JSONOK ( )
2023-01-20 14:42:33 +03:00
}
2023-04-19 17:28:28 +03:00
// UnsetDefaultProjectBoard unset default board for uncategorized issues/pulls
func UnsetDefaultProjectBoard ( ctx * context . Context ) {
project , _ := CheckProjectBoardChangePermissions ( ctx )
if ctx . Written ( ) {
return
}
2023-09-29 15:12:54 +03:00
if err := project_model . SetDefaultBoard ( ctx , project . ID , 0 ) ; err != nil {
2023-04-19 17:28:28 +03:00
ctx . ServerError ( "SetDefaultBoard" , err )
return
}
2023-07-26 09:04:01 +03:00
ctx . JSONOK ( )
2023-04-19 17:28:28 +03:00
}
2023-01-20 14:42:33 +03:00
// MoveIssues moves or keeps issues in a column and sorts them inside that column
func MoveIssues ( ctx * context . Context ) {
if ctx . Doer == nil {
ctx . JSON ( http . StatusForbidden , map [ string ] string {
"message" : "Only signed in users are allowed to perform this action." ,
} )
return
}
project , err := project_model . GetProjectByID ( ctx , ctx . ParamsInt64 ( ":id" ) )
if err != nil {
if project_model . IsErrProjectNotExist ( err ) {
ctx . NotFound ( "ProjectNotExist" , nil )
} else {
ctx . ServerError ( "GetProjectByID" , err )
}
return
}
if project . OwnerID != ctx . ContextUser . ID {
ctx . NotFound ( "InvalidRepoID" , nil )
return
}
var board * project_model . Board
if ctx . ParamsInt64 ( ":boardID" ) == 0 {
board = & project_model . Board {
ID : 0 ,
ProjectID : project . ID ,
2024-02-15 00:48:45 +03:00
Title : ctx . Locale . TrString ( "repo.projects.type.uncategorized" ) ,
2023-01-20 14:42:33 +03:00
}
} else {
board , err = project_model . GetBoard ( ctx , ctx . ParamsInt64 ( ":boardID" ) )
if err != nil {
if project_model . IsErrProjectBoardNotExist ( err ) {
ctx . NotFound ( "ProjectBoardNotExist" , nil )
} else {
ctx . ServerError ( "GetProjectBoard" , err )
}
return
}
if board . ProjectID != project . ID {
ctx . NotFound ( "BoardNotInProject" , nil )
return
}
}
type movedIssuesForm struct {
Issues [ ] struct {
IssueID int64 ` json:"issueID" `
Sorting int64 ` json:"sorting" `
} ` json:"issues" `
}
form := & movedIssuesForm { }
if err = json . NewDecoder ( ctx . Req . Body ) . Decode ( & form ) ; err != nil {
ctx . ServerError ( "DecodeMovedIssuesForm" , err )
}
issueIDs := make ( [ ] int64 , 0 , len ( form . Issues ) )
sortedIssueIDs := make ( map [ int64 ] int64 )
for _ , issue := range form . Issues {
issueIDs = append ( issueIDs , issue . IssueID )
sortedIssueIDs [ issue . Sorting ] = issue . IssueID
}
movedIssues , err := issues_model . GetIssuesByIDs ( ctx , issueIDs )
if err != nil {
if issues_model . IsErrIssueNotExist ( err ) {
ctx . NotFound ( "IssueNotExisting" , nil )
} else {
ctx . ServerError ( "GetIssueByID" , err )
}
return
}
if len ( movedIssues ) != len ( form . Issues ) {
ctx . ServerError ( "some issues do not exist" , errors . New ( "some issues do not exist" ) )
return
}
if _ , err = movedIssues . LoadRepositories ( ctx ) ; err != nil {
ctx . ServerError ( "LoadRepositories" , err )
return
}
for _ , issue := range movedIssues {
if issue . RepoID != project . RepoID && issue . Repo . OwnerID != project . OwnerID {
ctx . ServerError ( "Some issue's repoID is not equal to project's repoID" , errors . New ( "Some issue's repoID is not equal to project's repoID" ) )
return
}
}
2023-09-29 15:12:54 +03:00
if err = project_model . MoveIssuesOnProjectBoard ( ctx , board , sortedIssueIDs ) ; err != nil {
2023-01-20 14:42:33 +03:00
ctx . ServerError ( "MoveIssuesOnProjectBoard" , err )
return
}
2023-07-26 09:04:01 +03:00
ctx . JSONOK ( )
2023-01-20 14:42:33 +03:00
}