diff --git a/models/fixtures/project.yml b/models/fixtures/project.yml index 44d87bce04674..b8c6ff6c42408 100644 --- a/models/fixtures/project.yml +++ b/models/fixtures/project.yml @@ -42,7 +42,7 @@ is_closed: false creator_id: 2 board_type: 1 - type: 2 + type: 1 created_unix: 1688973000 updated_unix: 1688973000 @@ -54,7 +54,7 @@ is_closed: false creator_id: 2 board_type: 1 - type: 2 + type: 1 created_unix: 1688973000 updated_unix: 1688973000 @@ -66,6 +66,18 @@ is_closed: false creator_id: 2 board_type: 1 - type: 2 + type: 1 created_unix: 1688973000 updated_unix: 1688973000 + +- + id: 7 + title: project on org17 + owner_id: 17 + repo_id: 0 + is_closed: false + creator_id: 2 + board_type: 1 + type: 3 + created_unix: 1754044020 + updated_unix: 1754044020 diff --git a/models/project/project.go b/models/project/project.go index ab2e1c66f1d0b..6225722a090f2 100644 --- a/models/project/project.go +++ b/models/project/project.go @@ -42,6 +42,20 @@ const ( TypeOrganization ) +// ToString returns the string representation of the project type +func (t Type) ToString() string { + switch t { + case TypeIndividual: + return "individual" + case TypeRepository: + return "repository" + case TypeOrganization: + return "organization" + default: + return "unknown" + } +} + // ErrProjectNotExist represents a "ProjectNotExist" kind of error. type ErrProjectNotExist struct { ID int64 diff --git a/modules/structs/project.go b/modules/structs/project.go index 193fa3743a674..df1edc374f4ab 100644 --- a/modules/structs/project.go +++ b/modules/structs/project.go @@ -56,4 +56,5 @@ type Project struct { Repo *RepositoryMeta `json:"repository"` Creator *User `json:"creator"` Owner *User `json:"owner"` + Type string `json:"type"` } diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index c935789de8dbc..f9f4d51d167bd 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1477,6 +1477,7 @@ func Routes() *web.Router { m.Methods("HEAD,GET", "/{ball_type:tarball|zipball|bundle}/*", reqRepoReader(unit.TypeCode), repo.DownloadArchive) m.Group("/projects", func() { + m.Get("", projects.ListUserProjects) m.Post("", bind(api.NewProjectOption{}), projects.CreateRepoProject) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryProject)) }, repoAssignment(), checkTokenPublicOnly()) @@ -1728,9 +1729,9 @@ func Routes() *web.Router { // 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) + 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() { diff --git a/routers/api/v1/projects/project.go b/routers/api/v1/projects/project.go index a8643006b6543..75ed76ae11f36 100644 --- a/routers/api/v1/projects/project.go +++ b/routers/api/v1/projects/project.go @@ -146,7 +146,7 @@ func GetProject(ctx *context.APIContext) { // produces: // - application/json // parameters: - // - name: id + // - name: project_id // in: path // description: id of the project // type: string @@ -158,7 +158,7 @@ func GetProject(ctx *context.APIContext) { // "$ref": "#/responses/forbidden" // "404": // "$ref": "#/responses/notFound" - project, err := project_model.GetProjectByID(ctx, ctx.FormInt64("project_id")) + project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("project_id")) if err != nil { if project_model.IsErrProjectNotExist(err) { ctx.APIError(http.StatusNotFound, err) @@ -185,7 +185,7 @@ func UpdateProject(ctx *context.APIContext) { // consumes: // - application/json // parameters: - // - name: id + // - name: project_id // in: path // description: id of the project // type: string @@ -202,7 +202,7 @@ func UpdateProject(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" form := web.GetForm(ctx).(*api.UpdateProjectOption) - project, err := project_model.GetProjectByID(ctx, ctx.FormInt64("project_id")) + project, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("project_id")) if err != nil { if project_model.IsErrProjectNotExist(err) { ctx.APIError(http.StatusNotFound, err) @@ -236,7 +236,7 @@ func DeleteProject(ctx *context.APIContext) { // --- // summary: Delete project // parameters: - // - name: id + // - name: project_id // in: path // description: id of the project // type: string @@ -249,7 +249,7 @@ func DeleteProject(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - if err := project_model.DeleteProjectByID(ctx, ctx.FormInt64("project_id")); err != nil { + if err := project_model.DeleteProjectByID(ctx, ctx.PathParamInt64("project_id")); err != nil { ctx.APIErrorInternal(err) return } @@ -264,6 +264,11 @@ func ListUserProjects(ctx *context.APIContext) { // produces: // - application/json // parameters: + // - name: user + // in: path + // description: username of user + // type: string + // required: true // - name: closed // in: query // description: include closed projects or not diff --git a/services/convert/project.go b/services/convert/project.go index 94215a62e0d22..5d0350524c487 100644 --- a/services/convert/project.go +++ b/services/convert/project.go @@ -13,12 +13,14 @@ import ( func ToAPIProject(ctx context.Context, project *project_model.Project) (*api.Project, error) { apiProject := &api.Project{ + ID: project.ID, 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(), + Type: project.Type.ToString(), } if !project.ClosedDateUnix.IsZero() { tm := project.ClosedDateUnix.AsTime() diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index e46673787046a..5b2f085f4f4ac 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -4239,7 +4239,7 @@ { "type": "string", "description": "id of the project", - "name": "id", + "name": "project_id", "in": "path", "required": true } @@ -4266,7 +4266,7 @@ { "type": "string", "description": "id of the project", - "name": "id", + "name": "project_id", "in": "path", "required": true } @@ -4299,7 +4299,7 @@ { "type": "string", "description": "id of the project", - "name": "id", + "name": "project_id", "in": "path", "required": true }, @@ -21091,6 +21091,13 @@ "summary": "List user projects", "operationId": "projectListUserProjects", "parameters": [ + { + "type": "string", + "description": "username of user", + "name": "user", + "in": "path", + "required": true + }, { "type": "boolean", "description": "include closed projects or not", @@ -27069,6 +27076,10 @@ ], "x-go-name": "TemplateType" }, + "type": { + "type": "string", + "x-go-name": "Type" + }, "updated_at": { "type": "string", "format": "date-time", diff --git a/tests/integration/api_project_test.go b/tests/integration/api_project_test.go index 01392cfc97439..7d446a1c5fa47 100644 --- a/tests/integration/api_project_test.go +++ b/tests/integration/api_project_test.go @@ -45,7 +45,7 @@ func TestAPICreateOrgProject(t *testing.T) { templateType := project_model.TemplateTypeBasicKanban.ToString() orgName := "org17" - token := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteIssue, auth_model.AccessTokenScopeWriteOrganization) + token := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteIssue, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteProject) urlStr := fmt.Sprintf("/api/v1/orgs/%s/projects", orgName) req := NewRequestWithJSON(t, "POST", urlStr, &api.NewProjectOption{ @@ -70,7 +70,7 @@ func TestAPICreateRepoProject(t *testing.T) { ownerName := "user2" repoName := "repo1" - token := getUserToken(t, ownerName, auth_model.AccessTokenScopeWriteIssue, auth_model.AccessTokenScopeWriteOrganization) + token := getUserToken(t, ownerName, auth_model.AccessTokenScopeWriteIssue, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteProject) urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/projects", ownerName, repoName) req := NewRequestWithJSON(t, "POST", urlStr, &api.NewProjectOption{ @@ -90,7 +90,7 @@ func TestAPICreateRepoProject(t *testing.T) { func TestAPIListUserProjects(t *testing.T) { defer tests.PrepareTestEnv(t)() - token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadIssue) + token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadProject) link, _ := url.Parse("/api/v1/users/user2/projects") req := NewRequest(t, "GET", link.String()).AddTokenAuth(token) @@ -98,7 +98,7 @@ func TestAPIListUserProjects(t *testing.T) { resp := MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiProjects) - assert.Len(t, apiProjects, 1) + assert.Len(t, apiProjects, 3) } func TestAPIListOrgProjects(t *testing.T) { @@ -121,7 +121,7 @@ func TestAPIListRepoProjects(t *testing.T) { ownerName := "user2" repoName := "repo1" - token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopeReadIssue) + token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadProject) link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/projects", ownerName, repoName)) req := NewRequest(t, "GET", link.String()).AddTokenAuth(token) @@ -129,28 +129,35 @@ func TestAPIListRepoProjects(t *testing.T) { resp := MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiProjects) - assert.Len(t, apiProjects, 1) + assert.Len(t, apiProjects, 3) } func TestAPIGetProject(t *testing.T) { defer tests.PrepareTestEnv(t)() - token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadProject) - link, _ := url.Parse(fmt.Sprintf("/api/v1/projects/%d", 4)) + + project_id := 4 + user := "user2" + token := getUserToken(t, user, auth_model.AccessTokenScopeReadProject) + link, _ := url.Parse(fmt.Sprintf("/api/v1/projects/%d", project_id)) 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.Name) - assert.Equal(t, "repo1", apiProject.Repo.Name) - assert.Equal(t, "user2", apiProject.Creator.UserName) + assert.Equal(t, "project on user2", apiProject.Name) + assert.Equal(t, "individual", apiProject.Type) + assert.Equal(t, user, apiProject.Creator.UserName) + assert.EqualValues(t, project_id, apiProject.ID) } func TestAPIUpdateProject(t *testing.T) { defer tests.PrepareTestEnv(t)() - token := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteProject) - link, _ := url.Parse(fmt.Sprintf("/api/v1/projects/%d", 4)) + + project_id := 4 + user := "user2" + token := getUserToken(t, user, auth_model.AccessTokenScopeWriteProject) + link, _ := url.Parse(fmt.Sprintf("/api/v1/projects/%d", project_id)) req := NewRequestWithJSON(t, "PATCH", link.String(), &api.UpdateProjectOption{Name: "First project updated"}).AddTokenAuth(token) @@ -163,11 +170,14 @@ func TestAPIUpdateProject(t *testing.T) { func TestAPIDeleteProject(t *testing.T) { defer tests.PrepareTestEnv(t)() - token := getUserToken(t, "user2", auth_model.AccessTokenScopeWriteProject) - link, _ := url.Parse(fmt.Sprintf("/api/v1/projects/%d", 4)) + + project_id := 4 + user := "user2" + token := getUserToken(t, user, auth_model.AccessTokenScopeWriteProject) + link, _ := url.Parse(fmt.Sprintf("/api/v1/projects/%d", project_id)) req := NewRequest(t, "DELETE", link.String()).AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) - unittest.AssertNotExistsBean(t, &project_model.Project{ID: 1}) + unittest.AssertNotExistsBean(t, &project_model.Project{ID: int64(project_id)}) }