Skip to content

feat: api endpoints for project boards #28209

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4c0ec22
test: add 2 fixtures for project
dineshsalunke Nov 18, 2023
6092b81
refactor: add structs for project
dineshsalunke Nov 18, 2023
b6f9d05
feat: api for projects
dineshsalunke Nov 18, 2023
b6d45c6
Merge branch 'main' into feat/api-projects
dineshsalunke Nov 20, 2023
922cd13
Merge remote-tracking branch 'upstream/main' into feat/api-projects
dineshsalunke Nov 20, 2023
0b8d198
Merge branch 'feat/api-projects' of https://github.com/dineshsalunke/…
dineshsalunke Nov 20, 2023
44f20b3
refactor: add structs for board
dineshsalunke Nov 25, 2023
d8447fa
refactor: add the swagger related structs
dineshsalunke Nov 25, 2023
35ba105
feat: api endpoints for the boards
dineshsalunke Nov 25, 2023
26c9282
Merge remote-tracking branch 'upstream/main' into feat/api-project-bo…
dineshsalunke Nov 25, 2023
ebcafb5
chore: remove unwanted formatting on line
dineshsalunke Jan 16, 2024
7b19a66
Merge remote-tracking branch 'upstream/main' into feat/api-project-bo…
dineshsalunke Jan 17, 2024
937c027
refactor: update for db find method usage
dineshsalunke Jan 17, 2024
f5688db
refactor: fix swagger doc issues
dineshsalunke Jan 17, 2024
19c5e2f
Merge remote-tracking branch 'upstream/main' into feat/api-project-bo…
dineshsalunke Jan 22, 2024
313d160
revert formatting changes
denyskon Jan 21, 2024
c4f832c
more reverts
denyskon Jan 21, 2024
159ec84
more formatting fixes
denyskon Jan 21, 2024
a466ddf
fix project sort test
denyskon Jan 21, 2024
5f4e945
refactor: use the add token auth method for token in tests
dineshsalunke Jan 17, 2024
eeae271
test: use the add token auth method for token
dineshsalunke Jan 17, 2024
5ad5dc6
refactor: use the correct variable naming
dineshsalunke Jan 22, 2024
383be30
chore: formatting fixs
dineshsalunke Jan 22, 2024
33c5722
test: use the add token auth method on request
dineshsalunke Jan 22, 2024
f34c052
fix: ignore errors when loading data for converting project to response
dineshsalunke Jan 17, 2024
2ebe4f0
refactor: use the correct variable naming
dineshsalunke Jan 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions models/fixtures/project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,29 @@
type: 2
created_unix: 1688973000
updated_unix: 1688973000

# user projects
-
id: 5
title: project on user2
owner_id: 2
repo_id: 0
is_closed: false
creator_id: 2
board_type: 1
type: 1
created_unix: 1688973000
updated_unix: 1688973000

# org projects
-
id: 6
title: project on org17
owner_id: 17
repo_id: 0
is_closed: false
creator_id: 17
board_type: 1
type: 3
created_unix: 1688973000
updated_unix: 1688973000
18 changes: 18 additions & 0 deletions models/project/board.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,24 @@ func (p *Project) GetBoards(ctx context.Context) (BoardList, error) {
return append([]*Board{defaultB}, boards...), nil
}

func (p *Project) GetBoardsAndCount(ctx context.Context) (BoardList, int64, error) {
boards := make([]*Board, 0, 5)
count, err := db.GetEngine(ctx).
Where("project_id=? AND `default`=?", p.ID, false).
OrderBy("Sorting").
FindAndCount(&boards)
if err != nil {
return nil, 0, err
}

defaultB, err := p.getDefaultBoard(ctx)
if err != nil {
return nil, 0, err
}

return append([]*Board{defaultB}, boards...), count, nil
}

