diff --git a/models/auth/access_token_scope.go b/models/auth/access_token_scope.go index 3eae19b2a53..84bf2db43b1 100644 --- a/models/auth/access_token_scope.go +++ b/models/auth/access_token_scope.go @@ -24,6 +24,7 @@ const ( AccessTokenScopeCategoryIssue AccessTokenScopeCategoryRepository AccessTokenScopeCategoryUser + AccessTokenScopeCategoryProject ) // AllAccessTokenScopeCategories contains all access token scope categories @@ -37,6 +38,7 @@ var AllAccessTokenScopeCategories = []AccessTokenScopeCategory{ AccessTokenScopeCategoryIssue, AccessTokenScopeCategoryRepository, AccessTokenScopeCategoryUser, + AccessTokenScopeCategoryProject, } // AccessTokenScopeLevel represents the access levels without a given scope category @@ -82,6 +84,9 @@ const ( AccessTokenScopeReadUser AccessTokenScope = "read:user" AccessTokenScopeWriteUser AccessTokenScope = "write:user" + + AccessTokenScopeReadProject AccessTokenScope = "read:project" + AccessTokenScopeWriteProject AccessTokenScope = "write:project" ) // accessTokenScopeBitmap represents a bitmap of access token scopes. @@ -124,6 +129,9 @@ const ( accessTokenScopeReadUserBits accessTokenScopeBitmap = 1 << iota accessTokenScopeWriteUserBits accessTokenScopeBitmap = 1< 64 scopes, // refactoring the whole implementation in this file (and only this file) is needed. @@ -142,6 +150,7 @@ var allAccessTokenScopes = []AccessTokenScope{ AccessTokenScopeWriteIssue, AccessTokenScopeReadIssue, AccessTokenScopeWriteRepository, AccessTokenScopeReadRepository, AccessTokenScopeWriteUser, AccessTokenScopeReadUser, + AccessTokenScopeWriteProject, AccessTokenScopeReadProject, } // allAccessTokenScopeBits contains all access token scopes. @@ -166,6 +175,8 @@ var allAccessTokenScopeBits = map[AccessTokenScope]accessTokenScopeBitmap{ AccessTokenScopeWriteRepository: accessTokenScopeWriteRepositoryBits, AccessTokenScopeReadUser: accessTokenScopeReadUserBits, AccessTokenScopeWriteUser: accessTokenScopeWriteUserBits, + AccessTokenScopeReadProject: accessTokenScopeReadProjectBits, + AccessTokenScopeWriteProject: accessTokenScopeWriteProjectBits, } // readAccessTokenScopes maps a scope category to the read permission scope @@ -180,6 +191,7 @@ var accessTokenScopes = map[AccessTokenScopeLevel]map[AccessTokenScopeCategory]A AccessTokenScopeCategoryIssue: AccessTokenScopeReadIssue, AccessTokenScopeCategoryRepository: AccessTokenScopeReadRepository, AccessTokenScopeCategoryUser: AccessTokenScopeReadUser, + AccessTokenScopeCategoryProject: AccessTokenScopeReadProject, }, Write: { AccessTokenScopeCategoryActivityPub: AccessTokenScopeWriteActivityPub, @@ -191,6 +203,7 @@ var accessTokenScopes = map[AccessTokenScopeLevel]map[AccessTokenScopeCategory]A AccessTokenScopeCategoryIssue: AccessTokenScopeWriteIssue, AccessTokenScopeCategoryRepository: AccessTokenScopeWriteRepository, AccessTokenScopeCategoryUser: AccessTokenScopeWriteUser, + AccessTokenScopeCategoryProject: AccessTokenScopeWriteProject, }, } diff --git a/models/auth/access_token_scope_test.go b/models/auth/access_token_scope_test.go index b93c25528fe..6c804a295d3 100644 --- a/models/auth/access_token_scope_test.go +++ b/models/auth/access_token_scope_test.go @@ -17,7 +17,7 @@ type scopeTestNormalize struct { } func TestAccessTokenScope_Normalize(t *testing.T) { - assert.Equal(t, []string{"activitypub", "admin", "issue", "misc", "notification", "organization", "package", "repository", "user"}, GetAccessTokenCategories()) + assert.Equal(t, []string{"activitypub", "admin", "issue", "misc", "notification", "organization", "package", "project", "repository", "user"}, GetAccessTokenCategories()) tests := []scopeTestNormalize{ {"", "", nil}, {"write:misc,write:notification,read:package,write:notification,public-only", "public-only,write:misc,write:notification,read:package", nil}, diff --git a/models/project/column.go b/models/project/column.go index 9b9d874997e..319eb3df747 100644 --- a/models/project/column.go +++ b/models/project/column.go @@ -34,6 +34,28 @@ const ( CardTypeImagesAndText ) +func (p CardType) ToString() string { + switch p { + case CardTypeImagesAndText: + return "ImagesAndText" + case CardTypeTextOnly: + fallthrough + default: + return "TextOnly" + } +} + +func ToCardType(s string) CardType { + switch s { + case "ImagesAndText": + return CardTypeImagesAndText + case "TextOnly": + fallthrough + default: + return CardTypeTextOnly + } +} + // ColumnColorPattern is a regexp witch can validate ColumnColor var ColumnColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$") diff --git a/models/project/template.go b/models/project/template.go index 06d5d2af148..9846a334639 100644 --- a/models/project/template.go +++ b/models/project/template.go @@ -25,6 +25,31 @@ const ( TemplateTypeBugTriage ) +func (p TemplateType) ToString() string { + switch p { + case TemplateTypeBasicKanban: + return "BasicKanban" + case TemplateTypeBugTriage: + return "BugTriage" + case TemplateTypeNone: + fallthrough + default: + return "" + } +} + +// ToTemplateType converts a string to a TemplateType +func ToTemplateType(s string) TemplateType { + switch s { + case "BasicKanban": + return TemplateTypeBasicKanban + case "BugTriage": + return TemplateTypeBugTriage + default: + return TemplateTypeNone + } +} + // GetTemplateConfigs retrieves the template configs of configurations project columns could have func GetTemplateConfigs() []TemplateConfig { return []TemplateConfig{ diff --git a/modules/structs/project.go b/modules/structs/project.go index 26f23d6fd67..193fa3743a6 100644 --- a/modules/structs/project.go +++ b/modules/structs/project.go @@ -5,37 +5,53 @@ package structs import "time" +// NewProjectOption options when creating a new project // swagger:model -type NewProjectPayload struct { +type NewProjectOption struct { // required:true - Title string `json:"title" binding:"Required"` + // Keep compatibility with Github API to use "name" instead of "title" + Name string `json:"name" binding:"Required"` // required:true - BoardType uint8 `json:"board_type"` + // enum: , BasicKanban, BugTriage + // Note: this is the same as TemplateType in models/project/template.go + TemplateType string `json:"template_type"` // required:true - CardType uint8 `json:"card_type"` - Description string `json:"description"` + // enum: TextOnly, ImagesAndText + CardType string `json:"card_type"` + // Keep compatibility with Github API to use "body" instead of "description" + Body string `json:"body"` } +// UpdateProjectOption options when updating a project // swagger:model -type UpdateProjectPayload struct { +type UpdateProjectOption struct { // required:true - Title string `json:"title" binding:"Required"` - Description string `json:"description"` + // Keep compatibility with Github API to use "name" instead of "title" + Name string `json:"name" binding:"Required"` + // Keep compatibility with Github API to use "body" instead of "description" + Body string `json:"body"` } +// Project represents a project // swagger:model type Project struct { - ID int64 `json:"id"` - Title string `json:"title"` - Description string `json:"description"` - TemplateType uint8 `json:"board_type"` - IsClosed bool `json:"is_closed"` + ID int64 `json:"id"` + // Keep compatibility with Github API to use "name" instead of "title" + Name string `json:"name"` + // Keep compatibility with Github API to use "body" instead of "description" + Body string `json:"body"` + // required:true + // enum: , BasicKanban, BugTriage + // Note: this is the same as TemplateType in models/project/template.go + TemplateType string `json:"template_type"` + // enum: open, closed + State string `json:"state"` // swagger:strfmt date-time Created time.Time `json:"created_at"` // swagger:strfmt date-time Updated time.Time `json:"updated_at"` // swagger:strfmt date-time - Closed time.Time `json:"closed_at"` + Closed *time.Time `json:"closed_at"` Repo *RepositoryMeta `json:"repository"` Creator *User `json:"creator"` diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 6082a54c10d..c935789de8d 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1042,6 +1042,13 @@ func Routes() *web.Router { m.Get("/subscriptions", user.GetWatchedRepos) }, context.UserAssignmentAPI(), checkTokenPublicOnly()) + + m.Group("/{username}", func() { + m.Group("/projects", func() { + m.Get("", projects.ListUserProjects) + m.Post("", bind(api.NewProjectOption{}), projects.CreateUserProject) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryProject)) + }, context.UserAssignmentAPI()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken()) // Users (requires user scope) @@ -1162,11 +1169,6 @@ func Routes() *web.Router { m.Delete("", user.UnblockUser) }, context.UserAssignmentAPI(), checkTokenPublicOnly()) }) - - m.Group("/projects", func() { - m.Get("", projects.ListUserProjects) - m.Post("", bind(api.NewProjectPayload{}), projects.CreateUserProject) - }) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken()) // Repositories (requires repo scope, org scope) @@ -1475,8 +1477,8 @@ func Routes() *web.Router { m.Methods("HEAD,GET", "/{ball_type:tarball|zipball|bundle}/*", reqRepoReader(unit.TypeCode), repo.DownloadArchive) m.Group("/projects", func() { - m.Post("", bind(api.NewProjectPayload{}), projects.CreateRepoProject) - }) + m.Post("", bind(api.NewProjectOption{}), projects.CreateRepoProject) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryProject)) }, repoAssignment(), checkTokenPublicOnly()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) @@ -1699,9 +1701,9 @@ func Routes() *web.Router { }, reqToken(), reqOrgOwnership()) m.Group("/projects", func() { - m.Post("", bind(api.NewProjectPayload{}), projects.CreateOrgProject) + m.Post("", bind(api.NewProjectOption{}), projects.CreateOrgProject) m.Get("", projects.ListOrgProjects) - }) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryProject)) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true), checkTokenPublicOnly()) m.Group("/teams/{teamid}", func() { m.Combo("").Get(reqToken(), org.GetTeam). @@ -1724,6 +1726,13 @@ func Routes() *web.Router { m.Get("/activities/feeds", org.ListTeamActivityFeeds) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership(), checkTokenPublicOnly()) + // Projects + m.Group("/projects", func() { + m.Get("{project_id}", projects.GetProject) + m.Patch("{project_id}", bind(api.UpdateProjectOption{}), projects.UpdateProject) + m.Delete("{project_id}", projects.DeleteProject) + }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryProject), reqToken()) + m.Group("/admin", func() { m.Group("/cron", func() { m.Get("", admin.ListCronTasks) diff --git a/routers/api/v1/projects/project.go b/routers/api/v1/projects/project.go index 22264038e74..a8643006b65 100644 --- a/routers/api/v1/projects/project.go +++ b/routers/api/v1/projects/project.go @@ -8,28 +8,28 @@ import ( "code.gitea.io/gitea/models/db" project_model "code.gitea.io/gitea/models/project" - "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/convert" ) func innerCreateProject(ctx *context.APIContext, projectType project_model.Type) { - form := web.GetForm(ctx).(*api.NewProjectPayload) + form := web.GetForm(ctx).(*api.NewProjectOption) project := &project_model.Project{ - RepoID: 0, - OwnerID: ctx.Doer.ID, - Title: form.Title, - Description: form.Description, + Title: form.Name, + Description: form.Body, CreatorID: ctx.Doer.ID, - TemplateType: project_model.TemplateType(form.BoardType), + TemplateType: project_model.ToTemplateType(form.TemplateType), Type: projectType, } - if ctx.ContextUser != nil { - project.OwnerID = ctx.ContextUser.ID + if ctx.ContextUser == nil { + ctx.APIError(http.StatusForbidden, "Not authenticated") + return } + project.OwnerID = ctx.ContextUser.ID if projectType == project_model.TypeRepository { project.RepoID = ctx.Repo.Repository.ID @@ -67,7 +67,7 @@ func CreateUserProject(ctx *context.APIContext) { // - name: project // in: body // required: true - // schema: { "$ref": "#/definitions/NewProjectPayload" } + // schema: { "$ref": "#/definitions/NewProjectOption" } // responses: // "201": // "$ref": "#/responses/Project" @@ -95,7 +95,7 @@ func CreateOrgProject(ctx *context.APIContext) { // - name: project // in: body // required: true - // schema: { "$ref": "#/definitions/NewProjectPayload" } + // schema: { "$ref": "#/definitions/NewProjectOption" } // responses: // "201": // "$ref": "#/responses/Project" @@ -128,7 +128,7 @@ func CreateRepoProject(ctx *context.APIContext) { // - name: project // in: body // required: true - // schema: { "$ref": "#/definitions/NewProjectPayload" } + // schema: { "$ref": "#/definitions/NewProjectOption" } // responses: // "201": // "$ref": "#/responses/Project" @@ -140,7 +140,7 @@ func CreateRepoProject(ctx *context.APIContext) { } func GetProject(ctx *context.APIContext) { - // swagger:operation GET /projects/{id} project projectGetProject + // swagger:operation GET /projects/{project_id} project projectGetProject // --- // summary: Get project // produces: @@ -158,7 +158,7 @@ func GetProject(ctx *context.APIContext) { // "$ref": "#/responses/forbidden" // "404": // "$ref": "#/responses/notFound" - project, err := project_model.GetProjectByID(ctx, ctx.FormInt64(":id")) + project, err := project_model.GetProjectByID(ctx, ctx.FormInt64("project_id")) if err != nil { if project_model.IsErrProjectNotExist(err) { ctx.APIError(http.StatusNotFound, err) @@ -177,7 +177,7 @@ func GetProject(ctx *context.APIContext) { } func UpdateProject(ctx *context.APIContext) { - // swagger:operation PATCH /projects/{id} project projectUpdateProject + // swagger:operation PATCH /projects/{project_id} project projectUpdateProject // --- // summary: Update project // produces: @@ -193,7 +193,7 @@ func UpdateProject(ctx *context.APIContext) { // - name: project // in: body // required: true - // schema: { "$ref": "#/definitions/UpdateProjectPayload" } + // schema: { "$ref": "#/definitions/UpdateProjectOption" } // responses: // "200": // "$ref": "#/responses/Project" @@ -201,8 +201,8 @@ func UpdateProject(ctx *context.APIContext) { // "$ref": "#/responses/forbidden" // "404": // "$ref": "#/responses/notFound" - form := web.GetForm(ctx).(*api.UpdateProjectPayload) - project, err := project_model.GetProjectByID(ctx, ctx.FormInt64("id")) + form := web.GetForm(ctx).(*api.UpdateProjectOption) + project, err := project_model.GetProjectByID(ctx, ctx.FormInt64("project_id")) if err != nil { if project_model.IsErrProjectNotExist(err) { ctx.APIError(http.StatusNotFound, err) @@ -211,11 +211,11 @@ func UpdateProject(ctx *context.APIContext) { } return } - if project.Title != form.Title { - project.Title = form.Title + if project.Title != form.Name { + project.Title = form.Name } - if project.Description != form.Description { - project.Description = form.Description + if project.Description != form.Body { + project.Description = form.Body } err = project_model.UpdateProject(ctx, project) @@ -232,7 +232,7 @@ func UpdateProject(ctx *context.APIContext) { } func DeleteProject(ctx *context.APIContext) { - // swagger:operation DELETE /projects/{id} project projectDeleteProject + // swagger:operation DELETE /projects/{project_id} project projectDeleteProject // --- // summary: Delete project // parameters: @@ -249,7 +249,7 @@ func DeleteProject(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - if err := project_model.DeleteProjectByID(ctx, ctx.FormInt64(":id")); err != nil { + if err := project_model.DeleteProjectByID(ctx, ctx.FormInt64("project_id")); err != nil { ctx.APIErrorInternal(err) return } @@ -258,7 +258,7 @@ func DeleteProject(ctx *context.APIContext) { } func ListUserProjects(ctx *context.APIContext) { - // swagger:operation GET /user/projects project projectListUserProjects + // swagger:operation GET /users/{user}/projects project projectListUserProjects // --- // summary: List user projects // produces: @@ -283,18 +283,20 @@ func ListUserProjects(ctx *context.APIContext) { // "$ref": "#/responses/forbidden" // "404": // "$ref": "#/responses/notFound" + + listOptions := utils.GetListOptions(ctx) projects, count, err := db.FindAndCount[project_model.Project](ctx, project_model.SearchOptions{ Type: project_model.TypeIndividual, IsClosed: ctx.FormOptionalBool("closed"), OwnerID: ctx.Doer.ID, - ListOptions: db.ListOptions{Page: ctx.FormInt("page")}, + ListOptions: listOptions, }) if err != nil { ctx.APIErrorInternal(err) return } - ctx.SetLinkHeader(int(count), setting.UI.IssuePagingNum) + ctx.SetLinkHeader(int(count), listOptions.PageSize) ctx.SetTotalCountHeader(count) apiProjects, err := convert.ToAPIProjectList(ctx, projects) @@ -337,9 +339,11 @@ func ListOrgProjects(ctx *context.APIContext) { // "$ref": "#/responses/forbidden" // "404": // "$ref": "#/responses/notFound" + + listOptions := utils.GetListOptions(ctx) projects, count, err := db.FindAndCount[project_model.Project](ctx, project_model.SearchOptions{ OwnerID: ctx.Org.Organization.AsUser().ID, - ListOptions: db.ListOptions{Page: ctx.FormInt("page")}, + ListOptions: listOptions, IsClosed: ctx.FormOptionalBool("closed"), Type: project_model.TypeOrganization, }) @@ -348,7 +352,7 @@ func ListOrgProjects(ctx *context.APIContext) { return } - ctx.SetLinkHeader(int(count), setting.UI.IssuePagingNum) + ctx.SetLinkHeader(int(count), listOptions.PageSize) ctx.SetTotalCountHeader(count) apiProjects, err := convert.ToAPIProjectList(ctx, projects) @@ -397,19 +401,19 @@ func ListRepoProjects(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - page := ctx.FormInt("page") + listOptions := utils.GetListOptions(ctx) projects, count, err := db.FindAndCount[project_model.Project](ctx, project_model.SearchOptions{ RepoID: ctx.Repo.Repository.ID, IsClosed: ctx.FormOptionalBool("closed"), Type: project_model.TypeRepository, - ListOptions: db.ListOptions{Page: page}, + ListOptions: listOptions, }) if err != nil { ctx.APIErrorInternal(err) return } - ctx.SetLinkHeader(int(count), page) + ctx.SetLinkHeader(int(count), listOptions.PageSize) ctx.SetTotalCountHeader(count) apiProjects, err := convert.ToAPIProjectList(ctx, projects) diff --git a/routers/api/v1/swagger/options.go b/routers/api/v1/swagger/options.go index cf2dc951153..19af6d82595 100644 --- a/routers/api/v1/swagger/options.go +++ b/routers/api/v1/swagger/options.go @@ -221,9 +221,11 @@ type swaggerParameterBodies struct { UpdateVariableOption api.UpdateVariableOption // in:body - NewProjectPayload api.NewProjectPayload + LockIssueOption api.LockIssueOption // in:body - UpdateProjectPayload api.UpdateProjectPayload - LockIssueOption api.LockIssueOption + NewProjectOption api.NewProjectOption + + // in:body + UpdateProjectOption api.UpdateProjectOption } diff --git a/services/convert/project.go b/services/convert/project.go index 1cb00696abd..94215a62e0d 100644 --- a/services/convert/project.go +++ b/services/convert/project.go @@ -8,17 +8,21 @@ import ( project_model "code.gitea.io/gitea/models/project" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" ) func ToAPIProject(ctx context.Context, project *project_model.Project) (*api.Project, error) { apiProject := &api.Project{ - Title: project.Title, - Description: project.Description, - TemplateType: uint8(project.TemplateType), - IsClosed: project.IsClosed, + Name: project.Title, + Body: project.Description, + TemplateType: project.TemplateType.ToString(), + State: util.Iif(project.IsClosed, "closed", "open"), Created: project.CreatedUnix.AsTime(), Updated: project.UpdatedUnix.AsTime(), - Closed: project.ClosedDateUnix.AsTime(), + } + if !project.ClosedDateUnix.IsZero() { + tm := project.ClosedDateUnix.AsTime() + apiProject.Closed = &tm } if err := project.LoadRepo(ctx); err != nil { diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index a530f2db0cc..e4667378704 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -3385,7 +3385,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/NewProjectPayload" + "$ref": "#/definitions/NewProjectOption" } } ], @@ -4225,7 +4225,7 @@ } } }, - "/projects/{id}": { + "/projects/{project_id}": { "get": { "produces": [ "application/json" @@ -4308,7 +4308,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/UpdateProjectPayload" + "$ref": "#/definitions/UpdateProjectOption" } } ], @@ -13687,7 +13687,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/NewProjectPayload" + "$ref": "#/definitions/NewProjectOption" } } ], @@ -19955,47 +19955,6 @@ } }, "/user/projects": { - "get": { - "produces": [ - "application/json" - ], - "tags": [ - "project" - ], - "summary": "List user projects", - "operationId": "projectListUserProjects", - "parameters": [ - { - "type": "boolean", - "description": "include closed projects 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" - } - ], - "responses": { - "200": { - "$ref": "#/responses/ProjectList" - }, - "403": { - "$ref": "#/responses/forbidden" - }, - "404": { - "$ref": "#/responses/notFound" - } - } - }, "post": { "consumes": [ "application/json" @@ -20014,7 +19973,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/NewProjectPayload" + "$ref": "#/definitions/NewProjectOption" } } ], @@ -21121,6 +21080,49 @@ } } }, + "/users/{user}/projects": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "project" + ], + "summary": "List user projects", + "operationId": "projectListUserProjects", + "parameters": [ + { + "type": "boolean", + "description": "include closed projects 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" + } + ], + "responses": { + "200": { + "$ref": "#/responses/ProjectList" + }, + "403": { + "$ref": "#/responses/forbidden" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/version": { "get": { "produces": [ @@ -26399,31 +26401,40 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, - "NewProjectPayload": { + "NewProjectOption": { + "description": "NewProjectOption options when creating a new project", "type": "object", "required": [ - "title", - "board_type", + "name", + "template_type", "card_type" ], "properties": { - "board_type": { - "type": "integer", - "format": "uint8", - "x-go-name": "BoardType" + "body": { + "description": "Keep compatibility with Github API to use \"body\" instead of \"description\"", + "type": "string", + "x-go-name": "Body" }, "card_type": { - "type": "integer", - "format": "uint8", + "type": "string", + "enum": [ + "TextOnly", + " ImagesAndText" + ], "x-go-name": "CardType" }, - "description": { + "name": { "type": "string", - "x-go-name": "Description" + "x-go-name": "Name" }, - "title": { + "template_type": { "type": "string", - "x-go-name": "Title" + "enum": [ + "", + " BasicKanban", + " BugTriage" + ], + "x-go-name": "TemplateType" } }, "x-go-package": "code.gitea.io/gitea/modules/structs" @@ -27001,12 +27012,16 @@ "x-go-package": "code.gitea.io/gitea/modules/structs" }, "Project": { + "description": "Project represents a project", "type": "object", + "required": [ + "template_type" + ], "properties": { - "board_type": { - "type": "integer", - "format": "uint8", - "x-go-name": "TemplateType" + "body": { + "description": "Keep compatibility with Github API to use \"body\" instead of \"description\"", + "type": "string", + "x-go-name": "Body" }, "closed_at": { "type": "string", @@ -27021,18 +27036,15 @@ "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" + "name": { + "description": "Keep compatibility with Github API to use \"name\" instead of \"title\"", + "type": "string", + "x-go-name": "Name" }, "owner": { "$ref": "#/definitions/User" @@ -27040,9 +27052,22 @@ "repository": { "$ref": "#/definitions/RepositoryMeta" }, - "title": { + "state": { "type": "string", - "x-go-name": "Title" + "enum": [ + "open", + " closed" + ], + "x-go-name": "State" + }, + "template_type": { + "type": "string", + "enum": [ + "", + " BasicKanban", + " BugTriage" + ], + "x-go-name": "TemplateType" }, "updated_at": { "type": "string", @@ -28606,19 +28631,21 @@ }, "x-go-package": "code.gitea.io/gitea/modules/structs" }, - "UpdateProjectPayload": { + "UpdateProjectOption": { + "description": "UpdateProjectOption options when updating a project", "type": "object", "required": [ - "title" + "name" ], "properties": { - "description": { + "body": { + "description": "Keep compatibility with Github API to use \"body\" instead of \"description\"", "type": "string", - "x-go-name": "Description" + "x-go-name": "Body" }, - "title": { + "name": { "type": "string", - "x-go-name": "Title" + "x-go-name": "Name" } }, "x-go-package": "code.gitea.io/gitea/modules/structs" @@ -30166,10 +30193,7 @@ "parameterBodies": { "description": "parameterBodies", "schema": { - "$ref": "#/definitions/LockIssueOption" - }, - "headers": { - "LockIssueOption": {} + "$ref": "#/definitions/UpdateProjectOption" } }, "redirect": { diff --git a/tests/integration/api_project_test.go b/tests/integration/api_project_test.go index 740610050a4..66c3baefb20 100644 --- a/tests/integration/api_project_test.go +++ b/tests/integration/api_project_test.go @@ -20,67 +20,70 @@ import ( func TestAPICreateUserProject(t *testing.T) { defer tests.PrepareTestEnv(t)() - const title, description, boardType = "project_name", "project_description", uint8(project_model.TemplateTypeBasicKanban) + const title, description = "project_name", "project_description" + templateType := project_model.TemplateTypeBasicKanban.ToString() - token := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteIssue, auth_model.AccessTokenScopeWriteUser) + token := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteProject, auth_model.AccessTokenScopeWriteUser) - req := NewRequestWithJSON(t, "POST", "/api/v1/user/projects", &api.NewProjectPayload{ - Title: title, - Description: description, - BoardType: boardType, + req := NewRequestWithJSON(t, "POST", "/api/v1/users/user2/projects", &api.NewProjectOption{ + Name: title, + Body: description, + TemplateType: templateType, }).AddTokenAuth(token) 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, boardType, apiProject.TemplateType) + assert.Equal(t, title, apiProject.Name) + assert.Equal(t, description, apiProject.Body) + assert.Equal(t, templateType, apiProject.TemplateType) assert.Equal(t, "user2", apiProject.Creator.UserName) } func TestAPICreateOrgProject(t *testing.T) { defer tests.PrepareTestEnv(t)() - const title, description, boardType = "project_name", "project_description", uint8(project_model.TemplateTypeBasicKanban) + const title, description = "project_name", "project_description" + templateType := project_model.TemplateTypeBasicKanban.ToString() orgName := "org17" token := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteIssue, auth_model.AccessTokenScopeWriteOrganization) urlStr := fmt.Sprintf("/api/v1/orgs/%s/projects", orgName) - req := NewRequestWithJSON(t, "POST", urlStr, &api.NewProjectPayload{ - Title: title, - Description: description, - BoardType: boardType, + req := NewRequestWithJSON(t, "POST", urlStr, &api.NewProjectOption{ + Name: title, + Body: description, + TemplateType: templateType, }).AddTokenAuth(token) 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, boardType, apiProject.TemplateType) + assert.Equal(t, title, apiProject.Name) + assert.Equal(t, description, apiProject.Body) + assert.Equal(t, templateType, apiProject.TemplateType) assert.Equal(t, "user2", apiProject.Creator.UserName) assert.Equal(t, "org17", apiProject.Owner.UserName) } func TestAPICreateRepoProject(t *testing.T) { defer tests.PrepareTestEnv(t)() - const title, description, boardType = "project_name", "project_description", uint8(project_model.TemplateTypeBasicKanban) + const title, description = "project_name", "project_description" + templateType := project_model.TemplateTypeBasicKanban.ToString() ownerName := "user2" repoName := "repo1" token := getUserToken(t, ownerName, auth_model.AccessTokenScopeWriteIssue, auth_model.AccessTokenScopeWriteOrganization) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/projects", ownerName, repoName) - req := NewRequestWithJSON(t, "POST", urlStr, &api.NewProjectPayload{ - Title: title, - Description: description, - BoardType: boardType, + req := NewRequestWithJSON(t, "POST", urlStr, &api.NewProjectOption{ + Name: title, + Body: description, + TemplateType: templateType, }).AddTokenAuth(token) 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, boardType, apiProject.TemplateType) + assert.Equal(t, title, apiProject.Name) + assert.Equal(t, description, apiProject.Body) + assert.Equal(t, templateType, apiProject.TemplateType) assert.Equal(t, "repo1", apiProject.Repo.Name) } @@ -88,7 +91,7 @@ func TestAPIListUserProjects(t *testing.T) { defer tests.PrepareTestEnv(t)() token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadIssue) - link, _ := url.Parse("/api/v1/user/projects") + link, _ := url.Parse("/api/v1/users/user2/projects") req := NewRequest(t, "GET", link.String()).AddTokenAuth(token) var apiProjects []*api.Project @@ -131,37 +134,37 @@ func TestAPIListRepoProjects(t *testing.T) { 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)) + token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadProject) + link, _ := url.Parse(fmt.Sprintf("/api/v1/projects/%d", 4)) req := NewRequest(t, "GET", link.String()).AddTokenAuth(token) var apiProject *api.Project resp := MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiProject) - assert.Equal(t, "First project", apiProject.Title) + assert.Equal(t, "First project", apiProject.Name) 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)) + token := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteProject) + link, _ := url.Parse(fmt.Sprintf("/api/v1/projects/%d", 4)) - req := NewRequestWithJSON(t, "PATCH", link.String(), &api.UpdateProjectPayload{Title: "First project updated"}).AddTokenAuth(token) + req := NewRequestWithJSON(t, "PATCH", link.String(), &api.UpdateProjectOption{Name: "First project updated"}).AddTokenAuth(token) var apiProject *api.Project resp := MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiProject) - assert.Equal(t, "First project updated", apiProject.Title) + assert.Equal(t, "First project updated", apiProject.Name) } 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)) + token := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteProject) + link, _ := url.Parse(fmt.Sprintf("/api/v1/projects/%d", 4)) req := NewRequest(t, "DELETE", link.String()).AddTokenAuth(token)