mirror of
https://github.com/go-gitea/gitea.git
synced 2025-08-24 17:39:56 +00:00
feat: api for projects
This commit is contained in:
parent
6092b81563
commit
b6f9d05180
@ -95,6 +95,7 @@ type Project struct {
|
|||||||
RepoID int64 `xorm:"INDEX"`
|
RepoID int64 `xorm:"INDEX"`
|
||||||
Repo *repo_model.Repository `xorm:"-"`
|
Repo *repo_model.Repository `xorm:"-"`
|
||||||
CreatorID int64 `xorm:"NOT NULL"`
|
CreatorID int64 `xorm:"NOT NULL"`
|
||||||
|
Creator *user_model.User `xorm:"-"`
|
||||||
IsClosed bool `xorm:"INDEX"`
|
IsClosed bool `xorm:"INDEX"`
|
||||||
BoardType BoardType
|
BoardType BoardType
|
||||||
CardType CardType
|
CardType CardType
|
||||||
@ -115,6 +116,14 @@ func (p *Project) LoadOwner(ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Project) LoadCreator(ctx context.Context) (err error) {
|
||||||
|
if p.Creator != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
p.Creator, err = user_model.GetUserByID(ctx, p.CreatorID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Project) LoadRepo(ctx context.Context) (err error) {
|
func (p *Project) LoadRepo(ctx context.Context) (err error) {
|
||||||
if p.RepoID == 0 || p.Repo != nil {
|
if p.RepoID == 0 || p.Repo != nil {
|
||||||
return nil
|
return nil
|
||||||
@ -348,7 +357,11 @@ func updateRepositoryProjectCount(ctx context.Context, repoID int64) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ChangeProjectStatusByRepoIDAndID toggles a project between opened and closed
|
// ChangeProjectStatusByRepoIDAndID toggles a project between opened and closed
|
||||||
func ChangeProjectStatusByRepoIDAndID(ctx context.Context, repoID, projectID int64, isClosed bool) error {
|
func ChangeProjectStatusByRepoIDAndID(
|
||||||
|
ctx context.Context,
|
||||||
|
repoID, projectID int64,
|
||||||
|
isClosed bool,
|
||||||
|
) error {
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -389,7 +402,11 @@ func ChangeProjectStatus(ctx context.Context, p *Project, isClosed bool) error {
|
|||||||
func changeProjectStatus(ctx context.Context, p *Project, isClosed bool) error {
|
func changeProjectStatus(ctx context.Context, p *Project, isClosed bool) error {
|
||||||
p.IsClosed = isClosed
|
p.IsClosed = isClosed
|
||||||
p.ClosedDateUnix = timeutil.TimeStampNow()
|
p.ClosedDateUnix = timeutil.TimeStampNow()
|
||||||
count, err := db.GetEngine(ctx).ID(p.ID).Where("repo_id = ? AND is_closed = ?", p.RepoID, !isClosed).Cols("is_closed", "closed_date_unix").Update(p)
|
count, err := db.GetEngine(ctx).
|
||||||
|
ID(p.ID).
|
||||||
|
Where("repo_id = ? AND is_closed = ?", p.RepoID, !isClosed).
|
||||||
|
Cols("is_closed", "closed_date_unix").
|
||||||
|
Update(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -88,6 +88,7 @@ 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/projects"
|
||||||
"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"
|
||||||
"code.gitea.io/gitea/routers/api/v1/user"
|
"code.gitea.io/gitea/routers/api/v1/user"
|
||||||
@ -231,7 +232,11 @@ func repoAssignment() func(ctx *context.APIContext) {
|
|||||||
func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.APIContext) {
|
func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.APIContext) {
|
||||||
return func(ctx *context.APIContext) {
|
return func(ctx *context.APIContext) {
|
||||||
if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() {
|
if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() {
|
||||||
ctx.Error(http.StatusForbidden, "reqPackageAccess", "user should have specific permission or be a site admin")
|
ctx.Error(
|
||||||
|
http.StatusForbidden,
|
||||||
|
"reqPackageAccess",
|
||||||
|
"user should have specific permission or be a site admin",
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,7 +244,9 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.APIContext)
|
|||||||
|
|
||||||
// if a token is being used for auth, we check that it contains the required scope
|
// if a token is being used for auth, we check that it contains the required scope
|
||||||
// if a token is not being used, reqToken will enforce other sign in methods
|
// if a token is not being used, reqToken will enforce other sign in methods
|
||||||
func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeCategory) func(ctx *context.APIContext) {
|
func tokenRequiresScopes(
|
||||||
|
requiredScopeCategories ...auth_model.AccessTokenScopeCategory,
|
||||||
|
) func(ctx *context.APIContext) {
|
||||||
return func(ctx *context.APIContext) {
|
return func(ctx *context.APIContext) {
|
||||||
// no scope required
|
// no scope required
|
||||||
if len(requiredScopeCategories) == 0 {
|
if len(requiredScopeCategories) == 0 {
|
||||||
@ -257,27 +264,46 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
|
|||||||
|
|
||||||
// use the http method to determine the access level
|
// use the http method to determine the access level
|
||||||
requiredScopeLevel := auth_model.Read
|
requiredScopeLevel := auth_model.Read
|
||||||
if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || ctx.Req.Method == "PATCH" || ctx.Req.Method == "DELETE" {
|
if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || ctx.Req.Method == "PATCH" ||
|
||||||
|
ctx.Req.Method == "DELETE" {
|
||||||
requiredScopeLevel = auth_model.Write
|
requiredScopeLevel = auth_model.Write
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the required scope for the given access level and category
|
// get the required scope for the given access level and category
|
||||||
requiredScopes := auth_model.GetRequiredScopes(requiredScopeLevel, requiredScopeCategories...)
|
requiredScopes := auth_model.GetRequiredScopes(
|
||||||
|
requiredScopeLevel,
|
||||||
|
requiredScopeCategories...)
|
||||||
|
|
||||||
// check if scope only applies to public resources
|
// check if scope only applies to public resources
|
||||||
publicOnly, err := scope.PublicOnly()
|
publicOnly, err := scope.PublicOnly()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error())
|
ctx.Error(
|
||||||
|
http.StatusForbidden,
|
||||||
|
"tokenRequiresScope",
|
||||||
|
"parsing public resource scope failed: "+err.Error(),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// this context is used by the middleware in the specific route
|
// this context is used by the middleware in the specific route
|
||||||
ctx.Data["ApiTokenScopePublicRepoOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository)
|
ctx.Data["ApiTokenScopePublicRepoOnly"] = publicOnly &&
|
||||||
ctx.Data["ApiTokenScopePublicOrgOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization)
|
auth_model.ContainsCategory(
|
||||||
|
requiredScopeCategories,
|
||||||
|
auth_model.AccessTokenScopeCategoryRepository,
|
||||||
|
)
|
||||||
|
ctx.Data["ApiTokenScopePublicOrgOnly"] = publicOnly &&
|
||||||
|
auth_model.ContainsCategory(
|
||||||
|
requiredScopeCategories,
|
||||||
|
auth_model.AccessTokenScopeCategoryOrganization,
|
||||||
|
)
|
||||||
|
|
||||||
allow, err := scope.HasScope(requiredScopes...)
|
allow, err := scope.HasScope(requiredScopes...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error())
|
ctx.Error(
|
||||||
|
http.StatusForbidden,
|
||||||
|
"tokenRequiresScope",
|
||||||
|
"checking scope failed: "+err.Error(),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,7 +311,14 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes))
|
ctx.Error(
|
||||||
|
http.StatusForbidden,
|
||||||
|
"tokenRequiresScope",
|
||||||
|
fmt.Sprintf(
|
||||||
|
"token does not have at least one of required scope(s): %v",
|
||||||
|
requiredScopes,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,7 +336,11 @@ func reqToken() func(ctx *context.APIContext) {
|
|||||||
|
|
||||||
if pubRepoExists && publicRepo.(bool) &&
|
if pubRepoExists && publicRepo.(bool) &&
|
||||||
ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
|
ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
|
||||||
ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos")
|
ctx.Error(
|
||||||
|
http.StatusForbidden,
|
||||||
|
"reqToken",
|
||||||
|
"token scope is limited to public repos",
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,14 +363,19 @@ func reqToken() func(ctx *context.APIContext) {
|
|||||||
func reqExploreSignIn() func(ctx *context.APIContext) {
|
func reqExploreSignIn() func(ctx *context.APIContext) {
|
||||||
return func(ctx *context.APIContext) {
|
return func(ctx *context.APIContext) {
|
||||||
if setting.Service.Explore.RequireSigninView && !ctx.IsSigned {
|
if setting.Service.Explore.RequireSigninView && !ctx.IsSigned {
|
||||||
ctx.Error(http.StatusUnauthorized, "reqExploreSignIn", "you must be signed in to search for users")
|
ctx.Error(
|
||||||
|
http.StatusUnauthorized,
|
||||||
|
"reqExploreSignIn",
|
||||||
|
"you must be signed in to search for users",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) {
|
func reqBasicOrRevProxyAuth() func(ctx *context.APIContext) {
|
||||||
return func(ctx *context.APIContext) {
|
return func(ctx *context.APIContext) {
|
||||||
if ctx.IsSigned && setting.Service.EnableReverseProxyAuthAPI && ctx.Data["AuthedMethod"].(string) == auth.ReverseProxyMethodName {
|
if ctx.IsSigned && setting.Service.EnableReverseProxyAuthAPI &&
|
||||||
|
ctx.Data["AuthedMethod"].(string) == auth.ReverseProxyMethodName {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !ctx.IsBasicAuth {
|
if !ctx.IsBasicAuth {
|
||||||
@ -367,7 +409,11 @@ func reqOwner() func(ctx *context.APIContext) {
|
|||||||
func reqSelfOrAdmin() func(ctx *context.APIContext) {
|
func reqSelfOrAdmin() func(ctx *context.APIContext) {
|
||||||
return func(ctx *context.APIContext) {
|
return func(ctx *context.APIContext) {
|
||||||
if !ctx.IsUserSiteAdmin() && ctx.ContextUser != ctx.Doer {
|
if !ctx.IsUserSiteAdmin() && ctx.ContextUser != ctx.Doer {
|
||||||
ctx.Error(http.StatusForbidden, "reqSelfOrAdmin", "doer should be the site admin or be same as the contextUser")
|
ctx.Error(
|
||||||
|
http.StatusForbidden,
|
||||||
|
"reqSelfOrAdmin",
|
||||||
|
"doer should be the site admin or be same as the contextUser",
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -377,7 +423,11 @@ func reqSelfOrAdmin() func(ctx *context.APIContext) {
|
|||||||
func reqAdmin() func(ctx *context.APIContext) {
|
func reqAdmin() func(ctx *context.APIContext) {
|
||||||
return func(ctx *context.APIContext) {
|
return func(ctx *context.APIContext) {
|
||||||
if !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
|
if !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
|
||||||
ctx.Error(http.StatusForbidden, "reqAdmin", "user should be an owner or a collaborator with admin write of a repository")
|
ctx.Error(
|
||||||
|
http.StatusForbidden,
|
||||||
|
"reqAdmin",
|
||||||
|
"user should be an owner or a collaborator with admin write of a repository",
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -387,7 +437,11 @@ func reqAdmin() func(ctx *context.APIContext) {
|
|||||||
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.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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -396,8 +450,13 @@ func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) {
|
|||||||
// 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)
|
||||||
if !ok || (!ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) {
|
if !ok ||
|
||||||
ctx.Error(http.StatusForbidden, "reqRepoBranchWriter", "user should have a permission to write to this branch")
|
(!ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) {
|
||||||
|
ctx.Error(
|
||||||
|
http.StatusForbidden,
|
||||||
|
"reqRepoBranchWriter",
|
||||||
|
"user should have a permission to write to this branch",
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -406,7 +465,11 @@ func reqRepoBranchWriter(ctx *context.APIContext) {
|
|||||||
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.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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -416,7 +479,11 @@ func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) {
|
|||||||
func reqAnyRepoReader() func(ctx *context.APIContext) {
|
func reqAnyRepoReader() func(ctx *context.APIContext) {
|
||||||
return func(ctx *context.APIContext) {
|
return func(ctx *context.APIContext) {
|
||||||
if !ctx.Repo.HasAccess() && !ctx.IsUserSiteAdmin() {
|
if !ctx.Repo.HasAccess() && !ctx.IsUserSiteAdmin() {
|
||||||
ctx.Error(http.StatusForbidden, "reqAnyRepoReader", "user should have any permission to read repository or permissions of site admin")
|
ctx.Error(
|
||||||
|
http.StatusForbidden,
|
||||||
|
"reqAnyRepoReader",
|
||||||
|
"user should have any permission to read repository or permissions of site admin",
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -671,7 +738,11 @@ func mustEnableWiki(ctx *context.APIContext) {
|
|||||||
|
|
||||||
func mustNotBeArchived(ctx *context.APIContext) {
|
func mustNotBeArchived(ctx *context.APIContext) {
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -689,7 +760,11 @@ func bind[T any](_ T) any {
|
|||||||
theObj := new(T) // create a new form obj for every request but not use obj directly
|
theObj := new(T) // create a new form obj for every request but not use obj directly
|
||||||
errs := binding.Bind(ctx.Req, theObj)
|
errs := binding.Bind(ctx.Req, theObj)
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
ctx.Error(http.StatusUnprocessableEntity, "validationError", fmt.Sprintf("%s: %s", errs[0].FieldNames, errs[0].Error()))
|
ctx.Error(
|
||||||
|
http.StatusUnprocessableEntity,
|
||||||
|
"validationError",
|
||||||
|
fmt.Sprintf("%s: %s", errs[0].FieldNames, errs[0].Error()),
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
web.SetForm(ctx, theObj)
|
web.SetForm(ctx, theObj)
|
||||||
@ -739,7 +814,11 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIC
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin {
|
if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin {
|
||||||
log.Info("Failed authentication attempt for %s from %s", ctx.Doer.Name, ctx.RemoteAddr())
|
log.Info(
|
||||||
|
"Failed authentication attempt for %s from %s",
|
||||||
|
ctx.Doer.Name,
|
||||||
|
ctx.RemoteAddr(),
|
||||||
|
)
|
||||||
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
|
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
|
||||||
ctx.JSON(http.StatusForbidden, map[string]string{
|
ctx.JSON(http.StatusForbidden, map[string]string{
|
||||||
"message": "This account is prohibited from signing in, please contact your site administrator.",
|
"message": "This account is prohibited from signing in, please contact your site administrator.",
|
||||||
@ -800,8 +879,10 @@ func Routes() *web.Route {
|
|||||||
// setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option
|
// setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option
|
||||||
AllowedMethods: setting.CORSConfig.Methods,
|
AllowedMethods: setting.CORSConfig.Methods,
|
||||||
AllowCredentials: setting.CORSConfig.AllowCredentials,
|
AllowCredentials: setting.CORSConfig.AllowCredentials,
|
||||||
AllowedHeaders: append([]string{"Authorization", "X-Gitea-OTP"}, setting.CORSConfig.Headers...),
|
AllowedHeaders: append(
|
||||||
MaxAge: int(setting.CORSConfig.MaxAge.Seconds()),
|
[]string{"Authorization", "X-Gitea-OTP"},
|
||||||
|
setting.CORSConfig.Headers...),
|
||||||
|
MaxAge: int(setting.CORSConfig.MaxAge.Seconds()),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
m.Use(context.APIContexter())
|
m.Use(context.APIContexter())
|
||||||
@ -880,7 +961,12 @@ func Routes() *web.Route {
|
|||||||
m.Get("/heatmap", user.GetUserHeatmapData)
|
m.Get("/heatmap", user.GetUserHeatmapData)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Get("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), reqExploreSignIn(), user.ListUserRepos)
|
m.Get(
|
||||||
|
"/repos",
|
||||||
|
tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository),
|
||||||
|
reqExploreSignIn(),
|
||||||
|
user.ListUserRepos,
|
||||||
|
)
|
||||||
m.Group("/tokens", func() {
|
m.Group("/tokens", func() {
|
||||||
m.Combo("").Get(user.ListAccessTokens).
|
m.Combo("").Get(user.ListAccessTokens).
|
||||||
Post(bind(api.CreateAccessTokenOption{}), reqToken(), user.CreateAccessToken)
|
Post(bind(api.CreateAccessTokenOption{}), reqToken(), user.CreateAccessToken)
|
||||||
@ -968,7 +1054,8 @@ func Routes() *web.Route {
|
|||||||
m.Post("/gpg_key_verify", bind(api.VerifyGPGKeyOption{}), user.VerifyUserGPGKey)
|
m.Post("/gpg_key_verify", bind(api.VerifyGPGKeyOption{}), user.VerifyUserGPGKey)
|
||||||
|
|
||||||
// (repo scope)
|
// (repo scope)
|
||||||
m.Combo("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(user.ListMyRepos).
|
m.Combo("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).
|
||||||
|
Get(user.ListMyRepos).
|
||||||
Post(bind(api.CreateRepoOption{}), repo.Create)
|
Post(bind(api.CreateRepoOption{}), repo.Create)
|
||||||
|
|
||||||
// (repo scope)
|
// (repo scope)
|
||||||
@ -996,17 +1083,27 @@ func Routes() *web.Route {
|
|||||||
m.Post("", bind(api.UpdateUserAvatarOption{}), user.UpdateAvatar)
|
m.Post("", bind(api.UpdateUserAvatarOption{}), user.UpdateAvatar)
|
||||||
m.Delete("", user.DeleteAvatar)
|
m.Delete("", user.DeleteAvatar)
|
||||||
}, reqToken())
|
}, reqToken())
|
||||||
|
|
||||||
|
m.Combo("/projects", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue)).
|
||||||
|
Get(projects.ListUserProjects).
|
||||||
|
Post(bind(api.NewProjectPayload{}), projects.CreateUserProject)
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
|
||||||
|
|
||||||
// Repositories (requires repo scope, org scope)
|
// Repositories (requires repo scope, org scope)
|
||||||
m.Post("/org/{org}/repos",
|
m.Post(
|
||||||
tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository),
|
"/org/{org}/repos",
|
||||||
|
tokenRequiresScopes(
|
||||||
|
auth_model.AccessTokenScopeCategoryOrganization,
|
||||||
|
auth_model.AccessTokenScopeCategoryRepository,
|
||||||
|
),
|
||||||
reqToken(),
|
reqToken(),
|
||||||
bind(api.CreateRepoOption{}),
|
bind(api.CreateRepoOption{}),
|
||||||
repo.CreateOrgRepoDeprecated)
|
repo.CreateOrgRepoDeprecated,
|
||||||
|
)
|
||||||
|
|
||||||
// requires repo scope
|
// requires repo scope
|
||||||
m.Combo("/repositories/{id}", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(repo.GetByID)
|
m.Combo("/repositories/{id}", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).
|
||||||
|
Get(repo.GetByID)
|
||||||
|
|
||||||
// Repos (requires repo scope)
|
// Repos (requires repo scope)
|
||||||
m.Group("/repos", func() {
|
m.Group("/repos", func() {
|
||||||
@ -1019,7 +1116,13 @@ func Routes() *web.Route {
|
|||||||
m.Combo("").Get(reqAnyRepoReader(), repo.Get).
|
m.Combo("").Get(reqAnyRepoReader(), repo.Get).
|
||||||
Delete(reqToken(), reqOwner(), repo.Delete).
|
Delete(reqToken(), reqOwner(), repo.Delete).
|
||||||
Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit)
|
Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit)
|
||||||
m.Post("/generate", reqToken(), reqRepoReader(unit.TypeCode), bind(api.GenerateRepoOption{}), repo.Generate)
|
m.Post(
|
||||||
|
"/generate",
|
||||||
|
reqToken(),
|
||||||
|
reqRepoReader(unit.TypeCode),
|
||||||
|
bind(api.GenerateRepoOption{}),
|
||||||
|
repo.Generate,
|
||||||
|
)
|
||||||
m.Group("/transfer", func() {
|
m.Group("/transfer", func() {
|
||||||
m.Post("", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer)
|
m.Post("", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer)
|
||||||
m.Post("/accept", repo.AcceptTransfer)
|
m.Post("/accept", repo.AcceptTransfer)
|
||||||
@ -1045,7 +1148,12 @@ func Routes() *web.Route {
|
|||||||
m.Combo("").Get(repo.GetHook).
|
m.Combo("").Get(repo.GetHook).
|
||||||
Patch(bind(api.EditHookOption{}), repo.EditHook).
|
Patch(bind(api.EditHookOption{}), repo.EditHook).
|
||||||
Delete(repo.DeleteHook)
|
Delete(repo.DeleteHook)
|
||||||
m.Post("/tests", context.ReferencesGitRepo(), context.RepoRefForAPI, repo.TestHook)
|
m.Post(
|
||||||
|
"/tests",
|
||||||
|
context.ReferencesGitRepo(),
|
||||||
|
context.RepoRefForAPI,
|
||||||
|
repo.TestHook,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}, reqToken(), reqAdmin(), reqWebhooksEnabled())
|
}, reqToken(), reqAdmin(), reqWebhooksEnabled())
|
||||||
m.Group("/collaborators", func() {
|
m.Group("/collaborators", func() {
|
||||||
@ -1065,31 +1173,79 @@ func Routes() *web.Route {
|
|||||||
Put(reqAdmin(), repo.AddTeam).
|
Put(reqAdmin(), repo.AddTeam).
|
||||||
Delete(reqAdmin(), repo.DeleteTeam)
|
Delete(reqAdmin(), repo.DeleteTeam)
|
||||||
}, reqToken())
|
}, reqToken())
|
||||||
m.Get("/raw/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFile)
|
m.Get(
|
||||||
m.Get("/media/*", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetRawFileOrLFS)
|
"/raw/*",
|
||||||
|
context.ReferencesGitRepo(),
|
||||||
|
context.RepoRefForAPI,
|
||||||
|
reqRepoReader(unit.TypeCode),
|
||||||
|
repo.GetRawFile,
|
||||||
|
)
|
||||||
|
m.Get(
|
||||||
|
"/media/*",
|
||||||
|
context.ReferencesGitRepo(),
|
||||||
|
context.RepoRefForAPI,
|
||||||
|
reqRepoReader(unit.TypeCode),
|
||||||
|
repo.GetRawFileOrLFS,
|
||||||
|
)
|
||||||
m.Get("/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
|
m.Get("/archive/*", reqRepoReader(unit.TypeCode), repo.GetArchive)
|
||||||
m.Combo("/forks").Get(repo.ListForks).
|
m.Combo("/forks").Get(repo.ListForks).
|
||||||
Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
|
Post(reqToken(), reqRepoReader(unit.TypeCode), bind(api.CreateForkOption{}), repo.CreateFork)
|
||||||
m.Group("/branches", func() {
|
m.Group("/branches", func() {
|
||||||
m.Get("", repo.ListBranches)
|
m.Get("", repo.ListBranches)
|
||||||
m.Get("/*", repo.GetBranch)
|
m.Get("/*", repo.GetBranch)
|
||||||
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteBranch)
|
m.Delete(
|
||||||
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateBranchRepoOption{}), repo.CreateBranch)
|
"/*",
|
||||||
|
reqToken(),
|
||||||
|
reqRepoWriter(unit.TypeCode),
|
||||||
|
mustNotBeArchived,
|
||||||
|
repo.DeleteBranch,
|
||||||
|
)
|
||||||
|
m.Post(
|
||||||
|
"",
|
||||||
|
reqToken(),
|
||||||
|
reqRepoWriter(unit.TypeCode),
|
||||||
|
mustNotBeArchived,
|
||||||
|
bind(api.CreateBranchRepoOption{}),
|
||||||
|
repo.CreateBranch,
|
||||||
|
)
|
||||||
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
|
}, context.ReferencesGitRepo(), reqRepoReader(unit.TypeCode))
|
||||||
m.Group("/branch_protections", func() {
|
m.Group("/branch_protections", func() {
|
||||||
m.Get("", repo.ListBranchProtections)
|
m.Get("", repo.ListBranchProtections)
|
||||||
m.Post("", bind(api.CreateBranchProtectionOption{}), mustNotBeArchived, repo.CreateBranchProtection)
|
m.Post(
|
||||||
|
"",
|
||||||
|
bind(api.CreateBranchProtectionOption{}),
|
||||||
|
mustNotBeArchived,
|
||||||
|
repo.CreateBranchProtection,
|
||||||
|
)
|
||||||
m.Group("/{name}", func() {
|
m.Group("/{name}", func() {
|
||||||
m.Get("", repo.GetBranchProtection)
|
m.Get("", repo.GetBranchProtection)
|
||||||
m.Patch("", bind(api.EditBranchProtectionOption{}), mustNotBeArchived, repo.EditBranchProtection)
|
m.Patch(
|
||||||
|
"",
|
||||||
|
bind(api.EditBranchProtectionOption{}),
|
||||||
|
mustNotBeArchived,
|
||||||
|
repo.EditBranchProtection,
|
||||||
|
)
|
||||||
m.Delete("", repo.DeleteBranchProtection)
|
m.Delete("", repo.DeleteBranchProtection)
|
||||||
})
|
})
|
||||||
}, reqToken(), reqAdmin())
|
}, reqToken(), reqAdmin())
|
||||||
m.Group("/tags", func() {
|
m.Group("/tags", func() {
|
||||||
m.Get("", repo.ListTags)
|
m.Get("", repo.ListTags)
|
||||||
m.Get("/*", repo.GetTag)
|
m.Get("/*", repo.GetTag)
|
||||||
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateTagOption{}), repo.CreateTag)
|
m.Post(
|
||||||
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteTag)
|
"",
|
||||||
|
reqToken(),
|
||||||
|
reqRepoWriter(unit.TypeCode),
|
||||||
|
mustNotBeArchived,
|
||||||
|
bind(api.CreateTagOption{}),
|
||||||
|
repo.CreateTag,
|
||||||
|
)
|
||||||
|
m.Delete(
|
||||||
|
"/*",
|
||||||
|
reqToken(),
|
||||||
|
reqRepoWriter(unit.TypeCode),
|
||||||
|
mustNotBeArchived,
|
||||||
|
repo.DeleteTag,
|
||||||
|
)
|
||||||
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true))
|
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true))
|
||||||
m.Group("/keys", func() {
|
m.Group("/keys", func() {
|
||||||
m.Combo("").Get(repo.ListDeployKeys).
|
m.Combo("").Get(repo.ListDeployKeys).
|
||||||
@ -1107,7 +1263,14 @@ func Routes() *web.Route {
|
|||||||
Patch(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.EditWikiPage).
|
Patch(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.EditWikiPage).
|
||||||
Delete(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), repo.DeleteWikiPage)
|
Delete(mustNotBeArchived, reqToken(), reqRepoWriter(unit.TypeWiki), repo.DeleteWikiPage)
|
||||||
m.Get("/revisions/{pageName}", repo.ListPageRevisions)
|
m.Get("/revisions/{pageName}", repo.ListPageRevisions)
|
||||||
m.Post("/new", reqToken(), mustNotBeArchived, reqRepoWriter(unit.TypeWiki), bind(api.CreateWikiPageOptions{}), repo.NewWikiPage)
|
m.Post(
|
||||||
|
"/new",
|
||||||
|
reqToken(),
|
||||||
|
mustNotBeArchived,
|
||||||
|
reqRepoWriter(unit.TypeWiki),
|
||||||
|
bind(api.CreateWikiPageOptions{}),
|
||||||
|
repo.NewWikiPage,
|
||||||
|
)
|
||||||
m.Get("/pages", repo.ListWikiPages)
|
m.Get("/pages", repo.ListWikiPages)
|
||||||
}, mustEnableWiki)
|
}, mustEnableWiki)
|
||||||
m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
|
m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
|
||||||
@ -1152,7 +1315,13 @@ func Routes() *web.Route {
|
|||||||
Get(repo.GetPushMirrorByName)
|
Get(repo.GetPushMirrorByName)
|
||||||
}, reqAdmin(), reqToken())
|
}, reqAdmin(), reqToken())
|
||||||
|
|
||||||
m.Get("/editorconfig/{filename}", context.ReferencesGitRepo(), context.RepoRefForAPI, reqRepoReader(unit.TypeCode), repo.GetEditorconfig)
|
m.Get(
|
||||||
|
"/editorconfig/{filename}",
|
||||||
|
context.ReferencesGitRepo(),
|
||||||
|
context.RepoRefForAPI,
|
||||||
|
reqRepoReader(unit.TypeCode),
|
||||||
|
repo.GetEditorconfig,
|
||||||
|
)
|
||||||
m.Group("/pulls", func() {
|
m.Group("/pulls", func() {
|
||||||
m.Combo("").Get(repo.ListPullRequests).
|
m.Combo("").Get(repo.ListPullRequests).
|
||||||
Post(reqToken(), mustNotBeArchived, bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)
|
Post(reqToken(), mustNotBeArchived, bind(api.CreatePullRequestOption{}), repo.CreatePullRequest)
|
||||||
@ -1178,7 +1347,12 @@ func Routes() *web.Route {
|
|||||||
Post(reqToken(), bind(api.SubmitPullReviewOptions{}), repo.SubmitPullReview)
|
Post(reqToken(), bind(api.SubmitPullReviewOptions{}), repo.SubmitPullReview)
|
||||||
m.Combo("/comments").
|
m.Combo("/comments").
|
||||||
Get(repo.GetPullReviewComments)
|
Get(repo.GetPullReviewComments)
|
||||||
m.Post("/dismissals", reqToken(), bind(api.DismissPullReviewOptions{}), repo.DismissPullReview)
|
m.Post(
|
||||||
|
"/dismissals",
|
||||||
|
reqToken(),
|
||||||
|
bind(api.DismissPullReviewOptions{}),
|
||||||
|
repo.DismissPullReview,
|
||||||
|
)
|
||||||
m.Post("/undismissals", reqToken(), repo.UnDismissPullReview)
|
m.Post("/undismissals", reqToken(), repo.UnDismissPullReview)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1210,15 +1384,47 @@ func Routes() *web.Route {
|
|||||||
m.Get("/tags/{sha}", repo.GetAnnotatedTag)
|
m.Get("/tags/{sha}", repo.GetAnnotatedTag)
|
||||||
m.Get("/notes/{sha}", repo.GetNote)
|
m.Get("/notes/{sha}", repo.GetNote)
|
||||||
}, context.ReferencesGitRepo(true), reqRepoReader(unit.TypeCode))
|
}, context.ReferencesGitRepo(true), reqRepoReader(unit.TypeCode))
|
||||||
m.Post("/diffpatch", reqRepoWriter(unit.TypeCode), reqToken(), bind(api.ApplyDiffPatchFileOptions{}), mustNotBeArchived, repo.ApplyDiffPatch)
|
m.Post(
|
||||||
|
"/diffpatch",
|
||||||
|
reqRepoWriter(unit.TypeCode),
|
||||||
|
reqToken(),
|
||||||
|
bind(api.ApplyDiffPatchFileOptions{}),
|
||||||
|
mustNotBeArchived,
|
||||||
|
repo.ApplyDiffPatch,
|
||||||
|
)
|
||||||
m.Group("/contents", func() {
|
m.Group("/contents", func() {
|
||||||
m.Get("", repo.GetContentsList)
|
m.Get("", repo.GetContentsList)
|
||||||
m.Post("", reqToken(), bind(api.ChangeFilesOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.ChangeFiles)
|
m.Post(
|
||||||
|
"",
|
||||||
|
reqToken(),
|
||||||
|
bind(api.ChangeFilesOptions{}),
|
||||||
|
reqRepoBranchWriter,
|
||||||
|
mustNotBeArchived,
|
||||||
|
repo.ChangeFiles,
|
||||||
|
)
|
||||||
m.Get("/*", repo.GetContents)
|
m.Get("/*", repo.GetContents)
|
||||||
m.Group("/*", func() {
|
m.Group("/*", func() {
|
||||||
m.Post("", bind(api.CreateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.CreateFile)
|
m.Post(
|
||||||
m.Put("", bind(api.UpdateFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.UpdateFile)
|
"",
|
||||||
m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, mustNotBeArchived, repo.DeleteFile)
|
bind(api.CreateFileOptions{}),
|
||||||
|
reqRepoBranchWriter,
|
||||||
|
mustNotBeArchived,
|
||||||
|
repo.CreateFile,
|
||||||
|
)
|
||||||
|
m.Put(
|
||||||
|
"",
|
||||||
|
bind(api.UpdateFileOptions{}),
|
||||||
|
reqRepoBranchWriter,
|
||||||
|
mustNotBeArchived,
|
||||||
|
repo.UpdateFile,
|
||||||
|
)
|
||||||
|
m.Delete(
|
||||||
|
"",
|
||||||
|
bind(api.DeleteFileOptions{}),
|
||||||
|
reqRepoBranchWriter,
|
||||||
|
mustNotBeArchived,
|
||||||
|
repo.DeleteFile,
|
||||||
|
)
|
||||||
}, reqToken())
|
}, reqToken())
|
||||||
}, reqRepoReader(unit.TypeCode))
|
}, reqRepoReader(unit.TypeCode))
|
||||||
m.Get("/signing-key.gpg", misc.SigningKey)
|
m.Get("/signing-key.gpg", misc.SigningKey)
|
||||||
@ -1232,7 +1438,11 @@ func Routes() *web.Route {
|
|||||||
}, reqAnyRepoReader())
|
}, reqAnyRepoReader())
|
||||||
m.Get("/issue_templates", context.ReferencesGitRepo(), repo.GetIssueTemplates)
|
m.Get("/issue_templates", context.ReferencesGitRepo(), repo.GetIssueTemplates)
|
||||||
m.Get("/issue_config", context.ReferencesGitRepo(), repo.GetIssueConfig)
|
m.Get("/issue_config", context.ReferencesGitRepo(), repo.GetIssueConfig)
|
||||||
m.Get("/issue_config/validate", context.ReferencesGitRepo(), repo.ValidateIssueConfig)
|
m.Get(
|
||||||
|
"/issue_config/validate",
|
||||||
|
context.ReferencesGitRepo(),
|
||||||
|
repo.ValidateIssueConfig,
|
||||||
|
)
|
||||||
m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages)
|
m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages)
|
||||||
m.Get("/activities/feeds", repo.ListRepoActivityFeeds)
|
m.Get("/activities/feeds", repo.ListRepoActivityFeeds)
|
||||||
m.Get("/new_pin_allowed", repo.AreNewIssuePinsAllowed)
|
m.Get("/new_pin_allowed", repo.AreNewIssuePinsAllowed)
|
||||||
@ -1290,7 +1500,8 @@ func Routes() *web.Route {
|
|||||||
m.Group("/comments", func() {
|
m.Group("/comments", func() {
|
||||||
m.Combo("").Get(repo.ListIssueComments).
|
m.Combo("").Get(repo.ListIssueComments).
|
||||||
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
|
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
|
||||||
m.Combo("/{id}", reqToken()).Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated).
|
m.Combo("/{id}", reqToken()).
|
||||||
|
Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueCommentDeprecated).
|
||||||
Delete(repo.DeleteIssueCommentDeprecated)
|
Delete(repo.DeleteIssueCommentDeprecated)
|
||||||
})
|
})
|
||||||
m.Get("/timeline", repo.ListIssueCommentsAndTimeline)
|
m.Get("/timeline", repo.ListIssueCommentsAndTimeline)
|
||||||
@ -1308,7 +1519,8 @@ func Routes() *web.Route {
|
|||||||
Delete(repo.ResetIssueTime)
|
Delete(repo.ResetIssueTime)
|
||||||
m.Delete("/{id}", repo.DeleteTime)
|
m.Delete("/{id}", repo.DeleteTime)
|
||||||
}, reqToken())
|
}, reqToken())
|
||||||
m.Combo("/deadline").Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline)
|
m.Combo("/deadline").
|
||||||
|
Post(reqToken(), bind(api.EditDeadlineOption{}), repo.UpdateIssueDeadline)
|
||||||
m.Group("/stopwatch", func() {
|
m.Group("/stopwatch", func() {
|
||||||
m.Post("/start", repo.StartIssueStopwatch)
|
m.Post("/start", repo.StartIssueStopwatch)
|
||||||
m.Post("/stop", repo.StopIssueStopwatch)
|
m.Post("/stop", repo.StopIssueStopwatch)
|
||||||
@ -1363,6 +1575,12 @@ func Routes() *web.Route {
|
|||||||
Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
|
Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
|
||||||
Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone)
|
Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone)
|
||||||
})
|
})
|
||||||
|
m.Group("/projects", func() {
|
||||||
|
m.
|
||||||
|
Combo("").
|
||||||
|
Get(projects.ListRepoProjects).
|
||||||
|
Post(bind(api.NewProjectPayload{}), projects.CreateRepoProject)
|
||||||
|
}, mustEnableIssues)
|
||||||
}, repoAssignment())
|
}, repoAssignment())
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue))
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue))
|
||||||
|
|
||||||
@ -1370,20 +1588,43 @@ func Routes() *web.Route {
|
|||||||
m.Group("/packages/{username}", func() {
|
m.Group("/packages/{username}", func() {
|
||||||
m.Group("/{type}/{name}/{version}", func() {
|
m.Group("/{type}/{name}/{version}", func() {
|
||||||
m.Get("", reqToken(), packages.GetPackage)
|
m.Get("", reqToken(), packages.GetPackage)
|
||||||
m.Delete("", reqToken(), reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage)
|
m.Delete(
|
||||||
|
"",
|
||||||
|
reqToken(),
|
||||||
|
reqPackageAccess(perm.AccessModeWrite),
|
||||||
|
packages.DeletePackage,
|
||||||
|
)
|
||||||
m.Get("/files", reqToken(), packages.ListPackageFiles)
|
m.Get("/files", reqToken(), packages.ListPackageFiles)
|
||||||
})
|
})
|
||||||
m.Get("/", reqToken(), packages.ListPackages)
|
m.Get("/", reqToken(), packages.ListPackages)
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context_service.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead))
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context_service.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead))
|
||||||
|
|
||||||
// Organizations
|
// Organizations
|
||||||
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
|
m.Get(
|
||||||
|
"/user/orgs",
|
||||||
|
reqToken(),
|
||||||
|
tokenRequiresScopes(
|
||||||
|
auth_model.AccessTokenScopeCategoryUser,
|
||||||
|
auth_model.AccessTokenScopeCategoryOrganization,
|
||||||
|
),
|
||||||
|
org.ListMyOrgs,
|
||||||
|
)
|
||||||
m.Group("/users/{username}/orgs", func() {
|
m.Group("/users/{username}/orgs", func() {
|
||||||
m.Get("", reqToken(), org.ListUserOrgs)
|
m.Get("", reqToken(), org.ListUserOrgs)
|
||||||
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
|
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context_service.UserAssignmentAPI())
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context_service.UserAssignmentAPI())
|
||||||
m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create)
|
m.Post(
|
||||||
m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization))
|
"/orgs",
|
||||||
|
tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization),
|
||||||
|
reqToken(),
|
||||||
|
bind(api.CreateOrgOption{}),
|
||||||
|
org.Create,
|
||||||
|
)
|
||||||
|
m.Get(
|
||||||
|
"/orgs",
|
||||||
|
org.GetAll,
|
||||||
|
tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization),
|
||||||
|
)
|
||||||
m.Group("/orgs/{org}", func() {
|
m.Group("/orgs/{org}", func() {
|
||||||
m.Combo("").Get(org.Get).
|
m.Combo("").Get(org.Get).
|
||||||
Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
|
Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
|
||||||
@ -1414,7 +1655,13 @@ func Routes() *web.Route {
|
|||||||
}, reqToken(), reqOrgMembership())
|
}, reqToken(), reqOrgMembership())
|
||||||
m.Group("/labels", func() {
|
m.Group("/labels", func() {
|
||||||
m.Get("", org.ListLabels)
|
m.Get("", org.ListLabels)
|
||||||
m.Post("", reqToken(), reqOrgOwnership(), bind(api.CreateLabelOption{}), org.CreateLabel)
|
m.Post(
|
||||||
|
"",
|
||||||
|
reqToken(),
|
||||||
|
reqOrgOwnership(),
|
||||||
|
bind(api.CreateLabelOption{}),
|
||||||
|
org.CreateLabel,
|
||||||
|
)
|
||||||
m.Combo("/{id}").Get(reqToken(), org.GetLabel).
|
m.Combo("/{id}").Get(reqToken(), org.GetLabel).
|
||||||
Patch(reqToken(), reqOrgOwnership(), bind(api.EditLabelOption{}), org.EditLabel).
|
Patch(reqToken(), reqOrgOwnership(), bind(api.EditLabelOption{}), org.EditLabel).
|
||||||
Delete(reqToken(), reqOrgOwnership(), org.DeleteLabel)
|
Delete(reqToken(), reqOrgOwnership(), org.DeleteLabel)
|
||||||
@ -1431,6 +1678,12 @@ func Routes() *web.Route {
|
|||||||
m.Delete("", org.DeleteAvatar)
|
m.Delete("", org.DeleteAvatar)
|
||||||
}, reqToken(), reqOrgOwnership())
|
}, reqToken(), reqOrgOwnership())
|
||||||
m.Get("/activities/feeds", org.ListOrgActivityFeeds)
|
m.Get("/activities/feeds", org.ListOrgActivityFeeds)
|
||||||
|
|
||||||
|
m.Group("/projects", func() {
|
||||||
|
m.Combo("").
|
||||||
|
Get(projects.ListOrgProjects).
|
||||||
|
Post(bind(api.NewProjectPayload{}), projects.CreateOrgProject)
|
||||||
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue), reqToken(), reqOrgMembership())
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true))
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true))
|
||||||
m.Group("/teams/{teamid}", func() {
|
m.Group("/teams/{teamid}", func() {
|
||||||
m.Combo("").Get(reqToken(), org.GetTeam).
|
m.Combo("").Get(reqToken(), org.GetTeam).
|
||||||
@ -1493,6 +1746,13 @@ func Routes() *web.Route {
|
|||||||
})
|
})
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryAdmin), reqToken(), reqSiteAdmin())
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryAdmin), reqToken(), reqSiteAdmin())
|
||||||
|
|
||||||
|
m.Group("/projects", func() {
|
||||||
|
m.
|
||||||
|
Combo("/{id}").
|
||||||
|
Get(projects.GetProject).
|
||||||
|
Patch(bind(api.UpdateProjectPayload{}), projects.UpdateProject).
|
||||||
|
Delete(projects.DeleteProject)
|
||||||
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue), reqToken())
|
||||||
m.Group("/topics", func() {
|
m.Group("/topics", func() {
|
||||||
m.Get("/search", repo.TopicSearch)
|
m.Get("/search", repo.TopicSearch)
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
||||||
|
407
routers/api/v1/projects/project.go
Normal file
407
routers/api/v1/projects/project.go
Normal file
@ -0,0 +1,407 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package projects
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
project_model "code.gitea.io/gitea/models/project"
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
"code.gitea.io/gitea/services/convert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func innerCreateProject(
|
||||||
|
ctx *context.APIContext,
|
||||||
|
project_type project_model.Type,
|
||||||
|
) {
|
||||||
|
form := web.GetForm(ctx).(*api.NewProjectPayload)
|
||||||
|
project := &project_model.Project{
|
||||||
|
RepoID: 0,
|
||||||
|
OwnerID: ctx.Doer.ID,
|
||||||
|
Title: form.Title,
|
||||||
|
Description: form.Description,
|
||||||
|
CreatorID: ctx.Doer.ID,
|
||||||
|
BoardType: project_model.BoardType(form.BoardType),
|
||||||
|
Type: project_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.ContextUser != nil {
|
||||||
|
project.OwnerID = ctx.ContextUser.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
if project_type == project_model.TypeRepository {
|
||||||
|
project.RepoID = ctx.Repo.Repository.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := project_model.NewProject(ctx, project); err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "NewProject", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
project, err := project_model.GetProjectByID(ctx, project.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "NewProject", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusCreated, convert.ToAPIProject(ctx, project))
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateUserProject(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /user/projects project projectCreateUserProject
|
||||||
|
// ---
|
||||||
|
// summary: Create a user project
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: project
|
||||||
|
// in: body
|
||||||
|
// required: true
|
||||||
|
// schema: { "$ref": "#/definitions/NewProjectPayload" }
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// "$ref": "#/responses/Project"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
innerCreateProject(ctx, project_model.TypeIndividual)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateOrgProject(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /orgs/{org}/projects project projectCreateOrgProject
|
||||||
|
// ---
|
||||||
|
// summary: Create a organization project
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: owner of repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: project
|
||||||
|
// in: body
|
||||||
|
// required: true
|
||||||
|
// schema: { "$ref": "#/definitions/NewProjectPayload" }
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// "$ref": "#/responses/Project"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
innerCreateProject(ctx, project_model.TypeOrganization)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateRepoProject(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /repos/{owner}/{repo}/projects project projectCreateRepositoryProject
|
||||||
|
// ---
|
||||||
|
// summary: Create a repository project
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: project
|
||||||
|
// in: body
|
||||||
|
// required: true
|
||||||
|
// schema: { "$ref": "#/definitions/NewProjectPayload" }
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// "$ref": "#/responses/Project"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
innerCreateProject(ctx, project_model.TypeRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProject(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /projects/{id} project projectGetProject
|
||||||
|
// ---
|
||||||
|
// summary: Get project
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: id
|
||||||
|
// in: path
|
||||||
|
// description: id of the project
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/Project"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
||||||
|
if err != nil {
|
||||||
|
if project_model.IsErrProjectNotExist(err) {
|
||||||
|
ctx.NotFound()
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProjectByID", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, convert.ToAPIProject(ctx, project))
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateProject(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PATCH /projects/{id} project projectUpdateProject
|
||||||
|
// ---
|
||||||
|
// summary: Update project
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: id
|
||||||
|
// in: path
|
||||||
|
// description: id of the project
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: project
|
||||||
|
// in: body
|
||||||
|
// required: true
|
||||||
|
// schema: { "$ref": "#/definitions/UpdateProjectPayload" }
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/Project"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
form := web.GetForm(ctx).(*api.UpdateProjectPayload)
|
||||||
|
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64("id"))
|
||||||
|
if err != nil {
|
||||||
|
if project_model.IsErrProjectNotExist(err) {
|
||||||
|
ctx.NotFound()
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "UpdateProject", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if project.Title != form.Title {
|
||||||
|
project.Title = form.Title
|
||||||
|
}
|
||||||
|
if project.Description != form.Description {
|
||||||
|
project.Description = form.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
err = project_model.UpdateProject(ctx, project)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "UpdateProject", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.JSON(http.StatusOK, convert.ToAPIProject(ctx, project))
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteProject(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /projects/{id} project projectDeleteProject
|
||||||
|
// ---
|
||||||
|
// summary: Delete project
|
||||||
|
// parameters:
|
||||||
|
// - name: id
|
||||||
|
// in: path
|
||||||
|
// description: id of the project
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// "description": "Deleted the project"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
if err := project_model.DeleteProjectByID(ctx, ctx.ParamsInt64(":id")); err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "DeleteProjectByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListUserProjects(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /user/projects project projectListUserProjects
|
||||||
|
// ---
|
||||||
|
// summary: List repository projects
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: closed
|
||||||
|
// in: query
|
||||||
|
// description: include closed issues or not
|
||||||
|
// type: boolean
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/ProjectList"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
projects, count, err := project_model.FindProjects(ctx, project_model.SearchOptions{
|
||||||
|
OwnerID: ctx.Doer.ID,
|
||||||
|
Page: ctx.FormInt("page"),
|
||||||
|
IsClosed: ctx.FormOptionalBool("closed"),
|
||||||
|
Type: project_model.TypeIndividual,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "Projects", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetLinkHeader(int(count), setting.UI.IssuePagingNum)
|
||||||
|
ctx.SetTotalCountHeader(count)
|
||||||
|
|
||||||
|
apiProjects, err := convert.ToAPIProjectList(ctx, projects)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "Projects", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, apiProjects)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListOrgProjects(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /orgs/{org}/projects project projectListOrgProjects
|
||||||
|
// ---
|
||||||
|
// summary: List repository projects
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: closed
|
||||||
|
// in: query
|
||||||
|
// description: include closed issues or not
|
||||||
|
// type: boolean
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/ProjectList"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
projects, count, err := project_model.FindProjects(ctx, project_model.SearchOptions{
|
||||||
|
OwnerID: ctx.Org.Organization.AsUser().ID,
|
||||||
|
Page: ctx.FormInt("page"),
|
||||||
|
IsClosed: ctx.FormOptionalBool("closed"),
|
||||||
|
Type: project_model.TypeOrganization,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "Projects", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetLinkHeader(int(count), setting.UI.IssuePagingNum)
|
||||||
|
ctx.SetTotalCountHeader(count)
|
||||||
|
|
||||||
|
apiProjects, err := convert.ToAPIProjectList(ctx, projects)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "Projects", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, apiProjects)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListRepoProjects(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/projects project projectListRepositoryProjects
|
||||||
|
// ---
|
||||||
|
// summary: List repository projects
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: closed
|
||||||
|
// in: query
|
||||||
|
// description: include closed issues or not
|
||||||
|
// type: boolean
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/ProjectList"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
projects, count, err := project_model.FindProjects(ctx, project_model.SearchOptions{
|
||||||
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
|
Page: ctx.FormInt("page"),
|
||||||
|
IsClosed: ctx.FormOptionalBool("closed"),
|
||||||
|
Type: project_model.TypeRepository,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "Projects", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetLinkHeader(int(count), setting.UI.IssuePagingNum)
|
||||||
|
ctx.SetTotalCountHeader(count)
|
||||||
|
|
||||||
|
apiProjects, err := convert.ToAPIProjectList(ctx, projects)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "Projects", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, apiProjects)
|
||||||
|
}
|
22
routers/api/v1/swagger/project.go
Normal file
22
routers/api/v1/swagger/project.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package swagger
|
||||||
|
|
||||||
|
import (
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Project
|
||||||
|
// swagger:response Project
|
||||||
|
type swaggerResponseProject struct {
|
||||||
|
// in:body
|
||||||
|
Body api.Project `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProjectList
|
||||||
|
// swagger:response ProjectList
|
||||||
|
type swaggerResponseProjectList struct {
|
||||||
|
// in:body
|
||||||
|
Body []api.Project `json:"body"`
|
||||||
|
}
|
66
services/convert/project.go
Normal file
66
services/convert/project.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
project_model "code.gitea.io/gitea/models/project"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToAPIProject(ctx context.Context, project *project_model.Project) *api.Project {
|
||||||
|
|
||||||
|
apiProject := &api.Project{
|
||||||
|
Title: project.Title,
|
||||||
|
Description: project.Description,
|
||||||
|
BoardType: uint8(project.BoardType),
|
||||||
|
IsClosed: project.IsClosed,
|
||||||
|
Created: project.CreatedUnix.AsTime(),
|
||||||
|
Updated: project.UpdatedUnix.AsTime(),
|
||||||
|
Closed: project.ClosedDateUnix.AsTime(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to laod the repo
|
||||||
|
project.LoadRepo(ctx)
|
||||||
|
if project.Repo != nil {
|
||||||
|
apiProject.Repo = &api.RepositoryMeta{
|
||||||
|
ID: project.RepoID,
|
||||||
|
Name: project.Repo.Name,
|
||||||
|
Owner: project.Repo.OwnerName,
|
||||||
|
FullName: project.Repo.FullName(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
project.LoadCreator(ctx)
|
||||||
|
if project.Creator != nil {
|
||||||
|
apiProject.Creator = &api.User{
|
||||||
|
ID: project.Creator.ID,
|
||||||
|
UserName: project.Creator.Name,
|
||||||
|
FullName: project.Creator.FullName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
project.LoadOwner(ctx)
|
||||||
|
if project.Owner != nil {
|
||||||
|
apiProject.Owner = &api.User{
|
||||||
|
ID: project.Owner.ID,
|
||||||
|
UserName: project.Owner.Name,
|
||||||
|
FullName: project.Owner.FullName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiProject
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToAPIProjectList(
|
||||||
|
ctx context.Context,
|
||||||
|
projects []*project_model.Project,
|
||||||
|
) ([]*api.Project, error) {
|
||||||
|
result := make([]*api.Project, len(projects))
|
||||||
|
for i := range projects {
|
||||||
|
result[i] = ToAPIProject(ctx, projects[i])
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
338
templates/swagger/v1_json.tmpl
generated
338
templates/swagger/v1_json.tmpl
generated
@ -2311,6 +2311,75 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/orgs/{org}/projects": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"project"
|
||||||
|
],
|
||||||
|
"summary": "List repository projects",
|
||||||
|
"operationId": "projectListOrgProjects",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repository",
|
||||||
|
"name": "org",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "include closed issues or not",
|
||||||
|
"name": "closed",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"project"
|
||||||
|
],
|
||||||
|
"summary": "Create a organization project",
|
||||||
|
"operationId": "projectCreateOrgProject",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of repo",
|
||||||
|
"name": "org",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "project",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/NewProjectPayload"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/orgs/{org}/public_members": {
|
"/orgs/{org}/public_members": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@ -2912,6 +2981,73 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/projects/{id}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"project"
|
||||||
|
],
|
||||||
|
"summary": "Get project",
|
||||||
|
"operationId": "projectGetProject",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "id of the project",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"tags": [
|
||||||
|
"project"
|
||||||
|
],
|
||||||
|
"summary": "Delete project",
|
||||||
|
"operationId": "projectDeleteProject",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "id of the project",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"project"
|
||||||
|
],
|
||||||
|
"summary": "Update project",
|
||||||
|
"operationId": "projectUpdateProject",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "id of the project",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "project",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/UpdateProjectPayload"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/issues/search": {
|
"/repos/issues/search": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@ -10140,6 +10276,89 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/projects": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"project"
|
||||||
|
],
|
||||||
|
"summary": "List repository projects",
|
||||||
|
"operationId": "projectListRepositoryProjects",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repository",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "include closed issues or not",
|
||||||
|
"name": "closed",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"project"
|
||||||
|
],
|
||||||
|
"summary": "Create a repository project",
|
||||||
|
"operationId": "projectCreateRepositoryProject",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "project",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/NewProjectPayload"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/pulls": {
|
"/repos/{owner}/{repo}/pulls": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@ -15481,6 +15700,61 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/user/projects": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"project"
|
||||||
|
],
|
||||||
|
"summary": "List repository projects",
|
||||||
|
"operationId": "projectListUserProjects",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "include closed issues or not",
|
||||||
|
"name": "closed",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page number of results to return (1-based)",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "page size of results",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"project"
|
||||||
|
],
|
||||||
|
"summary": "Create a user project",
|
||||||
|
"operationId": "projectCreateUserProject",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "project",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/NewProjectPayload"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/user/repos": {
|
"/user/repos": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@ -21310,6 +21584,55 @@
|
|||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"Project": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"board_type": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "uint8",
|
||||||
|
"x-go-name": "BoardType"
|
||||||
|
},
|
||||||
|
"closed_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "Closed"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "Created"
|
||||||
|
},
|
||||||
|
"creator": {
|
||||||
|
"$ref": "#/definitions/User"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Description"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "ID"
|
||||||
|
},
|
||||||
|
"is_closed": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "IsClosed"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"$ref": "#/definitions/RepositoryMeta"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Title"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "Updated"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"PublicKey": {
|
"PublicKey": {
|
||||||
"description": "PublicKey publickey is a user key to push code to repository",
|
"description": "PublicKey publickey is a user key to push code to repository",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -23628,6 +23951,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Project": {
|
||||||
|
"description": "Project",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/Project"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ProjectList": {
|
||||||
|
"description": "ProjectList",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/Project"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"PublicKey": {
|
"PublicKey": {
|
||||||
"description": "PublicKey",
|
"description": "PublicKey",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
228
tests/integration/api_project_test.go
Normal file
228
tests/integration/api_project_test.go
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
project_model "code.gitea.io/gitea/models/project"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/tests"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAPICreateUserProject(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
const title, description, board_type = "project_name", "project_description", uint8(project_model.BoardTypeBasicKanban)
|
||||||
|
|
||||||
|
token := getUserToken(
|
||||||
|
t,
|
||||||
|
"user2",
|
||||||
|
auth_model.AccessTokenScopeWriteIssue,
|
||||||
|
auth_model.AccessTokenScopeWriteUser,
|
||||||
|
)
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/user/projects?token=%s", token)
|
||||||
|
|
||||||
|
req := NewRequestWithJSON(t, "POST", urlStr, &api.NewProjectPayload{
|
||||||
|
Title: title,
|
||||||
|
Description: description,
|
||||||
|
BoardType: board_type,
|
||||||
|
})
|
||||||
|
resp := MakeRequest(t, req, http.StatusCreated)
|
||||||
|
var apiProject api.Project
|
||||||
|
DecodeJSON(t, resp, &apiProject)
|
||||||
|
assert.Equal(t, title, apiProject.Title)
|
||||||
|
assert.Equal(t, description, apiProject.Description)
|
||||||
|
assert.Equal(t, board_type, apiProject.BoardType)
|
||||||
|
assert.Equal(t, "user2", apiProject.Creator.UserName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPICreateOrgProject(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
const title, description, board_type = "project_name", "project_description", uint8(project_model.BoardTypeBasicKanban)
|
||||||
|
|
||||||
|
orgName := "org17"
|
||||||
|
token := getUserToken(
|
||||||
|
t,
|
||||||
|
"user2",
|
||||||
|
auth_model.AccessTokenScopeWriteIssue,
|
||||||
|
auth_model.AccessTokenScopeWriteOrganization,
|
||||||
|
)
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/orgs/%s/projects?token=%s", orgName, token)
|
||||||
|
|
||||||
|
req := NewRequestWithJSON(t, "POST", urlStr, &api.NewProjectPayload{
|
||||||
|
Title: title,
|
||||||
|
Description: description,
|
||||||
|
BoardType: board_type,
|
||||||
|
})
|
||||||
|
resp := MakeRequest(t, req, http.StatusCreated)
|
||||||
|
var apiProject api.Project
|
||||||
|
DecodeJSON(t, resp, &apiProject)
|
||||||
|
assert.Equal(t, title, apiProject.Title)
|
||||||
|
assert.Equal(t, description, apiProject.Description)
|
||||||
|
assert.Equal(t, board_type, apiProject.BoardType)
|
||||||
|
assert.Equal(t, "org17", apiProject.Creator.UserName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPICreateRepoProject(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
const title, description, board_type = "project_name", "project_description", uint8(project_model.BoardTypeBasicKanban)
|
||||||
|
|
||||||
|
ownerName := "user2"
|
||||||
|
repoName := "repo1"
|
||||||
|
token := getUserToken(
|
||||||
|
t,
|
||||||
|
ownerName,
|
||||||
|
auth_model.AccessTokenScopeWriteIssue,
|
||||||
|
auth_model.AccessTokenScopeWriteOrganization,
|
||||||
|
)
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/projects?token=%s", ownerName, repoName, token)
|
||||||
|
|
||||||
|
req := NewRequestWithJSON(t, "POST", urlStr, &api.NewProjectPayload{
|
||||||
|
Title: title,
|
||||||
|
Description: description,
|
||||||
|
BoardType: board_type,
|
||||||
|
})
|
||||||
|
resp := MakeRequest(t, req, http.StatusCreated)
|
||||||
|
var apiProject api.Project
|
||||||
|
DecodeJSON(t, resp, &apiProject)
|
||||||
|
assert.Equal(t, title, apiProject.Title)
|
||||||
|
assert.Equal(t, description, apiProject.Description)
|
||||||
|
assert.Equal(t, board_type, apiProject.BoardType)
|
||||||
|
assert.Equal(t, "repo1", apiProject.Repo.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIListUserProjects(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
token := getUserToken(
|
||||||
|
t,
|
||||||
|
"user2",
|
||||||
|
auth_model.AccessTokenScopeReadUser,
|
||||||
|
auth_model.AccessTokenScopeReadIssue,
|
||||||
|
)
|
||||||
|
link, _ := url.Parse(fmt.Sprintf("/api/v1/user/projects"))
|
||||||
|
|
||||||
|
link.RawQuery = url.Values{"token": {token}}.Encode()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", link.String())
|
||||||
|
var apiProjects []*api.Project
|
||||||
|
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiProjects)
|
||||||
|
assert.Len(t, apiProjects, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIListOrgProjects(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
orgName := "org17"
|
||||||
|
token := getUserToken(
|
||||||
|
t,
|
||||||
|
"user2",
|
||||||
|
auth_model.AccessTokenScopeReadOrganization,
|
||||||
|
auth_model.AccessTokenScopeReadIssue,
|
||||||
|
)
|
||||||
|
link, _ := url.Parse(fmt.Sprintf("/api/v1/orgs/%s/projects", orgName))
|
||||||
|
|
||||||
|
link.RawQuery = url.Values{"token": {token}}.Encode()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", link.String())
|
||||||
|
var apiProjects []*api.Project
|
||||||
|
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiProjects)
|
||||||
|
assert.Len(t, apiProjects, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIListRepoProjects(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
ownerName := "user2"
|
||||||
|
repoName := "repo1"
|
||||||
|
token := getUserToken(
|
||||||
|
t,
|
||||||
|
"user2",
|
||||||
|
auth_model.AccessTokenScopeReadRepository,
|
||||||
|
auth_model.AccessTokenScopeReadIssue,
|
||||||
|
)
|
||||||
|
link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/projects", ownerName, repoName))
|
||||||
|
|
||||||
|
link.RawQuery = url.Values{"token": {token}}.Encode()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", link.String())
|
||||||
|
var apiProjects []*api.Project
|
||||||
|
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiProjects)
|
||||||
|
assert.Len(t, apiProjects, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIGetProject(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
token := getUserToken(
|
||||||
|
t,
|
||||||
|
"user2",
|
||||||
|
auth_model.AccessTokenScopeReadUser,
|
||||||
|
auth_model.AccessTokenScopeReadIssue,
|
||||||
|
)
|
||||||
|
link, _ := url.Parse(fmt.Sprintf("/api/v1/projects/%d", 1))
|
||||||
|
|
||||||
|
link.RawQuery = url.Values{"token": {token}}.Encode()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", link.String())
|
||||||
|
var apiProject *api.Project
|
||||||
|
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiProject)
|
||||||
|
assert.Equal(t, "First project", apiProject.Title)
|
||||||
|
assert.Equal(t, "repo1", apiProject.Repo.Name)
|
||||||
|
assert.Equal(t, "user2", apiProject.Creator.UserName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIUpdateProject(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
token := getUserToken(
|
||||||
|
t,
|
||||||
|
"user2",
|
||||||
|
auth_model.AccessTokenScopeWriteUser,
|
||||||
|
auth_model.AccessTokenScopeWriteIssue,
|
||||||
|
)
|
||||||
|
link, _ := url.Parse(fmt.Sprintf("/api/v1/projects/%d", 1))
|
||||||
|
|
||||||
|
link.RawQuery = url.Values{"token": {token}}.Encode()
|
||||||
|
|
||||||
|
req := NewRequestWithJSON(t, "PATCH", link.String(), &api.UpdateProjectPayload{
|
||||||
|
Title: "First project updated",
|
||||||
|
})
|
||||||
|
|
||||||
|
var apiProject *api.Project
|
||||||
|
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &apiProject)
|
||||||
|
assert.Equal(t, "First project updated", apiProject.Title)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIDeleteProject(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
token := getUserToken(
|
||||||
|
t,
|
||||||
|
"user2",
|
||||||
|
auth_model.AccessTokenScopeWriteUser,
|
||||||
|
auth_model.AccessTokenScopeWriteIssue,
|
||||||
|
)
|
||||||
|
link, _ := url.Parse(fmt.Sprintf("/api/v1/projects/%d", 1))
|
||||||
|
|
||||||
|
link.RawQuery = url.Values{"token": {token}}.Encode()
|
||||||
|
|
||||||
|
req := NewRequest(t, "DELETE", link.String())
|
||||||
|
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
unittest.AssertNotExistsBean(t, &project_model.Project{ID: 1})
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user