mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 03:18:24 +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/organization" | ||||||
| 	"code.gitea.io/gitea/models/perm" | 	"code.gitea.io/gitea/models/perm" | ||||||
| 	access_model "code.gitea.io/gitea/models/perm/access" | 	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" | 	repo_model "code.gitea.io/gitea/models/repo" | ||||||
| 	"code.gitea.io/gitea/models/unit" | 	"code.gitea.io/gitea/models/unit" | ||||||
| 	user_model "code.gitea.io/gitea/models/user" | 	user_model "code.gitea.io/gitea/models/user" | ||||||
| @@ -89,9 +90,9 @@ import ( | |||||||
| 	"code.gitea.io/gitea/routers/api/v1/notify" | 	"code.gitea.io/gitea/routers/api/v1/notify" | ||||||
| 	"code.gitea.io/gitea/routers/api/v1/org" | 	"code.gitea.io/gitea/routers/api/v1/org" | ||||||
| 	"code.gitea.io/gitea/routers/api/v1/packages" | 	"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/repo" | ||||||
| 	"code.gitea.io/gitea/routers/api/v1/settings" | 	"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/api/v1/user" | ||||||
| 	"code.gitea.io/gitea/routers/common" | 	"code.gitea.io/gitea/routers/common" | ||||||
| 	"code.gitea.io/gitea/services/actions" | 	"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) { | func repoAssignment() func(ctx *context.APIContext) { | ||||||
| 	return func(ctx *context.APIContext) { | 	return func(ctx *context.APIContext) { | ||||||
| 		userName := ctx.PathParam("username") | 		userName := ctx.PathParam("username") | ||||||
| @@ -169,6 +278,10 @@ func repoAssignment() func(ctx *context.APIContext) { | |||||||
| 		ctx.Repo.Owner = owner | 		ctx.Repo.Owner = owner | ||||||
| 		ctx.ContextUser = owner | 		ctx.ContextUser = owner | ||||||
|  |  | ||||||
|  | 		if owner.IsOrganization() { | ||||||
|  | 			ctx.Org.Organization = (*organization.Organization)(owner) | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		// Get repository. | 		// Get repository. | ||||||
| 		repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, repoName) | 		repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, repoName) | ||||||
| 		if err != nil { | 		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 | // 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) { | func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { | ||||||
| 	return func(ctx *context.APIContext) { | 	return func(ctx *context.APIContext) { | ||||||
|  | 		if ctx.Repo.Repository == nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
| 		if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { | 		if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { | ||||||
| 			ctx.Error(http.StatusForbidden, "reqRepoWriter", "user should have a permission to write to a repo") | 			ctx.Error(http.StatusForbidden, "reqRepoWriter", "user should have a permission to write to a repo") | ||||||
| 			return | 			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 | // reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin | ||||||
| func reqRepoBranchWriter(ctx *context.APIContext) { | func reqRepoBranchWriter(ctx *context.APIContext) { | ||||||
| 	options, ok := web.GetForm(ctx).(api.FileOptionInterface) | 	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 | // 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) { | func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) { | ||||||
| 	return func(ctx *context.APIContext) { | 	return func(ctx *context.APIContext) { | ||||||
|  | 		if ctx.Repo.Repository == nil { | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		if !ctx.Repo.CanRead(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() { | 		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") | 			ctx.Error(http.StatusForbidden, "reqRepoReader", "user should have specific read permission or be a repo admin or a site admin") | ||||||
| 			return | 			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) { | func orgAssignment(args ...bool) func(ctx *context.APIContext) { | ||||||
| 	var ( | 	var ( | ||||||
| 		assignOrg  bool | 		assignOrg  bool | ||||||
| @@ -569,18 +686,6 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) { | |||||||
| 	return func(ctx *context.APIContext) { | 	return func(ctx *context.APIContext) { | ||||||
| 		ctx.Org = new(context.APIOrganization) | 		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 | 		var err error | ||||||
| 		if assignOrg { | 		if assignOrg { | ||||||
| 			getOrganizationByParams(ctx) | 			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) { | func mustEnableIssues(ctx *context.APIContext) { | ||||||
| 	if !ctx.Repo.CanRead(unit.TypeIssues) { | 	if !ctx.Repo.CanRead(unit.TypeIssues) { | ||||||
| 		if log.IsTrace() { | 		if log.IsTrace() { | ||||||
| @@ -719,6 +830,10 @@ func mustEnableWiki(ctx *context.APIContext) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func mustNotBeArchived(ctx *context.APIContext) { | func mustNotBeArchived(ctx *context.APIContext) { | ||||||
|  | 	if ctx.Repo.Repository == nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if ctx.Repo.Repository.IsArchived { | 	if ctx.Repo.Repository.IsArchived { | ||||||
| 		ctx.Error(http.StatusLocked, "RepoArchived", fmt.Errorf("%s is archived", ctx.Repo.Repository.LogString())) | 		ctx.Error(http.StatusLocked, "RepoArchived", fmt.Errorf("%s is archived", ctx.Repo.Repository.LogString())) | ||||||
| 		return | 		return | ||||||
| @@ -1015,66 +1130,51 @@ func Routes() *web.Router { | |||||||
| 			}, context.UserAssignmentAPI(), individualPermsChecker) | 			}, context.UserAssignmentAPI(), individualPermsChecker) | ||||||
| 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser)) | 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser)) | ||||||
|  |  | ||||||
| 		// Users (requires user scope) | 		// Projects | ||||||
| 		m.Group("/{username}/-", func() { | 		m.Group("/orgs/{org}/projects", func() { | ||||||
| 			m.Group("/projects", func() { | 			m.Get("", reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true), org.GetProjects) | ||||||
| 				m.Group("", func() { | 			m.Post("", reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), bind(api.CreateProjectOption{}), org.CreateProject) | ||||||
| 					m.Get("", project_shared.ProjectHandler("org", project_shared.GetProjects)) | 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true)) | ||||||
| 					m.Get("/{id}", project_shared.ProjectHandler("org", project_shared.GetProject)) |  | ||||||
| 				}) | 		m.Group("/projects", func() { | ||||||
|  | 			m.Group("/{project_id}", func() { | ||||||
|  | 				m.Get("", project.GetProject) | ||||||
|  | 				m.Get("/columns", project.GetProjectColumns) | ||||||
|  |  | ||||||
| 				m.Group("", func() { | 				m.Group("", func() { | ||||||
| 					m.Post("", bind(api.CreateProjectOption{}), project_shared.ProjectHandler("org", project_shared.CreateProject)) | 					m.Patch("", bind(api.EditProjectOption{}), project.EditProject) | ||||||
| 					m.Group("/{id}", func() { | 					m.Delete("", project.DeleteProject) | ||||||
| 						m.Post("", bind(api.CreateProjectColumnOption{}), project_shared.ProjectHandler("org", project_shared.AddColumnToProject)) | 					m.Post("/{action:open|close}", project.ChangeProjectStatus) | ||||||
| 						m.Delete("", project_shared.ProjectHandler("org", project_shared.DeleteProject)) | 					m.Group("/columns", func() { | ||||||
| 						m.Put("", bind(api.EditProjectOption{}), project_shared.ProjectHandler("org", project_shared.EditProject)) | 						m.Post("", bind(api.CreateProjectColumnOption{}), project.AddColumnToProject) | ||||||
| 						m.Post("/move", project_shared.MoveColumns) | 						m.Patch("/move", project.MoveColumns) | ||||||
| 						m.Post("/{action:open|close}", project_shared.ChangeProjectStatus) | 						m.Patch("/{column_id}/move", project.MoveIssues) | ||||||
|  |  | ||||||
| 						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)) |  | ||||||
| 						}) |  | ||||||
| 					}) | 					}) | ||||||
| 				}, reqSelfOrAdmin(), reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true)) | 				}, reqRepoWriter(unit.TypeProjects), mustNotBeArchived, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), reqProjectOwner()) | ||||||
| 			}, individualPermsChecker) | 			}) | ||||||
|  |  | ||||||
| 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), reqToken(), context.UserAssignmentAPI(), orgAssignment(), reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true)) | 			m.Group("/columns/{column_id}", func() { | ||||||
|  | 				m.Get("", project.GetProjectColumn) | ||||||
| 		// 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("", func() { | 				m.Group("", func() { | ||||||
| 					m.Post("", bind(api.CreateProjectOption{}), project_shared.ProjectHandler("repo", project_shared.CreateProject)) | 					m.Patch("", bind(api.EditProjectColumnOption{}), project.EditProjectColumn) | ||||||
| 					m.Group("/{id}", func() { | 					m.Delete("", project.DeleteProjectColumn) | ||||||
| 						m.Post("", bind(api.CreateProjectColumnOption{}), project_shared.ProjectHandler("repo", project_shared.AddColumnToProject)) | 					m.Post("/default", project.SetDefaultProjectColumn) | ||||||
| 						m.Delete("", project_shared.ProjectHandler("repo", project_shared.DeleteProject)) | 				}, reqRepoWriter(unit.TypeProjects), mustNotBeArchived, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), reqProjectOwner()) | ||||||
| 						m.Put("", bind(api.EditProjectOption{}), project_shared.ProjectHandler("repo", project_shared.EditProject)) | 			}) | ||||||
| 						m.Post("/move", project_shared.MoveColumns) | 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository), reqToken(), projectIDAssignmentAPI(), columnAssignment(), individualPermsChecker, reqRepoReader(unit.TypeProjects), mustEnableRepoProjects, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true)) | ||||||
| 						m.Post("/{action:open|close}", project_shared.ChangeProjectStatus) |  | ||||||
|  |  | ||||||
| 						m.Group("/{columnID}", func() { | 		m.Group("/repos/{username}/{reponame}/projects", func() { | ||||||
| 							m.Put("", bind(api.EditProjectColumnOption{}), project_shared.ProjectHandler("repo", project_shared.EditProjectColumn)) | 			m.Get("", repo.GetProjects) | ||||||
| 							m.Delete("", project_shared.ProjectHandler("repo", project_shared.DeleteProjectColumn)) | 			m.Group("", func() { | ||||||
| 							m.Post("/default", project_shared.ProjectHandler("repo", project_shared.SetDefaultProjectColumn)) | 				m.Post("", bind(api.CreateProjectOption{}), repo.CreateProject) | ||||||
| 							m.Post("/move", project_shared.ProjectHandler("repo", project_shared.MoveIssues)) | 				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) | ||||||
| 				}, reqRepoWriter(unit.TypeProjects), mustNotBeArchived) |  | ||||||
| 			}, individualPermsChecker) |  | ||||||
|  |  | ||||||
| 			m.Group("/{type:issues|pulls}", func() { | 		m.Post("/user/projects", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken(), getAuthenticatedUser, reqSelfOrAdmin(), bind(api.CreateProjectOption{}), user.CreateProject) | ||||||
| 				m.Post("/projects", reqRepoWriterOr(unit.TypeIssues, unit.TypePullRequests), reqRepoWriter(unit.TypeProjects), project_shared.UpdateIssueProject) |  | ||||||
| 			}, individualPermsChecker) | 		m.Get("/users/{username}/projects", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken(), context.UserAssignmentAPI(), individualPermsChecker, user.GetProjects) | ||||||
| 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository), reqToken(), repoAssignment(), reqRepoReader(unit.TypeProjects), mustEnableRepoProjects) |  | ||||||
|  |  | ||||||
| 		// Users (requires user scope) | 		// Users (requires user scope) | ||||||
| 		m.Group("/users", func() { | 		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 | 	// in:body | ||||||
| 	Body api.Column `json:"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