2023-01-20 19:42:33 +08:00
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package org
import (
"errors"
"fmt"
"net/http"
"strings"
2023-11-24 11:49:41 +08:00
"code.gitea.io/gitea/models/db"
2023-01-20 19:42:33 +08:00
issues_model "code.gitea.io/gitea/models/issues"
2024-09-12 06:53:40 +03:00
org_model "code.gitea.io/gitea/models/organization"
2023-01-20 19:42:33 +08:00
project_model "code.gitea.io/gitea/models/project"
2023-04-12 02:28:40 +08:00
attachment_model "code.gitea.io/gitea/models/repo"
2023-01-20 19:42:33 +08:00
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/json"
2024-03-02 16:42:31 +01:00
"code.gitea.io/gitea/modules/optional"
2023-01-20 19:42:33 +08:00
"code.gitea.io/gitea/modules/setting"
2024-03-01 15:11:51 +08:00
"code.gitea.io/gitea/modules/templates"
2023-01-20 19:42:33 +08:00
"code.gitea.io/gitea/modules/web"
shared_user "code.gitea.io/gitea/routers/web/shared/user"
2024-02-27 15:12:22 +08:00
"code.gitea.io/gitea/services/context"
2023-01-20 19:42:33 +08:00
"code.gitea.io/gitea/services/forms"
2024-08-09 09:29:02 +08:00
project_service "code.gitea.io/gitea/services/projects"
2023-01-20 19:42:33 +08:00
)
const (
2023-05-31 08:50:18 +02:00
tplProjects base . TplName = "org/projects/list"
tplProjectsNew base . TplName = "org/projects/new"
tplProjectsView base . TplName = "org/projects/view"
2023-01-20 19:42:33 +08:00
)
// MustEnableProjects check if projects are enabled in settings
func MustEnableProjects ( ctx * context . Context ) {
if unit . TypeProjects . UnitGlobalDisabled ( ) {
2024-05-27 16:59:54 +08:00
ctx . NotFound ( "EnableProjects" , nil )
2023-01-20 19:42:33 +08:00
return
}
}
// Projects renders the home page of projects
func Projects ( ctx * context . Context ) {
2023-07-07 00:29:24 +05:30
shared_user . PrepareContextForProfileBigAvatar ( ctx )
2024-05-27 16:59:54 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.projects" )
2023-01-20 19:42:33 +08:00
sortType := ctx . FormTrim ( "sort" )
isShowClosed := strings . ToLower ( ctx . FormTrim ( "state" ) ) == "closed"
2023-08-12 12:30:28 +02:00
keyword := ctx . FormTrim ( "q" )
2023-01-20 19:42:33 +08:00
page := ctx . FormInt ( "page" )
if page <= 1 {
page = 1
}
2023-03-17 22:07:23 +09:00
var projectType project_model . Type
if ctx . ContextUser . IsOrganization ( ) {
projectType = project_model . TypeOrganization
} else {
projectType = project_model . TypeIndividual
}
2023-11-24 11:49:41 +08: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 19:42:33 +08:00
OwnerID : ctx . ContextUser . ID ,
2024-03-02 16:42:31 +01:00
IsClosed : optional . Some ( isShowClosed ) ,
2023-07-12 03:47:50 +09:00
OrderBy : project_model . GetSearchOrderByBySortType ( sortType ) ,
2023-03-17 22:07:23 +09:00
Type : projectType ,
2023-08-12 12:30:28 +02:00
Title : keyword ,
2023-01-20 19:42:33 +08:00
} )
if err != nil {
ctx . ServerError ( "FindProjects" , err )
return
}
2023-11-24 11:49:41 +08:00
opTotal , err := db . Count [ project_model . Project ] ( ctx , project_model . SearchOptions {
2023-01-20 19:42:33 +08:00
OwnerID : ctx . ContextUser . ID ,
2024-03-02 16:42:31 +01:00
IsClosed : optional . Some ( ! isShowClosed ) ,
2023-03-17 22:07:23 +09:00
Type : projectType ,
2023-01-20 19:42:33 +08: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"
}
2024-11-05 14:04:26 +08:00
renderUtils := templates . NewRenderUtils ( ctx )
2023-01-20 19:42:33 +08:00
for _ , project := range projects {
2024-11-05 14:04:26 +08:00
project . RenderedContent = renderUtils . MarkdownToHtml ( project . Description )
2023-01-20 19:42:33 +08:00
}
2023-08-11 19:08:05 +02:00
err = shared_user . LoadHeaderCount ( ctx )
if err != nil {
ctx . ServerError ( "LoadHeaderCount" , err )
return
}
2023-01-20 19:42:33 +08:00
numPages := 0
if total > 0 {
numPages = ( int ( total ) - 1 / setting . UI . IssuePagingNum )
}
pager := context . NewPagination ( int ( total ) , setting . UI . IssuePagingNum , page , numPages )
2024-03-16 20:07:56 +08:00
pager . AddParamString ( "state" , fmt . Sprint ( ctx . Data [ "State" ] ) )
2023-01-20 19:42:33 +08:00
ctx . Data [ "Page" ] = pager
2023-03-10 04:57:20 +09:00
ctx . Data [ "CanWriteProjects" ] = canWriteProjects ( ctx )
2023-01-20 19:42:33 +08:00
ctx . Data [ "IsShowClosed" ] = isShowClosed
ctx . Data [ "PageIsViewProjects" ] = true
ctx . Data [ "SortType" ] = sortType
ctx . HTML ( http . StatusOK , tplProjects )
}
2023-03-10 04:57:20 +09:00
func canWriteProjects ( ctx * context . Context ) bool {
2023-01-20 19:42:33 +08: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 08:50:18 +02:00
// RenderNewProject render creating a project page
func RenderNewProject ( ctx * context . Context ) {
2023-01-20 19:42:33 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.projects.new" )
2024-05-27 16:59:54 +08:00
ctx . Data [ "TemplateConfigs" ] = project_model . GetTemplateConfigs ( )
2023-04-12 02:28:40 +08:00
ctx . Data [ "CardTypes" ] = project_model . GetCardConfig ( )
2023-03-10 04:57:20 +09:00
ctx . Data [ "CanWriteProjects" ] = canWriteProjects ( ctx )
2023-03-11 00:18:20 +09:00
ctx . Data [ "PageIsViewProjects" ] = true
2023-01-20 19:42:33 +08:00
ctx . Data [ "HomeLink" ] = ctx . ContextUser . HomeLink ( )
2023-05-31 08:50:18 +02:00
ctx . Data [ "CancelLink" ] = ctx . ContextUser . HomeLink ( ) + "/-/projects"
2023-01-20 19:42:33 +08:00
shared_user . RenderUserHeader ( ctx )
2023-08-11 19:08:05 +02:00
err := shared_user . LoadHeaderCount ( ctx )
if err != nil {
ctx . ServerError ( "LoadHeaderCount" , err )
return
}
2023-01-20 19:42:33 +08: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 08:50:18 +02:00
RenderNewProject ( ctx )
2023-01-20 19:42:33 +08:00
return
}
2023-03-17 22:07:23 +09:00
newProject := project_model . Project {
2024-05-27 16:59:54 +08:00
OwnerID : ctx . ContextUser . ID ,
Title : form . Title ,
Description : form . Content ,
CreatorID : ctx . Doer . ID ,
TemplateType : form . TemplateType ,
CardType : form . CardType ,
2023-03-17 22:07:23 +09:00
}
if ctx . ContextUser . IsOrganization ( ) {
newProject . Type = project_model . TypeOrganization
} else {
newProject . Type = project_model . TypeIndividual
}
2023-09-29 14:12:54 +02:00
if err := project_model . NewProject ( ctx , & newProject ) ; err != nil {
2023-01-20 19:42:33 +08: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 ) {
2024-04-14 01:17:01 +09:00
var toClose bool
2024-06-19 06:32:45 +08:00
switch ctx . PathParam ( ":action" ) {
2023-01-20 19:42:33 +08:00
case "open" :
toClose = false
case "close" :
toClose = true
default :
2024-04-14 01:17:01 +09:00
ctx . JSONRedirect ( ctx . ContextUser . HomeLink ( ) + "/-/projects" )
return
2023-01-20 19:42:33 +08:00
}
2024-06-19 06:32:45 +08:00
id := ctx . PathParamInt64 ( ":id" )
2023-01-20 19:42:33 +08:00
2023-09-29 14:12:54 +02:00
if err := project_model . ChangeProjectStatusByRepoIDAndID ( ctx , 0 , id , toClose ) ; err != nil {
2024-03-27 21:54:32 +01:00
ctx . NotFoundOrServerError ( "ChangeProjectStatusByRepoIDAndID" , project_model . IsErrProjectNotExist , err )
2023-01-20 19:42:33 +08:00
return
}
2024-04-14 01:17:01 +09:00
ctx . JSONRedirect ( fmt . Sprintf ( "%s/-/projects/%d" , ctx . ContextUser . HomeLink ( ) , id ) )
2023-01-20 19:42:33 +08:00
}
// DeleteProject delete a project
func DeleteProject ( ctx * context . Context ) {
2024-06-19 06:32:45 +08:00
p , err := project_model . GetProjectByID ( ctx , ctx . PathParamInt64 ( ":id" ) )
2023-01-20 19:42:33 +08:00
if err != nil {
2024-03-27 21:54:32 +01:00
ctx . NotFoundOrServerError ( "GetProjectByID" , project_model . IsErrProjectNotExist , err )
2023-01-20 19:42:33 +08:00
return
}
2023-03-10 01:59:50 +09:00
if p . OwnerID != ctx . ContextUser . ID {
2023-01-20 19:42:33 +08: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 14:04:01 +08:00
ctx . JSONRedirect ( ctx . ContextUser . HomeLink ( ) + "/-/projects" )
2023-01-20 19:42:33 +08:00
}
2023-05-31 08:50:18 +02:00
// RenderEditProject allows a project to be edited
func RenderEditProject ( ctx * context . Context ) {
2023-01-20 19:42:33 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.projects.edit" )
ctx . Data [ "PageIsEditProjects" ] = true
ctx . Data [ "PageIsViewProjects" ] = true
2023-03-10 04:57:20 +09:00
ctx . Data [ "CanWriteProjects" ] = canWriteProjects ( ctx )
2023-04-12 02:28:40 +08:00
ctx . Data [ "CardTypes" ] = project_model . GetCardConfig ( )
2023-01-20 19:42:33 +08:00
shared_user . RenderUserHeader ( ctx )
2024-06-19 06:32:45 +08:00
p , err := project_model . GetProjectByID ( ctx , ctx . PathParamInt64 ( ":id" ) )
2023-01-20 19:42:33 +08:00
if err != nil {
2024-03-27 21:54:32 +01:00
ctx . NotFoundOrServerError ( "GetProjectByID" , project_model . IsErrProjectNotExist , err )
2023-01-20 19:42:33 +08:00
return
}
2023-03-10 01:59:50 +09:00
if p . OwnerID != ctx . ContextUser . ID {
2023-01-20 19:42:33 +08:00
ctx . NotFound ( "" , nil )
return
}
2023-03-24 16:37:56 +08:00
ctx . Data [ "projectID" ] = p . ID
2023-01-20 19:42:33 +08:00
ctx . Data [ "title" ] = p . Title
ctx . Data [ "content" ] = p . Description
2023-03-09 23:38:29 +09:00
ctx . Data [ "redirect" ] = ctx . FormString ( "redirect" )
2023-03-24 16:37:56 +08:00
ctx . Data [ "HomeLink" ] = ctx . ContextUser . HomeLink ( )
2023-04-12 02:28:40 +08:00
ctx . Data [ "card_type" ] = p . CardType
2023-05-31 08:50:18 +02:00
ctx . Data [ "CancelLink" ] = fmt . Sprintf ( "%s/-/projects/%d" , ctx . ContextUser . HomeLink ( ) , p . ID )
2023-01-20 19:42:33 +08:00
ctx . HTML ( http . StatusOK , tplProjectsNew )
}
// EditProjectPost response for editing a project
func EditProjectPost ( ctx * context . Context ) {
form := web . GetForm ( ctx ) . ( * forms . CreateProjectForm )
2024-06-19 06:32:45 +08:00
projectID := ctx . PathParamInt64 ( ":id" )
2023-01-20 19:42:33 +08:00
ctx . Data [ "Title" ] = ctx . Tr ( "repo.projects.edit" )
ctx . Data [ "PageIsEditProjects" ] = true
ctx . Data [ "PageIsViewProjects" ] = true
2023-03-10 04:57:20 +09:00
ctx . Data [ "CanWriteProjects" ] = canWriteProjects ( ctx )
2023-04-12 02:28:40 +08:00
ctx . Data [ "CardTypes" ] = project_model . GetCardConfig ( )
2023-05-31 08:50:18 +02:00
ctx . Data [ "CancelLink" ] = fmt . Sprintf ( "%s/-/projects/%d" , ctx . ContextUser . HomeLink ( ) , projectID )
2023-04-12 02:28:40 +08:00
2023-01-20 19:42:33 +08:00
shared_user . RenderUserHeader ( ctx )
2023-08-11 19:08:05 +02:00
err := shared_user . LoadHeaderCount ( ctx )
if err != nil {
ctx . ServerError ( "LoadHeaderCount" , err )
return
}
2023-01-20 19:42:33 +08:00
if ctx . HasError ( ) {
ctx . HTML ( http . StatusOK , tplProjectsNew )
return
}
2023-05-31 08:50:18 +02:00
p , err := project_model . GetProjectByID ( ctx , projectID )
2023-01-20 19:42:33 +08:00
if err != nil {
2024-03-27 21:54:32 +01:00
ctx . NotFoundOrServerError ( "GetProjectByID" , project_model . IsErrProjectNotExist , err )
2023-01-20 19:42:33 +08:00
return
}
2023-03-10 01:59:50 +09:00
if p . OwnerID != ctx . ContextUser . ID {
2023-01-20 19:42:33 +08:00
ctx . NotFound ( "" , nil )
return
}
p . Title = form . Title
p . Description = form . Content
2023-04-12 02:28:40 +08:00
p . CardType = form . CardType
2023-01-20 19:42:33 +08: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 23:38:29 +09:00
if ctx . FormString ( "redirect" ) == "project" {
2023-09-29 14:12:54 +02:00
ctx . Redirect ( p . Link ( ctx ) )
2023-03-09 23:38:29 +09:00
} else {
ctx . Redirect ( ctx . ContextUser . HomeLink ( ) + "/-/projects" )
}
2023-01-20 19:42:33 +08:00
}
2024-05-27 16:59:54 +08:00
// ViewProject renders the project with board view for a project
2023-01-20 19:42:33 +08:00
func ViewProject ( ctx * context . Context ) {
2024-06-19 06:32:45 +08:00
project , err := project_model . GetProjectByID ( ctx , ctx . PathParamInt64 ( ":id" ) )
2023-01-20 19:42:33 +08:00
if err != nil {
2024-03-27 21:54:32 +01:00
ctx . NotFoundOrServerError ( "GetProjectByID" , project_model . IsErrProjectNotExist , err )
2023-01-20 19:42:33 +08:00
return
}
if project . OwnerID != ctx . ContextUser . ID {
ctx . NotFound ( "" , nil )
return
}
2024-05-27 16:59:54 +08:00
columns , err := project . GetColumns ( ctx )
2023-01-20 19:42:33 +08:00
if err != nil {
2024-05-27 16:59:54 +08:00
ctx . ServerError ( "GetProjectColumns" , err )
2023-01-20 19:42:33 +08:00
return
}
2024-09-12 06:53:40 +03:00
var labelIDs [ ] int64
// 1,-2 means including label 1 and excluding label 2
// 0 means issues with no label
// blank means labels will not be filtered for issues
selectLabels := ctx . FormString ( "labels" )
if selectLabels == "" {
ctx . Data [ "AllLabels" ] = true
} else if selectLabels == "0" {
ctx . Data [ "NoLabel" ] = true
}
if len ( selectLabels ) > 0 {
labelIDs , err = base . StringsToInt64s ( strings . Split ( selectLabels , "," ) )
if err != nil {
ctx . Flash . Error ( ctx . Tr ( "invalid_data" , selectLabels ) , true )
}
}
assigneeID := ctx . FormInt64 ( "assignee" )
issuesMap , err := issues_model . LoadIssuesFromColumnList ( ctx , columns , & issues_model . IssuesOptions {
LabelIDs : labelIDs ,
AssigneeID : assigneeID ,
} )
2023-01-20 19:42:33 +08:00
if err != nil {
2024-05-27 16:59:54 +08:00
ctx . ServerError ( "LoadIssuesOfColumns" , err )
2023-01-20 19:42:33 +08:00
return
}
2023-04-12 02:28:40 +08: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 19:42:33 +08:00
linkedPrsMap := make ( map [ int64 ] [ ] * issues_model . Issue )
for _ , issuesList := range issuesMap {
for _ , issue := range issuesList {
2024-02-14 13:19:57 -05:00
var referencedIDs [ ] int64
2023-01-20 19:42:33 +08:00
for _ , comment := range issue . Comments {
if comment . RefIssueID != 0 && comment . RefIsPull {
2024-02-14 13:19:57 -05:00
referencedIDs = append ( referencedIDs , comment . RefIssueID )
2023-01-20 19:42:33 +08:00
}
}
2024-02-14 13:19:57 -05:00
if len ( referencedIDs ) > 0 {
2023-01-20 19:42:33 +08:00
if linkedPrs , err := issues_model . Issues ( ctx , & issues_model . IssuesOptions {
2024-02-14 13:19:57 -05:00
IssueIDs : referencedIDs ,
2024-03-02 16:42:31 +01:00
IsPull : optional . Some ( true ) ,
2023-01-20 19:42:33 +08:00
} ) ; err == nil {
linkedPrsMap [ issue . ID ] = linkedPrs
}
}
}
}
2024-09-12 06:53:40 +03:00
// TODO: Add option to filter also by repository specific labels
labels , err := issues_model . GetLabelsByOrgID ( ctx , project . OwnerID , "" , db . ListOptions { } )
if err != nil {
ctx . ServerError ( "GetLabelsByOrgID" , err )
return
}
// Get the exclusive scope for every label ID
labelExclusiveScopes := make ( [ ] string , 0 , len ( labelIDs ) )
for _ , labelID := range labelIDs {
foundExclusiveScope := false
for _ , label := range labels {
if label . ID == labelID || label . ID == - labelID {
labelExclusiveScopes = append ( labelExclusiveScopes , label . ExclusiveScope ( ) )
foundExclusiveScope = true
break
}
}
if ! foundExclusiveScope {
labelExclusiveScopes = append ( labelExclusiveScopes , "" )
}
}
for _ , l := range labels {
l . LoadSelectedLabelsAfterClick ( labelIDs , labelExclusiveScopes )
}
ctx . Data [ "Labels" ] = labels
ctx . Data [ "NumLabels" ] = len ( labels )
// Get assignees.
assigneeUsers , err := org_model . GetOrgAssignees ( ctx , project . OwnerID )
if err != nil {
ctx . ServerError ( "GetRepoAssignees" , err )
return
}
ctx . Data [ "Assignees" ] = shared_user . MakeSelfOnTop ( ctx . Doer , assigneeUsers )
ctx . Data [ "SelectLabels" ] = selectLabels
ctx . Data [ "AssigneeID" ] = assigneeID
2024-11-05 14:04:26 +08:00
project . RenderedContent = templates . NewRenderUtils ( ctx ) . MarkdownToHtml ( project . Description )
2023-01-20 19:42:33 +08:00
ctx . Data [ "LinkedPRs" ] = linkedPrsMap
ctx . Data [ "PageIsViewProjects" ] = true
2023-03-10 04:57:20 +09:00
ctx . Data [ "CanWriteProjects" ] = canWriteProjects ( ctx )
2023-01-20 19:42:33 +08:00
ctx . Data [ "Project" ] = project
ctx . Data [ "IssuesMap" ] = issuesMap
2024-05-27 16:59:54 +08:00
ctx . Data [ "Columns" ] = columns
2023-01-20 19:42:33 +08:00
shared_user . RenderUserHeader ( ctx )
2023-08-11 19:08:05 +02:00
err = shared_user . LoadHeaderCount ( ctx )
if err != nil {
ctx . ServerError ( "LoadHeaderCount" , err )
return
}
2023-01-20 19:42:33 +08:00
ctx . HTML ( http . StatusOK , tplProjectsView )
}
2024-05-27 16:59:54 +08:00
// DeleteProjectColumn allows for the deletion of a project column
func DeleteProjectColumn ( ctx * context . Context ) {
2023-01-20 19:42:33 +08:00
if ctx . Doer == nil {
ctx . JSON ( http . StatusForbidden , map [ string ] string {
"message" : "Only signed in users are allowed to perform this action." ,
} )
return
}
2024-06-19 06:32:45 +08:00
project , err := project_model . GetProjectByID ( ctx , ctx . PathParamInt64 ( ":id" ) )
2023-01-20 19:42:33 +08:00
if err != nil {
2024-03-27 21:54:32 +01:00
ctx . NotFoundOrServerError ( "GetProjectByID" , project_model . IsErrProjectNotExist , err )
2023-01-20 19:42:33 +08:00
return
}
2024-06-19 06:32:45 +08:00
pb , err := project_model . GetColumn ( ctx , ctx . PathParamInt64 ( ":columnID" ) )
2023-01-20 19:42:33 +08:00
if err != nil {
2024-05-27 16:59:54 +08:00
ctx . ServerError ( "GetProjectColumn" , err )
2023-01-20 19:42:33 +08:00
return
}
2024-06-19 06:32:45 +08:00
if pb . ProjectID != ctx . PathParamInt64 ( ":id" ) {
2023-01-20 19:42:33 +08:00
ctx . JSON ( http . StatusUnprocessableEntity , map [ string ] string {
2024-05-27 16:59:54 +08:00
"message" : fmt . Sprintf ( "ProjectColumn[%d] is not in Project[%d] as expected" , pb . ID , project . ID ) ,
2023-01-20 19:42:33 +08:00
} )
return
}
if project . OwnerID != ctx . ContextUser . ID {
ctx . JSON ( http . StatusUnprocessableEntity , map [ string ] string {
2024-05-27 16:59:54 +08:00
"message" : fmt . Sprintf ( "ProjectColumn[%d] is not in Owner[%d] as expected" , pb . ID , ctx . ContextUser . ID ) ,
2023-01-20 19:42:33 +08:00
} )
return
}
2024-06-19 06:32:45 +08:00
if err := project_model . DeleteColumnByID ( ctx , ctx . PathParamInt64 ( ":columnID" ) ) ; err != nil {
2024-05-27 16:59:54 +08:00
ctx . ServerError ( "DeleteProjectColumnByID" , err )
2023-01-20 19:42:33 +08:00
return
}
2023-07-26 14:04:01 +08:00
ctx . JSONOK ( )
2023-01-20 19:42:33 +08:00
}
2024-05-27 16:59:54 +08:00
// AddColumnToProjectPost allows a new column to be added to a project.
func AddColumnToProjectPost ( ctx * context . Context ) {
form := web . GetForm ( ctx ) . ( * forms . EditProjectColumnForm )
2023-01-20 19:42:33 +08:00
2024-06-19 06:32:45 +08:00
project , err := project_model . GetProjectByID ( ctx , ctx . PathParamInt64 ( ":id" ) )
2023-01-20 19:42:33 +08:00
if err != nil {
2024-03-27 21:54:32 +01:00
ctx . NotFoundOrServerError ( "GetProjectByID" , project_model . IsErrProjectNotExist , err )
2023-01-20 19:42:33 +08:00
return
}
2024-05-27 16:59:54 +08:00
if err := project_model . NewColumn ( ctx , & project_model . Column {
2023-01-20 19:42:33 +08:00
ProjectID : project . ID ,
Title : form . Title ,
Color : form . Color ,
CreatorID : ctx . Doer . ID ,
} ) ; err != nil {
2024-05-27 16:59:54 +08:00
ctx . ServerError ( "NewProjectColumn" , err )
2023-01-20 19:42:33 +08:00
return
}
2023-07-26 14:04:01 +08:00
ctx . JSONOK ( )
2023-01-20 19:42:33 +08:00
}
2024-05-27 16:59:54 +08:00
// CheckProjectColumnChangePermissions check permission
func CheckProjectColumnChangePermissions ( ctx * context . Context ) ( * project_model . Project , * project_model . Column ) {
2023-01-20 19:42:33 +08:00
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
}
2024-06-19 06:32:45 +08:00
project , err := project_model . GetProjectByID ( ctx , ctx . PathParamInt64 ( ":id" ) )
2023-01-20 19:42:33 +08:00
if err != nil {
2024-03-27 21:54:32 +01:00
ctx . NotFoundOrServerError ( "GetProjectByID" , project_model . IsErrProjectNotExist , err )
2023-01-20 19:42:33 +08:00
return nil , nil
}
2024-06-19 06:32:45 +08:00
column , err := project_model . GetColumn ( ctx , ctx . PathParamInt64 ( ":columnID" ) )
2023-01-20 19:42:33 +08:00
if err != nil {
2024-05-27 16:59:54 +08:00
ctx . ServerError ( "GetProjectColumn" , err )
2023-01-20 19:42:33 +08:00
return nil , nil
}
2024-06-19 06:32:45 +08:00
if column . ProjectID != ctx . PathParamInt64 ( ":id" ) {
2023-01-20 19:42:33 +08:00
ctx . JSON ( http . StatusUnprocessableEntity , map [ string ] string {
2024-05-27 16:59:54 +08:00
"message" : fmt . Sprintf ( "ProjectColumn[%d] is not in Project[%d] as expected" , column . ID , project . ID ) ,
2023-01-20 19:42:33 +08:00
} )
return nil , nil
}
if project . OwnerID != ctx . ContextUser . ID {
ctx . JSON ( http . StatusUnprocessableEntity , map [ string ] string {
2024-05-27 16:59:54 +08:00
"message" : fmt . Sprintf ( "ProjectColumn[%d] is not in Repository[%d] as expected" , column . ID , project . ID ) ,
2023-01-20 19:42:33 +08:00
} )
return nil , nil
}
2024-05-27 16:59:54 +08:00
return project , column
2023-01-20 19:42:33 +08:00
}
2024-05-27 16:59:54 +08:00
// EditProjectColumn allows a project column's to be updated
func EditProjectColumn ( ctx * context . Context ) {
form := web . GetForm ( ctx ) . ( * forms . EditProjectColumnForm )
_ , column := CheckProjectColumnChangePermissions ( ctx )
2023-01-20 19:42:33 +08:00
if ctx . Written ( ) {
return
}
if form . Title != "" {
2024-05-27 16:59:54 +08:00
column . Title = form . Title
2023-01-20 19:42:33 +08:00
}
2024-05-27 16:59:54 +08:00
column . Color = form . Color
2023-01-20 19:42:33 +08:00
if form . Sorting != 0 {
2024-05-27 16:59:54 +08:00
column . Sorting = form . Sorting
2023-01-20 19:42:33 +08:00
}
2024-05-27 16:59:54 +08:00
if err := project_model . UpdateColumn ( ctx , column ) ; err != nil {
ctx . ServerError ( "UpdateProjectColumn" , err )
2023-01-20 19:42:33 +08:00
return
}
2023-07-26 14:04:01 +08:00
ctx . JSONOK ( )
2023-01-20 19:42:33 +08:00
}
2024-05-27 16:59:54 +08:00
// SetDefaultProjectColumn set default column for uncategorized issues/pulls
func SetDefaultProjectColumn ( ctx * context . Context ) {
project , column := CheckProjectColumnChangePermissions ( ctx )
2023-01-20 19:42:33 +08:00
if ctx . Written ( ) {
return
}
2024-05-27 16:59:54 +08:00
if err := project_model . SetDefaultColumn ( ctx , project . ID , column . ID ) ; err != nil {
ctx . ServerError ( "SetDefaultColumn" , err )
2023-01-20 19:42:33 +08:00
return
}
2023-07-26 14:04:01 +08:00
ctx . JSONOK ( )
2023-01-20 19:42:33 +08: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
}
2024-06-19 06:32:45 +08:00
project , err := project_model . GetProjectByID ( ctx , ctx . PathParamInt64 ( ":id" ) )
2023-01-20 19:42:33 +08:00
if err != nil {
2024-03-27 21:54:32 +01:00
ctx . NotFoundOrServerError ( "GetProjectByID" , project_model . IsErrProjectNotExist , err )
2023-01-20 19:42:33 +08:00
return
}
if project . OwnerID != ctx . ContextUser . ID {
ctx . NotFound ( "InvalidRepoID" , nil )
return
}
2024-06-19 06:32:45 +08:00
column , err := project_model . GetColumn ( ctx , ctx . PathParamInt64 ( ":columnID" ) )
2024-03-27 21:54:32 +01:00
if err != nil {
2024-05-27 16:59:54 +08:00
ctx . NotFoundOrServerError ( "GetProjectColumn" , project_model . IsErrProjectColumnNotExist , err )
2024-03-27 21:54:32 +01:00
return
}
2023-01-20 19:42:33 +08:00
2024-05-27 16:59:54 +08:00
if column . ProjectID != project . ID {
ctx . NotFound ( "ColumnNotInProject" , nil )
2024-03-27 21:54:32 +01:00
return
2023-01-20 19:42:33 +08:00
}
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 )
2024-05-28 17:31:59 +08:00
return
2023-01-20 19:42:33 +08:00
}
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 {
2024-03-27 21:54:32 +01:00
ctx . NotFoundOrServerError ( "GetIssueByID" , issues_model . IsErrIssueNotExist , err )
2023-01-20 19:42:33 +08:00
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
}
}
2024-08-09 09:29:02 +08:00
if err = project_service . MoveIssuesOnProjectColumn ( ctx , ctx . Doer , column , sortedIssueIDs ) ; err != nil {
2024-05-27 16:59:54 +08:00
ctx . ServerError ( "MoveIssuesOnProjectColumn" , err )
2023-01-20 19:42:33 +08:00
return
}
2023-07-26 14:04:01 +08:00
ctx . JSONOK ( )
2023-01-20 19:42:33 +08:00
}