mirror of
				https://github.com/go-gitea/gitea
				synced 2025-11-04 05:18:25 +00:00 
			
		
		
		
	refactor: refactor endpoints to comply with github api
introduce new logic to handle project and column assignment, refactor everything and removed unnecessary code, create swagger docs
This commit is contained in:
		@@ -76,6 +76,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/models/organization"
 | 
			
		||||
	"code.gitea.io/gitea/models/perm"
 | 
			
		||||
	access_model "code.gitea.io/gitea/models/perm/access"
 | 
			
		||||
	project_model "code.gitea.io/gitea/models/project"
 | 
			
		||||
	repo_model "code.gitea.io/gitea/models/repo"
 | 
			
		||||
	"code.gitea.io/gitea/models/unit"
 | 
			
		||||
	user_model "code.gitea.io/gitea/models/user"
 | 
			
		||||
@@ -89,9 +90,9 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/routers/api/v1/notify"
 | 
			
		||||
	"code.gitea.io/gitea/routers/api/v1/org"
 | 
			
		||||
	"code.gitea.io/gitea/routers/api/v1/packages"
 | 
			
		||||
	"code.gitea.io/gitea/routers/api/v1/project"
 | 
			
		||||
	"code.gitea.io/gitea/routers/api/v1/repo"
 | 
			
		||||
	"code.gitea.io/gitea/routers/api/v1/settings"
 | 
			
		||||
	project_shared "code.gitea.io/gitea/routers/api/v1/shared"
 | 
			
		||||
	"code.gitea.io/gitea/routers/api/v1/user"
 | 
			
		||||
	"code.gitea.io/gitea/routers/common"
 | 
			
		||||
	"code.gitea.io/gitea/services/actions"
 | 
			
		||||