// getDefaultBoard return default board and create a dummy if none exist
func (p *Project) getDefaultBoard(ctx context.Context) (*Board, error) {
var board Board
Expand Down
23 changes: 21 additions & 2 deletions models/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ type Project struct {
RepoID int64 `xorm:"INDEX"`
Repo *repo_model.Repository `xorm:"-"`
CreatorID int64 `xorm:"NOT NULL"`
Creator *user_model.User `xorm:"-"`
IsClosed bool `xorm:"INDEX"`
BoardType BoardType
CardType CardType
Expand All @@ -107,6 +108,8 @@ type Project struct {
ClosedDateUnix timeutil.TimeStamp
}

type ProjectList []*Project

func (p *Project) LoadOwner(ctx context.Context) (err error) {
if p.Owner != nil {
return nil
Expand All @@ -115,6 +118,14 @@ func (p *Project) LoadOwner(ctx context.Context) (err error) {
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) {
if p.RepoID == 0 || p.Repo != nil {
return nil
Expand Down Expand Up @@ -343,7 +354,11 @@ func updateRepositoryProjectCount(ctx context.Context, repoID int64) error {
}

// 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)
if err != nil {
return err
Expand Down Expand Up @@ -384,7 +399,11 @@ func ChangeProjectStatus(ctx context.Context, p *Project, isClosed bool) error {
func changeProjectStatus(ctx context.Context, p *Project, isClosed bool) error {
p.IsClosed = isClosed
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 {
return err
}
Expand Down
12 changes: 6 additions & 6 deletions models/project/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,19 +92,19 @@ func TestProjectsSort(t *testing.T) {
}{
{
sortType: "default",
wants: []int64{1, 3, 2, 4},
wants: []int64{1, 3, 2, 4, 5, 6},
},
{
sortType: "oldest",
wants: []int64{4, 2, 3, 1},
wants: []int64{4, 5, 6, 2, 3, 1},
},
{
sortType: "recentupdate",
wants: []int64{1, 3, 2, 4},
wants: []int64{1, 3, 2, 4, 5, 6},
},
{
sortType: "leastupdate",
wants: []int64{4, 2, 3, 1},
wants: []int64{4, 5, 6, 2, 3, 1},
},
}

Expand All @@ -113,8 +113,8 @@ func TestProjectsSort(t *testing.T) {
OrderBy: GetSearchOrderByBySortType(tt.sortType),
})
assert.NoError(t, err)
assert.EqualValues(t, int64(4), count)
if assert.Len(t, projects, 4) {
assert.EqualValues(t, int64(6), count)
if assert.Len(t, projects, 6) {
for i := range projects {
assert.EqualValues(t, tt.wants[i], projects[i].ID)
}
Expand Down
42 changes: 42 additions & 0 deletions modules/structs/project.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package structs

import "time"

// swagger:model
type NewProjectPayload struct {
// required:true
Title string `json:"title" binding:"Required"`
// required:true
BoardType uint8 `json:"board_type"`
// required:true
CardType uint8 `json:"card_type"`
Description string `json:"description"`
}

// swagger:model
type UpdateProjectPayload struct {
// required:true
Title string `json:"title" binding:"Required"`
Description string `json:"description"`
}

type Project struct {
ID int64 `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
BoardType uint8 `json:"board_type"`
IsClosed bool `json:"is_closed"`
// 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"`

Repo *RepositoryMeta `json:"repository"`
Creator *User `json:"creator"`
Owner *User `json:"owner"`
}
35 changes: 35 additions & 0 deletions modules/structs/project_board.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package structs

import "time"

type ProjectBoard struct {
ID int64 `json:"id"`
Title string `json:"title"`
Default bool `json:"default"`
Color string `json:"color"`
Sorting int8 `json:"sorting"`
Project *Project `json:"project"`
Creator *User `json:"creator"`
// swagger:strfmt date-time
Created time.Time `json:"created_at"`
// swagger:strfmt date-time
Updated time.Time `json:"updated_at"`
}

// swagger:model
type NewProjectBoardPayload struct {
// required:true
Title string `json:"title"`
Default bool `json:"default"`
Color string `json:"color"`
Sorting int8 `json:"sorting`
}

// swagger:model
type UpdateProjectBoardPayload struct {
Title string `json:"title"`
Color string `json:"color"`
}
32 changes: 32 additions & 0 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ import (
"code.gitea.io/gitea/routers/api/v1/notify"
"code.gitea.io/gitea/routers/api/v1/org"
"code.gitea.io/gitea/routers/api/v1/packages"
"code.gitea.io/gitea/routers/api/v1/projects"
"code.gitea.io/gitea/routers/api/v1/repo"
"code.gitea.io/gitea/routers/api/v1/settings"
"code.gitea.io/gitea/routers/api/v1/user"
Expand Down Expand Up @@ -1029,6 +1030,10 @@ func Routes() *web.Route {
m.Post("", bind(api.UpdateUserAvatarOption{}), user.UpdateAvatar)
m.Delete("", user.DeleteAvatar)
}, reqToken())

m.Combo("/projects", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue)).
Get(projects.ListUserProjects).
Post(bind(api.NewProjectPayload{}), projects.CreateUserProject)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())

// Repositories (requires repo scope, org scope)
Expand Down Expand Up @@ -1402,6 +1407,10 @@ func Routes() *web.Route {
Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone).
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())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue))

Expand Down Expand Up @@ -1476,6 +1485,10 @@ func Routes() *web.Route {
m.Delete("", org.DeleteAvatar)
}, reqToken(), reqOrgOwnership())
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))
m.Group("/teams/{teamid}", func() {
m.Combo("").Get(reqToken(), org.GetTeam).
Expand Down Expand Up @@ -1541,6 +1554,25 @@ func Routes() *web.Route {
})
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryAdmin), reqToken(), reqSiteAdmin())

m.Group("/projects", func() {
m.Group("/{projectId}", func() {
m.Combo("").
Get(projects.GetProject).
Patch(bind(api.UpdateProjectPayload{}), projects.UpdateProject).
Delete(projects.DeleteProject)
m.Combo("/boards").
Get(projects.ListProjectBoards).
Post(bind(api.NewProjectBoardPayload{}), projects.CreateProjectBoard)
})

m.Group("/boards", func() {
m.Combo("/{boardId}").
Get(projects.GetProjectBoard).
Patch(bind(api.UpdateProjectBoardPayload{}), projects.UpdateProjectBoard).
Delete(projects.DeleteProjectBoard)
})

}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue), reqToken())
m.Group("/topics", func() {
m.Get("/search", repo.TopicSearch)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
Expand Down
Loading