Skip to content

Commit d41dc95

Browse files
committed
refactor: refactor endpoints to comply with github api
introduce new logic to handle project and column assignment, refactor everything and removed unnecessary code, create swagger docs
1 parent 1b47d91 commit d41dc95

File tree

9 files changed

+2951
-2791
lines changed

9 files changed

+2951
-2791
lines changed

routers/api/v1/api.go

Lines changed: 177 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ import (
7676
"code.gitea.io/gitea/models/organization"
7777
"code.gitea.io/gitea/models/perm"
7878
access_model "code.gitea.io/gitea/models/perm/access"
79+
project_model "code.gitea.io/gitea/models/project"
7980
repo_model "code.gitea.io/gitea/models/repo"
8081
"code.gitea.io/gitea/models/unit"
8182
user_model "code.gitea.io/gitea/models/user"
@@ -89,9 +90,9 @@ import (
8990
"code.gitea.io/gitea/routers/api/v1/notify"
9091
"code.gitea.io/gitea/routers/api/v1/org"
9192
"code.gitea.io/gitea/routers/api/v1/packages"
93+
"code.gitea.io/gitea/routers/api/v1/project"
9294
"code.gitea.io/gitea/routers/api/v1/repo"
9395
"code.gitea.io/gitea/routers/api/v1/settings"
94-
project_shared "code.gitea.io/gitea/routers/api/v1/shared"
9596
"code.gitea.io/gitea/routers/api/v1/user"
9697
"code.gitea.io/gitea/routers/common"
9798
"code.gitea.io/gitea/services/actions"
@@ -135,6 +136,114 @@ func sudo() func(ctx *context.APIContext) {
135136
}
136137
}
137138

139+
func projectIDAssignmentAPI() func(ctx *context.APIContext) {
140+
return func(ctx *context.APIContext) {
141+
if ctx.PathParam(":project_id") == "" {
142+
return
143+
}
144+
145+
projectAssignment(ctx, ctx.PathParamInt64(":project_id"))
146+
}
147+
}
148+
149+
func projectAssignment(ctx *context.APIContext, projectID int64) {
150+
var (
151+
owner *user_model.User
152+
err error
153+
)
154+
155+
project, err := project_model.GetProjectByID(ctx, projectID)
156+
if err != nil {
157+
ctx.Error(http.StatusNotFound, "GetProjectByID", err)
158+
return
159+
}
160+
161+
if project.Type == project_model.TypeIndividual || project.Type == project_model.TypeOrganization {
162+
if err := project.LoadOwner(ctx); err != nil {
163+
ctx.Error(http.StatusNotFound, "LoadOwner", err)
164+
return
165+
}
166+
167+
if ctx.IsSigned && ctx.Doer.LowerName == strings.ToLower(project.Owner.Name) {
168+
owner = ctx.Doer
169+
} else {
170+
owner = project.Owner
171+
}
172+
173+
if project.Type == project_model.TypeOrganization {
174+
ctx.Org.Organization = (*organization.Organization)(owner)
175+
}
176+
} else {
177+
if err := project.LoadRepo(ctx); err != nil {
178+
ctx.Error(http.StatusNotFound, "LoadRepo", err)
179+
}
180+
181+
repo := project.Repo
182+
183+
if err := repo.LoadOwner(ctx); err != nil {
184+
ctx.Error(http.StatusNotFound, "LoadOwner", err)
185+
return
186+
}
187+
188+
ctx.Repo.Repository = repo
189+
owner = repo.Owner
190+
191+
if ctx.Doer != nil && ctx.Doer.ID == user_model.ActionsUserID {
192+
taskID := ctx.Data["ActionsTaskID"].(int64)
193+
task, err := actions_model.GetTaskByID(ctx, taskID)
194+
if err != nil {
195+
ctx.Error(http.StatusInternalServerError, "actions_model.GetTaskByID", err)
196+
return
197+
}
198+
if task.RepoID != repo.ID {
199+
ctx.NotFound()
200+
return
201+
}
202+
203+
if task.IsForkPullRequest {
204+
ctx.Repo.Permission.AccessMode = perm.AccessModeRead
205+
} else {
206+
ctx.Repo.Permission.AccessMode = perm.AccessModeWrite
207+
}
208+
209+
if err := ctx.Repo.Repository.LoadUnits(ctx); err != nil {
210+
ctx.Error(http.StatusInternalServerError, "LoadUnits", err)
211+
return
212+
}
213+
ctx.Repo.Permission.SetUnitsWithDefaultAccessMode(ctx.Repo.Repository.Units, ctx.Repo.Permission.AccessMode)
214+
} else {
215+
ctx.Repo.Permission, err = access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
216+
if err != nil {
217+
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
218+
return
219+
}
220+
}
221+
222+
if !ctx.Repo.Permission.HasAnyUnitAccess() {
223+
ctx.NotFound()
224+
return
225+
}
226+
}
227+
ctx.ContextUser = owner
228+
}
229+
230+
func columnAssignment() func(ctx *context.APIContext) {
231+
return func(ctx *context.APIContext) {
232+
if ctx.PathParam("column_id") == "" {
233+
return
234+
}
235+
236+
column, err := project_model.GetColumn(ctx, ctx.PathParamInt64(":column_id"))
237+
238+
if err != nil {
239+
ctx.Error(http.StatusNotFound, "GetColumn", err)
240+
}
241+
242+
projectAssignment(ctx, column.ProjectID)
243+
244+
}
245+
}
246+
138247
func repoAssignment() func(ctx *context.APIContext) {
139248
return func(ctx *context.APIContext) {
140249
userName := ctx.PathParam("username")
@@ -169,6 +278,10 @@ func repoAssignment() func(ctx *context.APIContext) {
169278
ctx.Repo.Owner = owner
170279
ctx.ContextUser = owner
171280

281+
if owner.IsOrganization() {
282+
ctx.Org.Organization = (*organization.Organization)(owner)
283+
}
284+
172285
// Get repository.
173286
repo, err := repo_model.GetRepositoryByName(ctx, owner.ID, repoName)
174287
if err != nil {
@@ -388,25 +501,16 @@ func reqAdmin() func(ctx *context.APIContext) {
388501
// reqRepoWriter user should have a permission to write to a repo, or be a site admin
389502
func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) {
390503
return func(ctx *context.APIContext) {
504+
if ctx.Repo.Repository == nil {
505+
return
506+
}
391507
if !ctx.IsUserRepoWriter(unitTypes) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
392508
ctx.Error(http.StatusForbidden, "reqRepoWriter", "user should have a permission to write to a repo")
393509
return
394510
}
395511
}
396512
}
397513

398-
// reqRepoWriterOr returns a middleware for requiring repository write to one of the unit permission
399-
func reqRepoWriterOr(unitTypes ...unit.Type) func(ctx *context.APIContext) {
400-
return func(ctx *context.APIContext) {
401-
for _, unitType := range unitTypes {
402-
if ctx.Repo.CanWrite(unitType) {
403-
return
404-
}
405-
}
406-
ctx.NotFound(ctx.Req.URL.RequestURI(), nil)
407-
}
408-
}
409-
410514
// reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin
411515
func reqRepoBranchWriter(ctx *context.APIContext) {
412516
options, ok := web.GetForm(ctx).(api.FileOptionInterface)
@@ -419,6 +523,10 @@ func reqRepoBranchWriter(ctx *context.APIContext) {
419523
// reqRepoReader user should have specific read permission or be a repo admin or a site admin
420524
func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) {
421525
return func(ctx *context.APIContext) {
526+
if ctx.Repo.Repository == nil {
527+
return
528+
}
529+
422530
if !ctx.Repo.CanRead(unitType) && !ctx.IsUserRepoAdmin() && !ctx.IsUserSiteAdmin() {
423531
ctx.Error(http.StatusForbidden, "reqRepoReader", "user should have specific read permission or be a repo admin or a site admin")
424532
return
@@ -555,6 +663,15 @@ func reqWebhooksEnabled() func(ctx *context.APIContext) {
555663
}
556664
}
557665

666+
func reqProjectOwner() func(ctx *context.APIContext) {
667+
return func(ctx *context.APIContext) {
668+
if ctx.Repo.Repository == nil && ctx.ContextUser.IsIndividual() && ctx.ContextUser != ctx.Doer {
669+
ctx.Error(http.StatusForbidden, "", "must be the project owner")
670+
return
671+
}
672+
}
673+
}
674+
558675
func orgAssignment(args ...bool) func(ctx *context.APIContext) {
559676
var (
560677
assignOrg bool
@@ -569,18 +686,6 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) {
569686
return func(ctx *context.APIContext) {
570687
ctx.Org = new(context.APIOrganization)
571688

572-
if ctx.ContextUser == nil {
573-
if ctx.Org.Organization == nil {
574-
getOrganizationByParams(ctx)
575-
ctx.ContextUser = ctx.Org.Organization.AsUser()
576-
}
577-
} else if ctx.ContextUser.IsOrganization() {
578-
if ctx.Org == nil {
579-
ctx.Org = &context.APIOrganization{}
580-
}
581-
ctx.Org.Organization = (*organization.Organization)(ctx.ContextUser)
582-
}
583-
584689
var err error
585690
if assignOrg {
586691
getOrganizationByParams(ctx)
@@ -639,6 +744,12 @@ func mustEnableRepoProjects(ctx *context.APIContext) {
639744
}
640745
}
641746

747+
func getAuthenticatedUser(ctx *context.APIContext) {
748+
if ctx.IsSigned {
749+
ctx.ContextUser = ctx.Doer
750+
}
751+
}
752+
642753
func mustEnableIssues(ctx *context.APIContext) {
643754
if !ctx.Repo.CanRead(unit.TypeIssues) {
644755
if log.IsTrace() {
@@ -719,6 +830,10 @@ func mustEnableWiki(ctx *context.APIContext) {
719830
}
720831

721832
func mustNotBeArchived(ctx *context.APIContext) {
833+
if ctx.Repo.Repository == nil {
834+
return
835+
}
836+
722837
if ctx.Repo.Repository.IsArchived {
723838
ctx.Error(http.StatusLocked, "RepoArchived", fmt.Errorf("%s is archived", ctx.Repo.Repository.LogString()))
724839
return
@@ -1015,66 +1130,51 @@ func Routes() *web.Router {
10151130
}, context.UserAssignmentAPI(), individualPermsChecker)
10161131
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
10171132

1018-
// Users (requires user scope)
1019-
m.Group("/{username}/-", func() {
1020-
m.Group("/projects", func() {
1021-
m.Group("", func() {
1022-
m.Get("", project_shared.ProjectHandler("org", project_shared.GetProjects))
1023-
m.Get("/{id}", project_shared.ProjectHandler("org", project_shared.GetProject))
1024-
})
1133+
// Projects
1134+
m.Group("/orgs/{org}/projects", func() {
1135+
m.Get("", reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true), org.GetProjects)
1136+
m.Post("", reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), bind(api.CreateProjectOption{}), org.CreateProject)
1137+
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true))
1138+
1139+
m.Group("/projects", func() {
1140+
m.Group("/{project_id}", func() {
1141+
m.Get("", project.GetProject)
1142+
m.Get("/columns", project.GetProjectColumns)
10251143

10261144
m.Group("", func() {
1027-
m.Post("", bind(api.CreateProjectOption{}), project_shared.ProjectHandler("org", project_shared.CreateProject))
1028-
m.Group("/{id}", func() {
1029-
m.Post("", bind(api.CreateProjectColumnOption{}), project_shared.ProjectHandler("org", project_shared.AddColumnToProject))
1030-
m.Delete("", project_shared.ProjectHandler("org", project_shared.DeleteProject))
1031-
m.Put("", bind(api.EditProjectOption{}), project_shared.ProjectHandler("org", project_shared.EditProject))
1032-
m.Post("/move", project_shared.MoveColumns)
1033-
m.Post("/{action:open|close}", project_shared.ChangeProjectStatus)
1034-
1035-
m.Group("/{columnID}", func() {
1036-
m.Put("", bind(api.EditProjectColumnOption{}), project_shared.ProjectHandler("org", project_shared.EditProjectColumn))
1037-
m.Delete("", project_shared.ProjectHandler("org", project_shared.DeleteProjectColumn))
1038-
m.Post("/default", project_shared.ProjectHandler("org", project_shared.SetDefaultProjectColumn))
1039-
m.Post("/move", project_shared.ProjectHandler("org", project_shared.MoveIssues))
1040-
})
1145+
m.Patch("", bind(api.EditProjectOption{}), project.EditProject)
1146+
m.Delete("", project.DeleteProject)
1147+
m.Post("/{action:open|close}", project.ChangeProjectStatus)
1148+
m.Group("/columns", func() {
1149+
m.Post("", bind(api.CreateProjectColumnOption{}), project.AddColumnToProject)
1150+
m.Patch("/move", project.MoveColumns)
1151+
m.Patch("/{column_id}/move", project.MoveIssues)
10411152
})
1042-
}, reqSelfOrAdmin(), reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true))
1043-
}, individualPermsChecker)
1153+
}, reqRepoWriter(unit.TypeProjects), mustNotBeArchived, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), reqProjectOwner())
1154+
})
10441155

1045-
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), reqToken(), context.UserAssignmentAPI(), orgAssignment(), reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true))
1156+
m.Group("/columns/{column_id}", func() {
1157+
m.Get("", project.GetProjectColumn)
10461158

1047-
// Users (requires user scope)
1048-
m.Group("/{username}/{reponame}", func() {
1049-
m.Group("/projects", func() {
10501159
m.Group("", func() {
1051-
m.Get("", project_shared.ProjectHandler("repo", project_shared.GetProjects))
1052-
m.Get("/{id}", project_shared.ProjectHandler("repo", project_shared.GetProject))
1053-
})
1160+
m.Patch("", bind(api.EditProjectColumnOption{}), project.EditProjectColumn)
1161+
m.Delete("", project.DeleteProjectColumn)
1162+
m.Post("/default", project.SetDefaultProjectColumn)
1163+
}, reqRepoWriter(unit.TypeProjects), mustNotBeArchived, reqUnitAccess(unit.TypeProjects, perm.AccessModeWrite, true), reqProjectOwner())
1164+
})
1165+
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository), reqToken(), projectIDAssignmentAPI(), columnAssignment(), individualPermsChecker, reqRepoReader(unit.TypeProjects), mustEnableRepoProjects, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true))
10541166

1055-
m.Group("", func() {
1056-
m.Post("", bind(api.CreateProjectOption{}), project_shared.ProjectHandler("repo", project_shared.CreateProject))
1057-
m.Group("/{id}", func() {
1058-
m.Post("", bind(api.CreateProjectColumnOption{}), project_shared.ProjectHandler("repo", project_shared.AddColumnToProject))
1059-
m.Delete("", project_shared.ProjectHandler("repo", project_shared.DeleteProject))
1060-
m.Put("", bind(api.EditProjectOption{}), project_shared.ProjectHandler("repo", project_shared.EditProject))
1061-
m.Post("/move", project_shared.MoveColumns)
1062-
m.Post("/{action:open|close}", project_shared.ChangeProjectStatus)
1063-
1064-
m.Group("/{columnID}", func() {
1065-
m.Put("", bind(api.EditProjectColumnOption{}), project_shared.ProjectHandler("repo", project_shared.EditProjectColumn))
1066-
m.Delete("", project_shared.ProjectHandler("repo", project_shared.DeleteProjectColumn))
1067-
m.Post("/default", project_shared.ProjectHandler("repo", project_shared.SetDefaultProjectColumn))
1068-
m.Post("/move", project_shared.ProjectHandler("repo", project_shared.MoveIssues))
1069-
})
1070-
})
1071-
}, reqRepoWriter(unit.TypeProjects), mustNotBeArchived)
1072-
}, individualPermsChecker)
1167+
m.Group("/repos/{username}/{reponame}/projects", func() {
1168+
m.Get("", repo.GetProjects)
1169+
m.Group("", func() {
1170+
m.Post("", bind(api.CreateProjectOption{}), repo.CreateProject)
1171+
m.Put("/{type:issues|pulls}", reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.UpdateIssueProject)
1172+
}, reqRepoWriter(unit.TypeProjects), mustNotBeArchived)
1173+
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), reqToken(), repoAssignment(), individualPermsChecker, reqRepoReader(unit.TypeProjects), mustEnableRepoProjects)
1174+
1175+
m.Post("/user/projects", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken(), getAuthenticatedUser, reqSelfOrAdmin(), bind(api.CreateProjectOption{}), user.CreateProject)
10731176

1074-
m.Group("/{type:issues|pulls}", func() {
1075-
m.Post("/projects", reqRepoWriterOr(unit.TypeIssues, unit.TypePullRequests), reqRepoWriter(unit.TypeProjects), project_shared.UpdateIssueProject)
1076-
}, individualPermsChecker)
1077-
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository), reqToken(), repoAssignment(), reqRepoReader(unit.TypeProjects), mustEnableRepoProjects)
1177+
m.Get("/users/{username}/projects", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken(), context.UserAssignmentAPI(), individualPermsChecker, user.GetProjects)
10781178

10791179
// Users (requires user scope)
10801180
m.Group("/users", func() {

0 commit comments

Comments
 (0)