@@ -135,6 +136,114 @@ func sudo() func(ctx *context.APIContext) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func projectIDAssignmentAPI() func(ctx *context.APIContext) {
 | 
			
		||||
	return func(ctx *context.APIContext) {
 | 
			
		||||
		if ctx.PathParam(":project_id") == "" {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		projectAssignment(ctx, ctx.PathParamInt64(":project_id"))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func projectAssignment(ctx *context.APIContext, projectID int64) {
 | 
			
		||||
	var (
 | 
			
		||||
		owner *user_model.User
 | 
			
		||||
		err   error
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	project, err := project_model.GetProjectByID(ctx, projectID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.Error(http.StatusNotFound, "GetProjectByID", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if project.Type == project_model.TypeIndividual || project.Type == project_model.TypeOrganization {
 | 
			
		||||
		if err := project.LoadOwner(ctx); err != nil {
 | 
			
		||||
			ctx.Error(http.StatusNotFound, "LoadOwner", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if ctx.IsSigned && ctx.Doer.LowerName == strings.ToLower(project.Owner.Name) {
 | 
			
		||||
			owner = ctx.Doer
 | 
			
		||||
		} else {
 | 
			
		||||
			owner = project.Owner
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if project.Type == project_model.TypeOrganization {
 | 
			
		||||
			ctx.Org.Organization = (*organization.Organization)(owner)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if err := project.LoadRepo(ctx); err != nil {
 | 
			
		||||
			ctx.Error(http.StatusNotFound, "LoadRepo", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		repo := project.Repo
 | 
			
		||||
 | 
			
		||||
		if err := repo.LoadOwner(ctx); err != nil {
 | 
			
		||||
			ctx.Error(http.StatusNotFound, "LoadOwner", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ctx.Repo.Repository = repo
 | 
			
		||||
		owner = repo.Owner
 | 
			
		||||
 | 
			
		||||
		if ctx.Doer != nil && ctx.Doer.ID == user_model.ActionsUserID {
 | 
			
		||||
			taskID := ctx.Data["ActionsTaskID"].(int64)
 | 
			
		||||
			task, err := actions_model.GetTaskByID(ctx, taskID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.Error(http.StatusInternalServerError, "actions_model.GetTaskByID", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if task.RepoID != repo.ID {
 | 
			
		||||
				ctx.NotFound()
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if task.IsForkPullRequest {
 | 
			
		||||
				ctx.Repo.Permission.AccessMode = perm.AccessModeRead
 | 
			
		||||
			} else {
 | 
			
		||||
				ctx.Repo.Permission.AccessMode = perm.AccessModeWrite
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := ctx.Repo.Repository.LoadUnits(ctx); err != nil {
 | 
			
		||||
				ctx.Error(http.StatusInternalServerError, "LoadUnits", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Repo.Permission.SetUnitsWithDefaultAccessMode(ctx.Repo.Repository.Units, ctx.Repo.Permission.AccessMode)
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !ctx.Repo.Permission.HasAnyUnitAccess() {
 | 
			
		||||
			ctx.NotFound()
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ctx.ContextUser = owner
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func columnAssignment() func(ctx *context.APIContext) {
 | 
			
		||||
	return func(ctx *context.APIContext) {
 | 
			
		||||
		if ctx.PathParam("column_id") == "" {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		column, err := project_model.GetColumn(ctx, ctx.PathParamInt64(":column_id"))
 | 
			
		||||
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			ctx.Error(http.StatusNotFound, "GetColumn", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		projectAssignment(ctx, column.ProjectID)
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func repoAssignment() func(ctx *context.APIContext) {
 | 
			
		||||
	return func(ctx *context.APIContext) {
 | 
			
		||||
		userName := ctx.PathParam("username")
 | 
			
		||||
@@ -169,6 +278,10 @@ func repoAssignment() func(ctx *context.APIContext) {
 | 
			
		||||
		ctx.Repo.Owner = owner
 | 
			
		||||
		ctx.ContextUser = owner
 | 
			
		||||
 | 
			
		||||
		if owner.IsOrganization() {
 | 
			
		||||
			ctx.Org.Organization = (*organization.Organization)(owner)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Get repository.
 | 
			
		||||
		repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, repoName)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@@ -388,6 +501,9 @@ func reqAdmin() func(ctx *context.APIContext) {
 | 
			
		||||
// reqRepoWriter user should have a permission to write to a repo, or be a site admin
 | 
			
		||||
func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) {
 | 
			
		||||
	return func(ctx *context.APIContext) {
 | 
			
		||||
		if ctx.Repo.Repository == nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
 | 
			
		||||
			ctx.Error(http.StatusForbidden, "reqRepoWriter", "user should have a permission to write to a repo")
 | 
			
		||||
			return
 | 
			
		||||
@@ -395,18 +511,6 @@ func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// reqRepoWriterOr returns a middleware for requiring repository write to one of the unit permission
 | 
			
		||||
func reqRepoWriterOr(unitTypes ...unit.Type) func(ctx *context.APIContext) {
 | 
			
		||||
	return func(ctx *context.APIContext) {
 | 
			
		||||
		for _, unitType := range unitTypes {
 | 
			
		||||
			if ctx.Repo.CanWrite(unitType) {
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin
 | 
			
		||||
func reqRepoBranchWriter(ctx *context.APIContext) {
 | 
			
		||||
	options, ok := web.GetForm(ctx).(api.FileOptionInterface)
 | 
			
		||||
@@ -419,6 +523,10 @@ func reqRepoBranchWriter(ctx *context.APIContext) {
 | 
			
		||||
// reqRepoReader user should have specific read permission or be a repo admin or a site admin
 | 
			
		||||
func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) {
 | 
			
		||||
	return func(ctx *context.APIContext) {
 | 
			
		||||
		if ctx.Repo.Repository == nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !ctx.Repo.CanRead(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
 | 
			
		||||
			ctx.Error(http.StatusForbidden, "reqRepoReader", "user should have specific read permission or be a repo admin or a site admin")
 | 
			
		||||
			return
 | 
			
		||||
@@ -555,6 +663,15 @@ func reqWebhooksEnabled() func(ctx *context.APIContext) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func reqProjectOwner() func(ctx *context.APIContext) {
 | 
			
		||||
	return func(ctx *context.APIContext) {
 | 
			
		||||
		if ctx.Repo.Repository == nil && ctx.ContextUser.IsIndividual() && ctx.ContextUser != ctx.Doer {
 | 
			
		||||
			ctx.Error(http.StatusForbidden, "", "must be the project owner")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func orgAssignment(args ...bool) func(ctx *context.APIContext) {
 | 
			
		||||
	var (
 | 
			
		||||
		assignOrg  bool
 | 
			
		||||
@@ -569,18 +686,6 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) {
 | 
			
		||||
	return func(ctx *context.APIContext) {
 | 
			
		||||
		ctx.Org = new(context.APIOrganization)
 | 
			
		||||
 | 
			
		||||
		if ctx.ContextUser == nil {
 | 
			
		||||
			if ctx.Org.Organization == nil {
 | 
			
		||||
				getOrganizationByParams(ctx)
 | 
			
		||||
				ctx.ContextUser = ctx.Org.Organization.AsUser()
 | 
			
		||||
			}
 | 
			
		||||
		} else if ctx.ContextUser.IsOrganization() {
 | 
			
		||||
			if ctx.Org == nil {
 | 
			
		||||
				ctx.Org = &context.APIOrganization{}
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Org.Organization = (*organization.Organization)(ctx.ContextUser)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var err error
 | 
			
		||||
		if assignOrg {
 | 
			
		||||
			getOrganizationByParams(ctx)
 | 
			
		||||
@@ -639,6 +744,12 @@ func mustEnableRepoProjects(ctx *context.APIContext) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getAuthenticatedUser(ctx *context.APIContext) {
 | 
			
		||||
	if ctx.IsSigned {
 | 
			
		||||
		ctx.ContextUser = ctx.Doer
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mustEnableIssues(ctx *context.APIContext) {
 | 
			
		||||
	if !ctx.Repo.CanRead(unit.TypeIssues) {
 | 
			
		||||
		if log.IsTrace() {
 | 
			
		||||
@@ -719,6 +830,10 @@ func mustEnableWiki(ctx *context.APIContext) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mustNotBeArchived(ctx *context.APIContext) {
 | 
			
		||||
	if ctx.Repo.Repository == nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ctx.Repo.Repository.IsArchived {
 | 
			
		||||
		ctx.Error(http.StatusLocked, "RepoArchived", fmt.Errorf("%s is archived", ctx.Repo.Repository.LogString()))
 | 
			
		||||
		return
 | 
			
		||||
@@ -1015,66 +1130,51 @@ func Routes() *web.Router {
 | 
			
		||||
			}, context.UserAssignmentAPI(), individualPermsChecker)
 | 
			
		||||
		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
 | 
			
		||||
 | 
			
		||||
		// Users (requires user scope)
 | 
			
		||||
		m.Group("/{username}/-", func() {
 | 
			
		||||
			m.Group("/projects", func() {
 | 
			
		||||
				m.Group("", func() {
 | 
			
		||||
					m.Get("", project_shared.ProjectHandler("org", project_shared.GetProjects))
 | 
			
		||||
					m.Get("/{id}", project_shared.ProjectHandler("org", project_shared.GetProject))
 | 
			
		||||
				})
 | 
			
		||||
		// Projects
 | 
			
		||||
		m.Group("/orgs/{org}/projects", func() {
 | 
			
		||||
			m.Get("", reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true), org.GetProjects)
 | 
			
		||||
			m.Post("", reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), bind(api.CreateProjectOption{}), org.CreateProject)
 | 
			
		||||
		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true))
 | 
			
		||||
 | 
			
		||||
		m.Group("/projects", func() {
 | 
			
		||||
			m.Group("/{project_id}", func() {
 | 
			
		||||
				m.Get("", project.GetProject)
 | 
			
		||||
				m.Get("/columns", project.GetProjectColumns)
 | 
			
		||||
 | 
			
		||||
				m.Group("", func() {
 | 
			
		||||
					m.Post("", bind(api.CreateProjectOption{}), project_shared.ProjectHandler("org", project_shared.CreateProject))
 | 
			
		||||
					m.Group("/{id}", func() {
 | 
			
		||||
						m.Post("", bind(api.CreateProjectColumnOption{}), project_shared.ProjectHandler("org", project_shared.AddColumnToProject))
 | 
			
		||||
						m.Delete("", project_shared.ProjectHandler("org", project_shared.DeleteProject))
 | 
			
		||||
						m.Put("", bind(api.EditProjectOption{}), project_shared.ProjectHandler("org", project_shared.EditProject))
 | 
			
		||||
						m.Post("/move", project_shared.MoveColumns)
 | 
			
		||||
						m.Post("/{action:open|close}", project_shared.ChangeProjectStatus)
 | 
			
		||||
 | 
			
		||||
						m.Group("/{columnID}", func() {
 | 
			
		||||
							m.Put("", bind(api.EditProjectColumnOption{}), project_shared.ProjectHandler("org", project_shared.EditProjectColumn))
 | 
			
		||||
							m.Delete("", project_shared.ProjectHandler("org", project_shared.DeleteProjectColumn))
 | 
			
		||||
							m.Post("/default", project_shared.ProjectHandler("org", project_shared.SetDefaultProjectColumn))
 | 
			
		||||
							m.Post("/move", project_shared.ProjectHandler("org", project_shared.MoveIssues))
 | 
			
		||||
						})
 | 
			
		||||
					m.Patch("", bind(api.EditProjectOption{}), project.EditProject)
 | 
			
		||||
					m.Delete("", project.DeleteProject)
 | 
			
		||||
					m.Post("/{action:open|close}", project.ChangeProjectStatus)
 | 
			
		||||
					m.Group("/columns", func() {
 | 
			
		||||
						m.Post("", bind(api.CreateProjectColumnOption{}), project.AddColumnToProject)
 | 
			
		||||
						m.Patch("/move", project.MoveColumns)
 | 
			
		||||
						m.Patch("/{column_id}/move", project.MoveIssues)
 | 
			
		||||
					})
 | 
			
		||||
				}, reqSelfOrAdmin(), reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true))
 | 
			
		||||
			}, individualPermsChecker)
 | 
			
		||||
				}, reqRepoWriter(unit.TypeProjects), mustNotBeArchived, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), reqProjectOwner())
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), reqToken(), context.UserAssignmentAPI(), orgAssignment(), reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true))
 | 
			
		||||
 | 
			
		||||
		// Users (requires user scope)
 | 
			
		||||
		m.Group("/{username}/{reponame}", func() {
 | 
			
		||||
			m.Group("/projects", func() {
 | 
			
		||||
				m.Group("", func() {
 | 
			
		||||
					m.Get("", project_shared.ProjectHandler("repo", project_shared.GetProjects))
 | 
			
		||||
					m.Get("/{id}", project_shared.ProjectHandler("repo", project_shared.GetProject))
 | 
			
		||||
				})
 | 
			
		||||
			m.Group("/columns/{column_id}", func() {
 | 
			
		||||
				m.Get("", project.GetProjectColumn)
 | 
			
		||||
 | 
			
		||||
				m.Group("", func() {
 | 
			
		||||
					m.Post("", bind(api.CreateProjectOption{}), project_shared.ProjectHandler("repo", project_shared.CreateProject))
 | 
			
		||||
					m.Group("/{id}", func() {
 | 
			
		||||
						m.Post("", bind(api.CreateProjectColumnOption{}), project_shared.ProjectHandler("repo", project_shared.AddColumnToProject))
 | 
			
		||||
						m.Delete("", project_shared.ProjectHandler("repo", project_shared.DeleteProject))
 | 
			
		||||
						m.Put("", bind(api.EditProjectOption{}), project_shared.ProjectHandler("repo", project_shared.EditProject))
 | 
			
		||||
						m.Post("/move", project_shared.MoveColumns)
 | 
			
		||||
						m.Post("/{action:open|close}", project_shared.ChangeProjectStatus)
 | 
			
		||||
					m.Patch("", bind(api.EditProjectColumnOption{}), project.EditProjectColumn)
 | 
			
		||||
					m.Delete("", project.DeleteProjectColumn)
 | 
			
		||||
					m.Post("/default", project.SetDefaultProjectColumn)
 | 
			
		||||
				}, reqRepoWriter(unit.TypeProjects), mustNotBeArchived, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), reqProjectOwner())
 | 
			
		||||
			})
 | 
			
		||||
		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository), reqToken(), projectIDAssignmentAPI(), columnAssignment(), individualPermsChecker, reqRepoReader(unit.TypeProjects), mustEnableRepoProjects, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true))
 | 
			
		||||
 | 
			
		||||
						m.Group("/{columnID}", func() {
 | 
			
		||||
							m.Put("", bind(api.EditProjectColumnOption{}), project_shared.ProjectHandler("repo", project_shared.EditProjectColumn))
 | 
			
		||||
							m.Delete("", project_shared.ProjectHandler("repo", project_shared.DeleteProjectColumn))
 | 
			
		||||
							m.Post("/default", project_shared.ProjectHandler("repo", project_shared.SetDefaultProjectColumn))
 | 
			
		||||
							m.Post("/move", project_shared.ProjectHandler("repo", project_shared.MoveIssues))
 | 
			
		||||
						})
 | 
			
		||||
					})
 | 
			
		||||
				}, reqRepoWriter(unit.TypeProjects), mustNotBeArchived)
 | 
			
		||||
			}, individualPermsChecker)
 | 
			
		||||
		m.Group("/repos/{username}/{reponame}/projects", func() {
 | 
			
		||||
			m.Get("", repo.GetProjects)
 | 
			
		||||
			m.Group("", func() {
 | 
			
		||||
				m.Post("", bind(api.CreateProjectOption{}), repo.CreateProject)
 | 
			
		||||
				m.Put("/{type:issues|pulls}", reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.UpdateIssueProject)
 | 
			
		||||
			}, reqRepoWriter(unit.TypeProjects), mustNotBeArchived)
 | 
			
		||||
		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), reqToken(), repoAssignment(), individualPermsChecker, reqRepoReader(unit.TypeProjects), mustEnableRepoProjects)
 | 
			
		||||
 | 
			
		||||
			m.Group("/{type:issues|pulls}", func() {
 | 
			
		||||
				m.Post("/projects", reqRepoWriterOr(unit.TypeIssues, unit.TypePullRequests), reqRepoWriter(unit.TypeProjects), project_shared.UpdateIssueProject)
 | 
			
		||||
			}, individualPermsChecker)
 | 
			
		||||
		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository), reqToken(), repoAssignment(), reqRepoReader(unit.TypeProjects), mustEnableRepoProjects)
 | 
			
		||||
		m.Post("/user/projects", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken(), getAuthenticatedUser, reqSelfOrAdmin(), bind(api.CreateProjectOption{}), user.CreateProject)
 | 
			
		||||
 | 
			
		||||
		m.Get("/users/{username}/projects", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken(), context.UserAssignmentAPI(), individualPermsChecker, user.GetProjects)
 | 
			
		||||
 | 
			
		||||
		// Users (requires user scope)
 | 
			
		||||
		m.Group("/users", func() {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										114
									
								
								routers/api/v1/org/project.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								routers/api/v1/org/project.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
// Copyright 2017 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package org
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	project_model "code.gitea.io/gitea/models/project"
 | 
			
		||||
	"code.gitea.io/gitea/modules/optional"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
	"code.gitea.io/gitea/services/context"
 | 
			
		||||
	"code.gitea.io/gitea/services/convert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CreateProject creates a new project for organization
 | 
			
		||||
func CreateProject(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation POST /orgs/{org}/projects project createProject
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Create a new project
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: org
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: organization name that the project belongs to
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: string
 | 
			
		||||
	// - name: body
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   description: Project data
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//     "$ref": "#/definitions/CreateProjectOption"
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "201":
 | 
			
		||||
	//     "$ref": "#/responses/Project"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "412":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
	//   "422":
 | 
			
		||||
	//     "$ref": "#/responses/validationError"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	form := web.GetForm(ctx).(*api.CreateProjectOption)
 | 
			
		||||
 | 
			
		||||
	project := &project_model.Project{
 | 
			
		||||
		Title:        form.Title,
 | 
			
		||||
		Description:  form.Content,
 | 
			
		||||
		CreatorID:    ctx.Doer.ID,
 | 
			
		||||
		TemplateType: project_model.TemplateType(form.TemplateType),
 | 
			
		||||
		CardType:     project_model.CardType(form.CardType),
 | 
			
		||||
		Type:         project_model.TypeOrganization,
 | 
			
		||||
		OwnerID:      ctx.ContextUser.ID,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := project_model.NewProject(ctx, project); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "NewProject", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusCreated, convert.ToProject(ctx, project))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetProjects returns a list of projects that belong to an organization
 | 
			
		||||
func GetProjects(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation GET /orgs/{org}/projects project getProjects
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Get a list of projects
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: org
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: organization name that the project belongs to
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: string
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/ProjectList"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	sortType := ctx.FormTrim("sort")
 | 
			
		||||
 | 
			
		||||
	isShowClosed := strings.ToLower(ctx.FormTrim("state")) == "closed"
 | 
			
		||||
 | 
			
		||||
	searchOptions := project_model.SearchOptions{
 | 
			
		||||
		IsClosed: optional.Some(isShowClosed),
 | 
			
		||||
		OrderBy:  project_model.GetSearchOrderByBySortType(sortType),
 | 
			
		||||
		OwnerID:  ctx.ContextUser.ID,
 | 
			
		||||
		Type:     project_model.TypeOrganization,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	projects, err := db.Find[project_model.Project](ctx, &searchOptions)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("FindProjects", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToProjects(ctx, projects))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										208
									
								
								routers/api/v1/project/project.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								routers/api/v1/project/project.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,208 @@
 | 
			
		||||
// Copyright 2017 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package project
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
			
		||||
	project_model "code.gitea.io/gitea/models/project"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
	"code.gitea.io/gitea/services/context"
 | 
			
		||||
	"code.gitea.io/gitea/services/convert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetProject returns a project
 | 
			
		||||
func GetProject(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation GET /projects/{project_id} project getProject
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Get a project
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: project_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: project ID
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/Project"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":project_id"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	columns, err := project.GetColumns(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetProjectColumns", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	issuesMap, err := issues_model.LoadIssuesFromColumnList(ctx, columns)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("LoadIssuesOfColumns", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	issues := issues_model.IssueList{}
 | 
			
		||||
 | 
			
		||||
	for _, column := range columns {
 | 
			
		||||
		if empty := issuesMap[column.ID]; len(empty) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		issues = append(issues, issuesMap[column.ID]...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, map[string]any{
 | 
			
		||||
		"project": convert.ToProject(ctx, project),
 | 
			
		||||
		"columns": convert.ToColumns(ctx, columns),
 | 
			
		||||
		"issues":  convert.ToAPIIssueList(ctx, ctx.Doer, issues),
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EditProject edits a project
 | 
			
		||||
func EditProject(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation PATCH /projects/{project_id} project editProject
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Edit a project
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: project_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: project ID
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/Project"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "412":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	form := web.GetForm(ctx).(*api.EditProjectOption)
 | 
			
		||||
	projectID := ctx.PathParamInt64(":project_id")
 | 
			
		||||
 | 
			
		||||
	project, err := project_model.GetProjectByID(ctx, projectID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	project.Title = form.Title
 | 
			
		||||
	project.Description = form.Content
 | 
			
		||||
	project.CardType = project_model.CardType(form.CardType)
 | 
			
		||||
 | 
			
		||||
	if err = project_model.UpdateProject(ctx, project); err != nil {
 | 
			
		||||
		ctx.ServerError("UpdateProjects", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToProject(ctx, project))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteProject deletes a project
 | 
			
		||||
func DeleteProject(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation DELETE /projects/{project_id} project deleteProject
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Delete a project
 | 
			
		||||
	// description: Deletes a specific project for a given user and repository.
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: project_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: project ID
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "204":
 | 
			
		||||
	//     "$ref": "#/responses/empty"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	err := project_model.DeleteProjectByID(ctx, ctx.PathParamInt64(":project_id"))
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("DeleteProjectByID", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Status(http.StatusNoContent)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ChangeProjectStatus updates the status of a project between "open" and "close"
 | 
			
		||||
func ChangeProjectStatus(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation PATCH /projects/{project_id}/{action} project changeProjectStatus
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Change the status of a project
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: project_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: project ID
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	// - name: action
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: action to perform (open or close)
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   enum:
 | 
			
		||||
	//   - open
 | 
			
		||||
	//   - close
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/Project"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	var toClose bool
 | 
			
		||||
	switch ctx.PathParam(":action") {
 | 
			
		||||
	case "open":
 | 
			
		||||
		toClose = false
 | 
			
		||||
	case "close":
 | 
			
		||||
		toClose = true
 | 
			
		||||
	default:
 | 
			
		||||
		ctx.NotFound("ChangeProjectStatus", nil)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	id := ctx.PathParamInt64(":project_id")
 | 
			
		||||
 | 
			
		||||
	if err := project_model.ChangeProjectStatusByRepoIDAndID(ctx, 0, id, toClose); err != nil {
 | 
			
		||||
		ctx.NotFoundOrServerError("ChangeProjectStatusByRepoIDAndID", project_model.IsErrProjectNotExist, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	project, err := project_model.GetProjectByID(ctx, id)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToProject(ctx, project))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										425
									
								
								routers/api/v1/project/project_column.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										425
									
								
								routers/api/v1/project/project_column.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,425 @@
 | 
			
		||||
// Copyright 2017 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package project
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
			
		||||
	project_model "code.gitea.io/gitea/models/project"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
	"code.gitea.io/gitea/services/context"
 | 
			
		||||
	"code.gitea.io/gitea/services/convert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetProjectColumn returns a project column
 | 
			
		||||
func GetProjectColumn(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation GET /projects/columns/{column_id} project getProject
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Get a project column
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: column_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: column ID
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/Column"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	column, err := project_model.GetColumn(ctx, ctx.PathParamInt64(":column_id"))
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetProjectColumn", project_model.IsErrProjectColumnNotExist, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToColumn(ctx, column))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetProjectColumns returns a list of project columns
 | 
			
		||||
func GetProjectColumns(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation GET /projects/{project_id}/columns project getProject
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Get a list of project columns
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: project_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: project ID
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/ColumnList"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":project_id"))
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	columns, err := project.GetColumns(ctx)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetColumnsByProjectID", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToColumns(ctx, columns))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddColumnToProject adds a new column to a project
 | 
			
		||||
func AddColumnToProject(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation POST /projects/{project_id}/columns project addColumnToProject
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Add a column to a project
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: project_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: project ID
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	// - name: body
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   description: column data
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//     "$ref": "#/definitions/CreateProjectColumnOption"
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "201":
 | 
			
		||||
	//     "$ref": "#/responses/Column"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "412":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
	//   "422":
 | 
			
		||||
	//     "$ref": "#/responses/validationError"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	var project *project_model.Project
 | 
			
		||||
 | 
			
		||||
	projectID := ctx.PathParamInt64(":project_id")
 | 
			
		||||
 | 
			
		||||
	project, err := project_model.GetProjectByID(ctx, projectID)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	form := web.GetForm(ctx).(*api.CreateProjectColumnOption)
 | 
			
		||||
	column := &project_model.Column{
 | 
			
		||||
		ProjectID: project.ID,
 | 
			
		||||
		Title:     form.Title,
 | 
			
		||||
		Sorting:   form.Sorting,
 | 
			
		||||
		Color:     form.Color,
 | 
			
		||||
		CreatorID: ctx.Doer.ID,
 | 
			
		||||
	}
 | 
			
		||||
	if err := project_model.NewColumn(ctx, column); err != nil {
 | 
			
		||||
		ctx.ServerError("NewProjectColumn", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusCreated, convert.ToColumn(ctx, column))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EditProjectColumn edits a project column
 | 
			
		||||
func EditProjectColumn(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation PATCH /projects/columns/{column_id} project editProjectColumn
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Edit a project column
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: column_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: column ID
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	// - name: body
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   description: column data
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//     "$ref": "#/definitions/EditProjectColumnOption"
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/Column"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "412":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
	//   "422":
 | 
			
		||||
	//     "$ref": "#/responses/validationError"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	form := web.GetForm(ctx).(*api.EditProjectColumnOption)
 | 
			
		||||
	column, err := project_model.GetColumn(ctx, ctx.PathParamInt64(":column_id"))
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetProjectColumn", project_model.IsErrProjectColumnNotExist, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if form.Title != "" {
 | 
			
		||||
		column.Title = form.Title
 | 
			
		||||
	}
 | 
			
		||||
	column.Color = form.Color
 | 
			
		||||
	if form.Sorting != 0 {
 | 
			
		||||
		column.Sorting = form.Sorting
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := project_model.UpdateColumn(ctx, column); err != nil {
 | 
			
		||||
		ctx.ServerError("UpdateProjectColumn", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToColumn(ctx, column))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteProjectColumn deletes a project column
 | 
			
		||||
func DeleteProjectColumn(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation DELETE /projects/columns/{column_id} project deleteProjectColumn
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Delete a project column
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: column_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: column ID
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "204":
 | 
			
		||||
	//     "$ref": "#/responses/empty"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	if err := project_model.DeleteColumnByID(ctx, ctx.PathParamInt64(":column_id")); err != nil {
 | 
			
		||||
		ctx.ServerError("DeleteProjectColumnByID", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Status(http.StatusNoContent)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetDefaultProjectColumn set default column for issues/pulls
 | 
			
		||||
func SetDefaultProjectColumn(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation PUT /projects/columns/{column_id}/default project setDefaultProjectColumn
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Set default column for issues/pulls
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: column_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: column ID
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "204":
 | 
			
		||||
	//     "$ref": "#/responses/empty"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	column, err := project_model.GetColumn(ctx, ctx.PathParamInt64(":column_id"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetProjectColumn", project_model.IsErrProjectColumnNotExist, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := project_model.SetDefaultColumn(ctx, column.ProjectID, column.ID); err != nil {
 | 
			
		||||
		ctx.ServerError("SetDefaultColumn", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Status(http.StatusNoContent)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MoveColumns moves or keeps columns in a project and sorts them inside that project
 | 
			
		||||
func MoveColumns(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation PATCH /projects/{project_id}/columns/move project moveColumns
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Move columns in a project
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: project_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: project ID
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	// - name: body
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   description: columns data
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//    "$ref": "#/definitions/MovedColumnsOption"
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/ColumnList"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":project_id"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	form := &api.MovedColumnsOption{}
 | 
			
		||||
	if err = json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
 | 
			
		||||
		ctx.ServerError("DecodeMovedColumnsForm", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sortedColumnIDs := make(map[int64]int64)
 | 
			
		||||
	for _, column := range form.Columns {
 | 
			
		||||
		sortedColumnIDs[column.Sorting] = column.ColumnID
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = project_model.MoveColumnsOnProject(ctx, project, sortedColumnIDs); err != nil {
 | 
			
		||||
		ctx.ServerError("MoveColumnsOnProject", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	columns, err := project.GetColumns(ctx)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetColumns", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToColumns(ctx, columns))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MoveIssues moves or keeps issues in a column and sorts them inside that column
 | 
			
		||||
func MoveIssues(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation PATCH /projects/{project_id}/columns/{column_id}/move project moveIssues
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Move issues in a column
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: project_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: project ID
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	// - name: column_id
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: column ID
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: integer
 | 
			
		||||
	// - name: body
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   description: issues data
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//    "$ref": "#/definitions/MovedIssuesOption"
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/IssueList"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64(":project_id"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetProjectByID", project_model.IsErrProjectNotExist, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	column, err := project_model.GetColumn(ctx, ctx.PathParamInt64(":column_id"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetProjectColumn", project_model.IsErrProjectColumnNotExist, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	form := &api.MovedIssuesOption{}
 | 
			
		||||
	if err = json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
 | 
			
		||||
		ctx.ServerError("DecodeMovedIssuesForm", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
		ctx.NotFoundOrServerError("GetIssueByID", issues_model.IsErrIssueNotExist, 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
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err = project_model.MoveIssuesOnProjectColumn(ctx, column, sortedIssueIDs); err != nil {
 | 
			
		||||
		ctx.ServerError("MoveIssuesOnProjectColumn", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, movedIssues))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										242
									
								
								routers/api/v1/repo/project.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								routers/api/v1/repo/project.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,242 @@
 | 
			
		||||
// Copyright 2017 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	issues_model "code.gitea.io/gitea/models/issues"
 | 
			
		||||
	project_model "code.gitea.io/gitea/models/project"
 | 
			
		||||
	"code.gitea.io/gitea/models/unit"
 | 
			
		||||
	"code.gitea.io/gitea/modules/optional"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
	"code.gitea.io/gitea/services/context"
 | 
			
		||||
	"code.gitea.io/gitea/services/convert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetProjects returns a list of projects for a given user and repository.
 | 
			
		||||
func GetProjects(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation GET /repos/{owner}/{reponame}/projects project getProjects
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Get a list of projects
 | 
			
		||||
	// description: Returns a list of projects for a given user and repository.
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: owner
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: owner of the project
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: string
 | 
			
		||||
	// - name: reponame
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: repository name.
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: string
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/ProjectList"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	sortType := ctx.FormTrim("sort")
 | 
			
		||||
 | 
			
		||||
	isShowClosed := strings.ToLower(ctx.FormTrim("state")) == "closed"
 | 
			
		||||
 | 
			
		||||
	searchOptions := project_model.SearchOptions{
 | 
			
		||||
		IsClosed: optional.Some(isShowClosed),
 | 
			
		||||
		OrderBy:  project_model.GetSearchOrderByBySortType(sortType),
 | 
			
		||||
		RepoID:   ctx.Repo.Repository.ID,
 | 
			
		||||
		Type:     project_model.TypeRepository,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	projects, err := db.Find[project_model.Project](ctx, &searchOptions)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("FindProjects", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToProjects(ctx, projects))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateProject creates a new project
 | 
			
		||||
func CreateProject(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation POST /repos/{owner}/{reponame}/projects project createProject
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Create a new project
 | 
			
		||||
	// description: Creates a new project for a given user and repository.
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: owner
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: owner of the project
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: string
 | 
			
		||||
	// - name: reponame
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: repository name.
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: string
 | 
			
		||||
	// - name: body
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   description: Project data
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//     "$ref": "#/definitions/CreateProjectOption"
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "201":
 | 
			
		||||
	//     "$ref": "#/responses/Project"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "412":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
	//   "422":
 | 
			
		||||
	//     "$ref": "#/responses/validationError"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	form := web.GetForm(ctx).(*api.CreateProjectOption)
 | 
			
		||||
 | 
			
		||||
	project := &project_model.Project{
 | 
			
		||||
		Title:        form.Title,
 | 
			
		||||
		Description:  form.Content,
 | 
			
		||||
		CreatorID:    ctx.Doer.ID,
 | 
			
		||||
		TemplateType: project_model.TemplateType(form.TemplateType),
 | 
			
		||||
		CardType:     project_model.CardType(form.CardType),
 | 
			
		||||
		Type:         project_model.TypeRepository,
 | 
			
		||||
		RepoID:       ctx.Repo.Repository.ID,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := project_model.NewProject(ctx, project); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "NewProject", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusCreated, convert.ToProject(ctx, project))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateIssueProject change an issue's project to another project in a repository
 | 
			
		||||
func UpdateIssueProject(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation PUT /repos/{owner}/{reponame}/projects/{type} project updateIssueProject
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Change an issue's project
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: owner
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: owner of the project
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: string
 | 
			
		||||
	// - name: reponame
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: repository name.
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: string
 | 
			
		||||
	// - name: type
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: issue type (issues or pulls)
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: string
 | 
			
		||||
	//   enum:
 | 
			
		||||
	//   - issues
 | 
			
		||||
	//   - pulls
 | 
			
		||||
	// - name: body
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   description: issues data
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//    "$ref": "#/definitions/UpdateIssuesOption"
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "204":
 | 
			
		||||
	//     "$ref": "#/responses/empty"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	form := &api.UpdateIssuesOption{}
 | 
			
		||||
 | 
			
		||||
	if err := json.NewDecoder(ctx.Req.Body).Decode(&form); err != nil {
 | 
			
		||||
		ctx.ServerError("DecodeMovedIssuesForm", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	issues := getActionIssues(ctx, form.Issues)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := issues.LoadProjects(ctx); err != nil {
 | 
			
		||||
		ctx.ServerError("LoadProjects", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := issues.LoadRepositories(ctx); err != nil {
 | 
			
		||||
		ctx.ServerError("LoadProjects", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	projectID := form.ProjectID
 | 
			
		||||
	for _, issue := range issues {
 | 
			
		||||
		if issue.Project != nil && issue.Project.ID == projectID {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, ctx.Doer, projectID, 0); err != nil {
 | 
			
		||||
			if errors.Is(err, util.ErrPermissionDenied) {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			ctx.ServerError("IssueAssignOrRemoveProject", err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Status(http.StatusNoContent)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getActionIssues(ctx *context.APIContext, issuesIDs []int64) issues_model.IssueList {
 | 
			
		||||
 | 
			
		||||
	if len(issuesIDs) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	issues, err := issues_model.GetIssuesByIDs(ctx, issuesIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("GetIssuesByIDs", err)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -27,3 +27,10 @@ type swaggerResponseColumn struct {
 | 
			
		||||
	// in:body
 | 
			
		||||
	Body api.Column `json:"body"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ColumnList
 | 
			
		||||
// swagger:response ColumnList
 | 
			
		||||
type swaggerResponseColumnList struct {
 | 
			
		||||
	// in:body
 | 
			
		||||
	Body []api.Column `json:"body"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										109
									
								
								routers/api/v1/user/project.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								routers/api/v1/user/project.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
			
		||||
// Copyright 2017 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package user
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	project_model "code.gitea.io/gitea/models/project"
 | 
			
		||||
	"code.gitea.io/gitea/modules/optional"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/web"
 | 
			
		||||
	"code.gitea.io/gitea/services/context"
 | 
			
		||||
	"code.gitea.io/gitea/services/convert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CreateProject creates a new project for a user
 | 
			
		||||
func CreateProject(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation POST /user/projects project createProject
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Create a new project for user
 | 
			
		||||
	// consumes:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: body
 | 
			
		||||
	//   in: body
 | 
			
		||||
	//   description: Project data
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   schema:
 | 
			
		||||
	//     "$ref": "#/definitions/CreateProjectOption"
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "201":
 | 
			
		||||
	//     "$ref": "#/responses/Project"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "412":
 | 
			
		||||
	//     "$ref": "#/responses/error"
 | 
			
		||||
	//   "422":
 | 
			
		||||
	//     "$ref": "#/responses/validationError"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	form := web.GetForm(ctx).(*api.CreateProjectOption)
 | 
			
		||||
 | 
			
		||||
	project := &project_model.Project{
 | 
			
		||||
		Title:        form.Title,
 | 
			
		||||
		Description:  form.Content,
 | 
			
		||||
		CreatorID:    ctx.Doer.ID,
 | 
			
		||||
		TemplateType: project_model.TemplateType(form.TemplateType),
 | 
			
		||||
		CardType:     project_model.CardType(form.CardType),
 | 
			
		||||
		Type:         project_model.TypeIndividual,
 | 
			
		||||
		OwnerID:      ctx.ContextUser.ID,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := project_model.NewProject(ctx, project); err != nil {
 | 
			
		||||
		ctx.Error(http.StatusInternalServerError, "NewProject", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusCreated, convert.ToProject(ctx, project))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetProjects returns a list of projects that belong to a user
 | 
			
		||||
func GetProjects(ctx *context.APIContext) {
 | 
			
		||||
	// swagger:operation GET /users/{username}/projects project getProjects
 | 
			
		||||
	// ---
 | 
			
		||||
	// summary: Get a list of projects
 | 
			
		||||
	// produces:
 | 
			
		||||
	// - application/json
 | 
			
		||||
	// parameters:
 | 
			
		||||
	// - name: username
 | 
			
		||||
	//   in: path
 | 
			
		||||
	//   description: owner of the project
 | 
			
		||||
	//   required: true
 | 
			
		||||
	//   type: string
 | 
			
		||||
	// responses:
 | 
			
		||||
	//   "200":
 | 
			
		||||
	//     "$ref": "#/responses/ProjectList"
 | 
			
		||||
	//   "403":
 | 
			
		||||
	//     "$ref": "#/responses/forbidden"
 | 
			
		||||
	//   "404":
 | 
			
		||||
	//     "$ref": "#/responses/notFound"
 | 
			
		||||
	//   "423":
 | 
			
		||||
	//     "$ref": "#/responses/repoArchivedError"
 | 
			
		||||
 | 
			
		||||
	sortType := ctx.FormTrim("sort")
 | 
			
		||||
 | 
			
		||||
	isShowClosed := strings.ToLower(ctx.FormTrim("state")) == "closed"
 | 
			
		||||
 | 
			
		||||
	searchOptions := project_model.SearchOptions{
 | 
			
		||||
		IsClosed: optional.Some(isShowClosed),
 | 
			
		||||
		OrderBy:  project_model.GetSearchOrderByBySortType(sortType),
 | 
			
		||||
		OwnerID:  ctx.ContextUser.ID,
 | 
			
		||||
		Type:     project_model.TypeIndividual,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	projects, err := db.Find[project_model.Project](ctx, &searchOptions)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("FindProjects", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, convert.ToProjects(ctx, projects))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1539
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1539
									
								
								templates/swagger/v1_json.tmpl
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user