@@ -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+
138247func 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
389502func 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
411515func 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
420524func 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+
558675func 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+
642753func 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
721832func 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