code.gitea.io/gitea@v1.22.3/models/organization/org.go (about) 1 // Copyright 2014 The Gogs Authors. All rights reserved. 2 // Copyright 2019 The Gitea Authors. All rights reserved. 3 // SPDX-License-Identifier: MIT 4 5 package organization 6 7 import ( 8 "context" 9 "fmt" 10 "strings" 11 12 actions_model "code.gitea.io/gitea/models/actions" 13 "code.gitea.io/gitea/models/db" 14 "code.gitea.io/gitea/models/perm" 15 repo_model "code.gitea.io/gitea/models/repo" 16 secret_model "code.gitea.io/gitea/models/secret" 17 "code.gitea.io/gitea/models/unit" 18 user_model "code.gitea.io/gitea/models/user" 19 "code.gitea.io/gitea/modules/log" 20 "code.gitea.io/gitea/modules/setting" 21 "code.gitea.io/gitea/modules/structs" 22 "code.gitea.io/gitea/modules/util" 23 24 "xorm.io/builder" 25 ) 26 27 // ________ .__ __ .__ 28 // \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____ 29 // / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \ 30 // / | \ | \/ /_/ > __ \| | \ |/ / / __ \| | | ( <_> ) | \ 31 // \_______ /__| \___ (____ /___| /__/_____ \(____ /__| |__|\____/|___| / 32 // \/ /_____/ \/ \/ \/ \/ \/ 33 34 // ErrOrgNotExist represents a "OrgNotExist" kind of error. 35 type ErrOrgNotExist struct { 36 ID int64 37 Name string 38 } 39 40 // IsErrOrgNotExist checks if an error is a ErrOrgNotExist. 41 func IsErrOrgNotExist(err error) bool { 42 _, ok := err.(ErrOrgNotExist) 43 return ok 44 } 45 46 func (err ErrOrgNotExist) Error() string { 47 return fmt.Sprintf("org does not exist [id: %d, name: %s]", err.ID, err.Name) 48 } 49 50 func (err ErrOrgNotExist) Unwrap() error { 51 return util.ErrNotExist 52 } 53 54 // ErrLastOrgOwner represents a "LastOrgOwner" kind of error. 55 type ErrLastOrgOwner struct { 56 UID int64 57 } 58 59 // IsErrLastOrgOwner checks if an error is a ErrLastOrgOwner. 60 func IsErrLastOrgOwner(err error) bool { 61 _, ok := err.(ErrLastOrgOwner) 62 return ok 63 } 64 65 func (err ErrLastOrgOwner) Error() string { 66 return fmt.Sprintf("user is the last member of owner team [uid: %d]", err.UID) 67 } 68 69 // ErrUserNotAllowedCreateOrg represents a "UserNotAllowedCreateOrg" kind of error. 70 type ErrUserNotAllowedCreateOrg struct{} 71 72 // IsErrUserNotAllowedCreateOrg checks if an error is an ErrUserNotAllowedCreateOrg. 73 func IsErrUserNotAllowedCreateOrg(err error) bool { 74 _, ok := err.(ErrUserNotAllowedCreateOrg) 75 return ok 76 } 77 78 func (err ErrUserNotAllowedCreateOrg) Error() string { 79 return "user is not allowed to create organizations" 80 } 81 82 func (err ErrUserNotAllowedCreateOrg) Unwrap() error { 83 return util.ErrPermissionDenied 84 } 85 86 // Organization represents an organization 87 type Organization user_model.User 88 89 // OrgFromUser converts user to organization 90 func OrgFromUser(user *user_model.User) *Organization { 91 return (*Organization)(user) 92 } 93 94 // TableName represents the real table name of Organization 95 func (Organization) TableName() string { 96 return "user" 97 } 98 99 // IsOwnedBy returns true if given user is in the owner team. 100 func (org *Organization) IsOwnedBy(ctx context.Context, uid int64) (bool, error) { 101 return IsOrganizationOwner(ctx, org.ID, uid) 102 } 103 104 // IsOrgAdmin returns true if given user is in the owner team or an admin team. 105 func (org *Organization) IsOrgAdmin(ctx context.Context, uid int64) (bool, error) { 106 return IsOrganizationAdmin(ctx, org.ID, uid) 107 } 108 109 // IsOrgMember returns true if given user is member of organization. 110 func (org *Organization) IsOrgMember(ctx context.Context, uid int64) (bool, error) { 111 return IsOrganizationMember(ctx, org.ID, uid) 112 } 113 114 // CanCreateOrgRepo returns true if given user can create repo in organization 115 func (org *Organization) CanCreateOrgRepo(ctx context.Context, uid int64) (bool, error) { 116 return CanCreateOrgRepo(ctx, org.ID, uid) 117 } 118 119 // GetTeam returns named team of organization. 120 func (org *Organization) GetTeam(ctx context.Context, name string) (*Team, error) { 121 return GetTeam(ctx, org.ID, name) 122 } 123 124 // GetOwnerTeam returns owner team of organization. 125 func (org *Organization) GetOwnerTeam(ctx context.Context) (*Team, error) { 126 return org.GetTeam(ctx, OwnerTeamName) 127 } 128 129 // FindOrgTeams returns all teams of a given organization 130 func FindOrgTeams(ctx context.Context, orgID int64) ([]*Team, error) { 131 var teams []*Team 132 return teams, db.GetEngine(ctx). 133 Where("org_id=?", orgID). 134 OrderBy("CASE WHEN name LIKE '" + OwnerTeamName + "' THEN '' ELSE name END"). 135 Find(&teams) 136 } 137 138 // LoadTeams load teams if not loaded. 139 func (org *Organization) LoadTeams(ctx context.Context) ([]*Team, error) { 140 return FindOrgTeams(ctx, org.ID) 141 } 142 143 // GetMembers returns all members of organization. 144 func (org *Organization) GetMembers(ctx context.Context) (user_model.UserList, map[int64]bool, error) { 145 return FindOrgMembers(ctx, &FindOrgMembersOpts{ 146 OrgID: org.ID, 147 }) 148 } 149 150 // HasMemberWithUserID returns true if user with userID is part of the u organisation. 151 func (org *Organization) HasMemberWithUserID(ctx context.Context, userID int64) bool { 152 return org.hasMemberWithUserID(ctx, userID) 153 } 154 155 func (org *Organization) hasMemberWithUserID(ctx context.Context, userID int64) bool { 156 isMember, err := IsOrganizationMember(ctx, org.ID, userID) 157 if err != nil { 158 log.Error("IsOrganizationMember: %v", err) 159 return false 160 } 161 return isMember 162 } 163 164 // AvatarLink returns the full avatar link with http host 165 func (org *Organization) AvatarLink(ctx context.Context) string { 166 return org.AsUser().AvatarLink(ctx) 167 } 168 169 // HTMLURL returns the organization's full link. 170 func (org *Organization) HTMLURL() string { 171 return org.AsUser().HTMLURL() 172 } 173 174 // OrganisationLink returns the organization sub page link. 175 func (org *Organization) OrganisationLink() string { 176 return org.AsUser().OrganisationLink() 177 } 178 179 // ShortName ellipses username to length 180 func (org *Organization) ShortName(length int) string { 181 return org.AsUser().ShortName(length) 182 } 183 184 // HomeLink returns the user or organization home page link. 185 func (org *Organization) HomeLink() string { 186 return org.AsUser().HomeLink() 187 } 188 189 // CanCreateRepo returns if user login can create a repository 190 // NOTE: functions calling this assume a failure due to repository count limit; if new checks are added, those functions should be revised 191 func (org *Organization) CanCreateRepo() bool { 192 return org.AsUser().CanCreateRepo() 193 } 194 195 // FindOrgMembersOpts represensts find org members conditions 196 type FindOrgMembersOpts struct { 197 db.ListOptions 198 OrgID int64 199 PublicOnly bool 200 } 201 202 // CountOrgMembers counts the organization's members 203 func CountOrgMembers(ctx context.Context, opts *FindOrgMembersOpts) (int64, error) { 204 sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID) 205 if opts.PublicOnly { 206 sess.And("is_public = ?", true) 207 } 208 return sess.Count(new(OrgUser)) 209 } 210 211 // FindOrgMembers loads organization members according conditions 212 func FindOrgMembers(ctx context.Context, opts *FindOrgMembersOpts) (user_model.UserList, map[int64]bool, error) { 213 ous, err := GetOrgUsersByOrgID(ctx, opts) 214 if err != nil { 215 return nil, nil, err 216 } 217 218 ids := make([]int64, len(ous)) 219 idsIsPublic := make(map[int64]bool, len(ous)) 220 for i, ou := range ous { 221 ids[i] = ou.UID 222 idsIsPublic[ou.UID] = ou.IsPublic 223 } 224 225 users, err := user_model.GetUsersByIDs(ctx, ids) 226 if err != nil { 227 return nil, nil, err 228 } 229 return users, idsIsPublic, nil 230 } 231 232 // AsUser returns the org as user object 233 func (org *Organization) AsUser() *user_model.User { 234 return (*user_model.User)(org) 235 } 236 237 // DisplayName returns full name if it's not empty, 238 // returns username otherwise. 239 func (org *Organization) DisplayName() string { 240 return org.AsUser().DisplayName() 241 } 242 243 // CustomAvatarRelativePath returns user custom avatar relative path. 244 func (org *Organization) CustomAvatarRelativePath() string { 245 return org.Avatar 246 } 247 248 // UnitPermission returns unit permission 249 func (org *Organization) UnitPermission(ctx context.Context, doer *user_model.User, unitType unit.Type) perm.AccessMode { 250 if doer != nil { 251 teams, err := GetUserOrgTeams(ctx, org.ID, doer.ID) 252 if err != nil { 253 log.Error("GetUserOrgTeams: %v", err) 254 return perm.AccessModeNone 255 } 256 257 if err := teams.LoadUnits(ctx); err != nil { 258 log.Error("LoadUnits: %v", err) 259 return perm.AccessModeNone 260 } 261 262 if len(teams) > 0 { 263 return teams.UnitMaxAccess(unitType) 264 } 265 } 266 267 if org.Visibility.IsPublic() { 268 return perm.AccessModeRead 269 } 270 271 return perm.AccessModeNone 272 } 273 274 // CreateOrganization creates record of a new organization. 275 func CreateOrganization(ctx context.Context, org *Organization, owner *user_model.User) (err error) { 276 if !owner.CanCreateOrganization() { 277 return ErrUserNotAllowedCreateOrg{} 278 } 279 280 if err = user_model.IsUsableUsername(org.Name); err != nil { 281 return err 282 } 283 284 isExist, err := user_model.IsUserExist(ctx, 0, org.Name) 285 if err != nil { 286 return err 287 } else if isExist { 288 return user_model.ErrUserAlreadyExist{Name: org.Name} 289 } 290 291 org.LowerName = strings.ToLower(org.Name) 292 if org.Rands, err = user_model.GetUserSalt(); err != nil { 293 return err 294 } 295 if org.Salt, err = user_model.GetUserSalt(); err != nil { 296 return err 297 } 298 org.UseCustomAvatar = true 299 org.MaxRepoCreation = -1 300 org.NumTeams = 1 301 org.NumMembers = 1 302 org.Type = user_model.UserTypeOrganization 303 304 ctx, committer, err := db.TxContext(ctx) 305 if err != nil { 306 return err 307 } 308 defer committer.Close() 309 310 if err = user_model.DeleteUserRedirect(ctx, org.Name); err != nil { 311 return err 312 } 313 314 if err = db.Insert(ctx, org); err != nil { 315 return fmt.Errorf("insert organization: %w", err) 316 } 317 if err = user_model.GenerateRandomAvatar(ctx, org.AsUser()); err != nil { 318 return fmt.Errorf("generate random avatar: %w", err) 319 } 320 321 // Add initial creator to organization and owner team. 322 if err = db.Insert(ctx, &OrgUser{ 323 UID: owner.ID, 324 OrgID: org.ID, 325 IsPublic: setting.Service.DefaultOrgMemberVisible, 326 }); err != nil { 327 return fmt.Errorf("insert org-user relation: %w", err) 328 } 329 330 // Create default owner team. 331 t := &Team{ 332 OrgID: org.ID, 333 LowerName: strings.ToLower(OwnerTeamName), 334 Name: OwnerTeamName, 335 AccessMode: perm.AccessModeOwner, 336 NumMembers: 1, 337 IncludesAllRepositories: true, 338 CanCreateOrgRepo: true, 339 } 340 if err = db.Insert(ctx, t); err != nil { 341 return fmt.Errorf("insert owner team: %w", err) 342 } 343 344 // insert units for team 345 units := make([]TeamUnit, 0, len(unit.AllRepoUnitTypes)) 346 for _, tp := range unit.AllRepoUnitTypes { 347 up := perm.AccessModeOwner 348 if tp == unit.TypeExternalTracker || tp == unit.TypeExternalWiki { 349 up = perm.AccessModeRead 350 } 351 units = append(units, TeamUnit{ 352 OrgID: org.ID, 353 TeamID: t.ID, 354 Type: tp, 355 AccessMode: up, 356 }) 357 } 358 359 if err = db.Insert(ctx, &units); err != nil { 360 return err 361 } 362 363 if err = db.Insert(ctx, &TeamUser{ 364 UID: owner.ID, 365 OrgID: org.ID, 366 TeamID: t.ID, 367 }); err != nil { 368 return fmt.Errorf("insert team-user relation: %w", err) 369 } 370 371 return committer.Commit() 372 } 373 374 // GetOrgByName returns organization by given name. 375 func GetOrgByName(ctx context.Context, name string) (*Organization, error) { 376 if len(name) == 0 { 377 return nil, ErrOrgNotExist{0, name} 378 } 379 u := &Organization{ 380 LowerName: strings.ToLower(name), 381 Type: user_model.UserTypeOrganization, 382 } 383 has, err := db.GetEngine(ctx).Get(u) 384 if err != nil { 385 return nil, err 386 } else if !has { 387 return nil, ErrOrgNotExist{0, name} 388 } 389 return u, nil 390 } 391 392 // DeleteOrganization deletes models associated to an organization. 393 func DeleteOrganization(ctx context.Context, org *Organization) error { 394 if org.Type != user_model.UserTypeOrganization { 395 return fmt.Errorf("%s is a user not an organization", org.Name) 396 } 397 398 if err := db.DeleteBeans(ctx, 399 &Team{OrgID: org.ID}, 400 &OrgUser{OrgID: org.ID}, 401 &TeamUser{OrgID: org.ID}, 402 &TeamUnit{OrgID: org.ID}, 403 &TeamInvite{OrgID: org.ID}, 404 &secret_model.Secret{OwnerID: org.ID}, 405 &user_model.Blocking{BlockerID: org.ID}, 406 &actions_model.ActionRunner{OwnerID: org.ID}, 407 &actions_model.ActionRunnerToken{OwnerID: org.ID}, 408 ); err != nil { 409 return fmt.Errorf("DeleteBeans: %w", err) 410 } 411 412 if _, err := db.GetEngine(ctx).ID(org.ID).Delete(new(user_model.User)); err != nil { 413 return fmt.Errorf("Delete: %w", err) 414 } 415 416 return nil 417 } 418 419 // GetOrgUserMaxAuthorizeLevel returns highest authorize level of user in an organization 420 func (org *Organization) GetOrgUserMaxAuthorizeLevel(ctx context.Context, uid int64) (perm.AccessMode, error) { 421 var authorize perm.AccessMode 422 _, err := db.GetEngine(ctx). 423 Select("max(team.authorize)"). 424 Table("team"). 425 Join("INNER", "team_user", "team_user.team_id = team.id"). 426 Where("team_user.uid = ?", uid). 427 And("team_user.org_id = ?", org.ID). 428 Get(&authorize) 429 return authorize, err 430 } 431 432 // GetUsersWhoCanCreateOrgRepo returns users which are able to create repo in organization 433 func GetUsersWhoCanCreateOrgRepo(ctx context.Context, orgID int64) (map[int64]*user_model.User, error) { 434 // Use a map, in order to de-duplicate users. 435 users := make(map[int64]*user_model.User) 436 return users, db.GetEngine(ctx). 437 Join("INNER", "`team_user`", "`team_user`.uid=`user`.id"). 438 Join("INNER", "`team`", "`team`.id=`team_user`.team_id"). 439 Where(builder.Eq{"team.can_create_org_repo": true}.Or(builder.Eq{"team.authorize": perm.AccessModeOwner})). 440 And("team_user.org_id = ?", orgID).Find(&users) 441 } 442 443 // SearchOrganizationsOptions options to filter organizations 444 type SearchOrganizationsOptions struct { 445 db.ListOptions 446 All bool 447 } 448 449 // FindOrgOptions finds orgs options 450 type FindOrgOptions struct { 451 db.ListOptions 452 UserID int64 453 IncludePrivate bool 454 } 455 456 func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder { 457 cond := builder.Eq{"uid": userID} 458 if !includePrivate { 459 cond["is_public"] = true 460 } 461 return builder.Select("org_id").From("org_user").Where(cond) 462 } 463 464 func (opts FindOrgOptions) ToConds() builder.Cond { 465 var cond builder.Cond = builder.Eq{"`user`.`type`": user_model.UserTypeOrganization} 466 if opts.UserID > 0 { 467 cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludePrivate))) 468 } 469 if !opts.IncludePrivate { 470 cond = cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic}) 471 } 472 return cond 473 } 474 475 func (opts FindOrgOptions) ToOrders() string { 476 return "`user`.name ASC" 477 } 478 479 // HasOrgOrUserVisible tells if the given user can see the given org or user 480 func HasOrgOrUserVisible(ctx context.Context, orgOrUser, user *user_model.User) bool { 481 // If user is nil, it's an anonymous user/request. 482 // The Ghost user is handled like an anonymous user. 483 if user == nil || user.IsGhost() { 484 return orgOrUser.Visibility == structs.VisibleTypePublic 485 } 486 487 if user.IsAdmin || orgOrUser.ID == user.ID { 488 return true 489 } 490 491 if (orgOrUser.Visibility == structs.VisibleTypePrivate || user.IsRestricted) && !OrgFromUser(orgOrUser).hasMemberWithUserID(ctx, user.ID) { 492 return false 493 } 494 return true 495 } 496 497 // HasOrgsVisible tells if the given user can see at least one of the orgs provided 498 func HasOrgsVisible(ctx context.Context, orgs []*Organization, user *user_model.User) bool { 499 if len(orgs) == 0 { 500 return false 501 } 502 503 for _, org := range orgs { 504 if HasOrgOrUserVisible(ctx, org.AsUser(), user) { 505 return true 506 } 507 } 508 return false 509 } 510 511 // GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID 512 // are allowed to create repos. 513 func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organization, error) { 514 orgs := make([]*Organization, 0, 10) 515 516 return orgs, db.GetEngine(ctx).Where(builder.In("id", builder.Select("`user`.id").From("`user`"). 517 Join("INNER", "`team_user`", "`team_user`.org_id = `user`.id"). 518 Join("INNER", "`team`", "`team`.id = `team_user`.team_id"). 519 Where(builder.Eq{"`team_user`.uid": userID}). 520 And(builder.Eq{"`team`.authorize": perm.AccessModeOwner}.Or(builder.Eq{"`team`.can_create_org_repo": true})))). 521 Asc("`user`.name"). 522 Find(&orgs) 523 } 524 525 // GetOrgUsersByOrgID returns all organization-user relations by organization ID. 526 func GetOrgUsersByOrgID(ctx context.Context, opts *FindOrgMembersOpts) ([]*OrgUser, error) { 527 sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID) 528 if opts.PublicOnly { 529 sess.And("is_public = ?", true) 530 } 531 if opts.ListOptions.PageSize > 0 { 532 sess = db.SetSessionPagination(sess, opts) 533 534 ous := make([]*OrgUser, 0, opts.PageSize) 535 return ous, sess.Find(&ous) 536 } 537 538 var ous []*OrgUser 539 return ous, sess.Find(&ous) 540 } 541 542 // ChangeOrgUserStatus changes public or private membership status. 543 func ChangeOrgUserStatus(ctx context.Context, orgID, uid int64, public bool) error { 544 ou := new(OrgUser) 545 has, err := db.GetEngine(ctx). 546 Where("uid=?", uid). 547 And("org_id=?", orgID). 548 Get(ou) 549 if err != nil { 550 return err 551 } else if !has { 552 return nil 553 } 554 555 ou.IsPublic = public 556 _, err = db.GetEngine(ctx).ID(ou.ID).Cols("is_public").Update(ou) 557 return err 558 } 559 560 // AddOrgUser adds new user to given organization. 561 func AddOrgUser(ctx context.Context, orgID, uid int64) error { 562 isAlreadyMember, err := IsOrganizationMember(ctx, orgID, uid) 563 if err != nil || isAlreadyMember { 564 return err 565 } 566 567 ctx, committer, err := db.TxContext(ctx) 568 if err != nil { 569 return err 570 } 571 defer committer.Close() 572 573 // check in transaction 574 isAlreadyMember, err = IsOrganizationMember(ctx, orgID, uid) 575 if err != nil || isAlreadyMember { 576 return err 577 } 578 579 ou := &OrgUser{ 580 UID: uid, 581 OrgID: orgID, 582 IsPublic: setting.Service.DefaultOrgMemberVisible, 583 } 584 585 if err := db.Insert(ctx, ou); err != nil { 586 return err 587 } else if _, err = db.Exec(ctx, "UPDATE `user` SET num_members = num_members + 1 WHERE id = ?", orgID); err != nil { 588 return err 589 } 590 591 return committer.Commit() 592 } 593 594 // GetOrgByID returns the user object by given ID if exists. 595 func GetOrgByID(ctx context.Context, id int64) (*Organization, error) { 596 u := new(Organization) 597 has, err := db.GetEngine(ctx).ID(id).Get(u) 598 if err != nil { 599 return nil, err 600 } else if !has { 601 return nil, user_model.ErrUserNotExist{ 602 UID: id, 603 } 604 } 605 return u, nil 606 } 607 608 // RemoveOrgRepo removes all team-repository relations of organization. 609 func RemoveOrgRepo(ctx context.Context, orgID, repoID int64) error { 610 teamRepos := make([]*TeamRepo, 0, 10) 611 e := db.GetEngine(ctx) 612 if err := e.Find(&teamRepos, &TeamRepo{OrgID: orgID, RepoID: repoID}); err != nil { 613 return err 614 } 615 616 if len(teamRepos) == 0 { 617 return nil 618 } 619 620 if _, err := e.Delete(&TeamRepo{ 621 OrgID: orgID, 622 RepoID: repoID, 623 }); err != nil { 624 return err 625 } 626 627 teamIDs := make([]int64, len(teamRepos)) 628 for i, teamRepo := range teamRepos { 629 teamIDs[i] = teamRepo.TeamID 630 } 631 632 _, err := e.Decr("num_repos").In("id", teamIDs).Update(new(Team)) 633 return err 634 } 635 636 func (org *Organization) getUserTeams(ctx context.Context, userID int64, cols ...string) ([]*Team, error) { 637 teams := make([]*Team, 0, org.NumTeams) 638 return teams, db.GetEngine(ctx). 639 Where("`team_user`.org_id = ?", org.ID). 640 Join("INNER", "team_user", "`team_user`.team_id = team.id"). 641 Join("INNER", "`user`", "`user`.id=team_user.uid"). 642 And("`team_user`.uid = ?", userID). 643 Asc("`user`.name"). 644 Cols(cols...). 645 Find(&teams) 646 } 647 648 func (org *Organization) getUserTeamIDs(ctx context.Context, userID int64) ([]int64, error) { 649 teamIDs := make([]int64, 0, org.NumTeams) 650 return teamIDs, db.GetEngine(ctx). 651 Table("team"). 652 Cols("team.id"). 653 Where("`team_user`.org_id = ?", org.ID). 654 Join("INNER", "team_user", "`team_user`.team_id = team.id"). 655 And("`team_user`.uid = ?", userID). 656 Find(&teamIDs) 657 } 658 659 // TeamsWithAccessToRepo returns all teams that have given access level to the repository. 660 func (org *Organization) TeamsWithAccessToRepo(ctx context.Context, repoID int64, mode perm.AccessMode) ([]*Team, error) { 661 return GetTeamsWithAccessToRepo(ctx, org.ID, repoID, mode) 662 } 663 664 // GetUserTeamIDs returns of all team IDs of the organization that user is member of. 665 func (org *Organization) GetUserTeamIDs(ctx context.Context, userID int64) ([]int64, error) { 666 return org.getUserTeamIDs(ctx, userID) 667 } 668 669 // GetUserTeams returns all teams that belong to user, 670 // and that the user has joined. 671 func (org *Organization) GetUserTeams(ctx context.Context, userID int64) ([]*Team, error) { 672 return org.getUserTeams(ctx, userID) 673 } 674 675 // AccessibleReposEnvironment operations involving the repositories that are 676 // accessible to a particular user 677 type AccessibleReposEnvironment interface { 678 CountRepos() (int64, error) 679 RepoIDs(page, pageSize int) ([]int64, error) 680 Repos(page, pageSize int) (repo_model.RepositoryList, error) 681 MirrorRepos() (repo_model.RepositoryList, error) 682 AddKeyword(keyword string) 683 SetSort(db.SearchOrderBy) 684 } 685 686 type accessibleReposEnv struct { 687 org *Organization 688 user *user_model.User 689 team *Team 690 teamIDs []int64 691 ctx context.Context 692 keyword string 693 orderBy db.SearchOrderBy 694 } 695 696 // AccessibleReposEnv builds an AccessibleReposEnvironment for the repositories in `org` 697 // that are accessible to the specified user. 698 func AccessibleReposEnv(ctx context.Context, org *Organization, userID int64) (AccessibleReposEnvironment, error) { 699 var user *user_model.User 700 701 if userID > 0 { 702 u, err := user_model.GetUserByID(ctx, userID) 703 if err != nil { 704 return nil, err 705 } 706 user = u 707 } 708 709 teamIDs, err := org.getUserTeamIDs(ctx, userID) 710 if err != nil { 711 return nil, err 712 } 713 return &accessibleReposEnv{ 714 org: org, 715 user: user, 716 teamIDs: teamIDs, 717 ctx: ctx, 718 orderBy: db.SearchOrderByRecentUpdated, 719 }, nil 720 } 721 722 // AccessibleTeamReposEnv an AccessibleReposEnvironment for the repositories in `org` 723 // that are accessible to the specified team. 724 func (org *Organization) AccessibleTeamReposEnv(ctx context.Context, team *Team) AccessibleReposEnvironment { 725 return &accessibleReposEnv{ 726 org: org, 727 team: team, 728 ctx: ctx, 729 orderBy: db.SearchOrderByRecentUpdated, 730 } 731 } 732 733 func (env *accessibleReposEnv) cond() builder.Cond { 734 cond := builder.NewCond() 735 if env.team != nil { 736 cond = cond.And(builder.Eq{"team_repo.team_id": env.team.ID}) 737 } else { 738 if env.user == nil || !env.user.IsRestricted { 739 cond = cond.Or(builder.Eq{ 740 "`repository`.owner_id": env.org.ID, 741 "`repository`.is_private": false, 742 }) 743 } 744 if len(env.teamIDs) > 0 { 745 cond = cond.Or(builder.In("team_repo.team_id", env.teamIDs)) 746 } 747 } 748 if env.keyword != "" { 749 cond = cond.And(builder.Like{"`repository`.lower_name", strings.ToLower(env.keyword)}) 750 } 751 return cond 752 } 753 754 func (env *accessibleReposEnv) CountRepos() (int64, error) { 755 repoCount, err := db.GetEngine(env.ctx). 756 Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id"). 757 Where(env.cond()). 758 Distinct("`repository`.id"). 759 Count(&repo_model.Repository{}) 760 if err != nil { 761 return 0, fmt.Errorf("count user repositories in organization: %w", err) 762 } 763 return repoCount, nil 764 } 765 766 func (env *accessibleReposEnv) RepoIDs(page, pageSize int) ([]int64, error) { 767 if page <= 0 { 768 page = 1 769 } 770 771 repoIDs := make([]int64, 0, pageSize) 772 return repoIDs, db.GetEngine(env.ctx). 773 Table("repository"). 774 Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id"). 775 Where(env.cond()). 776 GroupBy("`repository`.id,`repository`."+strings.Fields(string(env.orderBy))[0]). 777 OrderBy(string(env.orderBy)). 778 Limit(pageSize, (page-1)*pageSize). 779 Cols("`repository`.id"). 780 Find(&repoIDs) 781 } 782 783 func (env *accessibleReposEnv) Repos(page, pageSize int) (repo_model.RepositoryList, error) { 784 repoIDs, err := env.RepoIDs(page, pageSize) 785 if err != nil { 786 return nil, fmt.Errorf("GetUserRepositoryIDs: %w", err) 787 } 788 789 repos := make([]*repo_model.Repository, 0, len(repoIDs)) 790 if len(repoIDs) == 0 { 791 return repos, nil 792 } 793 794 return repos, db.GetEngine(env.ctx). 795 In("`repository`.id", repoIDs). 796 OrderBy(string(env.orderBy)). 797 Find(&repos) 798 } 799 800 func (env *accessibleReposEnv) MirrorRepoIDs() ([]int64, error) { 801 repoIDs := make([]int64, 0, 10) 802 return repoIDs, db.GetEngine(env.ctx). 803 Table("repository"). 804 Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id AND `repository`.is_mirror=?", true). 805 Where(env.cond()). 806 GroupBy("`repository`.id, `repository`.updated_unix"). 807 OrderBy(string(env.orderBy)). 808 Cols("`repository`.id"). 809 Find(&repoIDs) 810 } 811 812 func (env *accessibleReposEnv) MirrorRepos() (repo_model.RepositoryList, error) { 813 repoIDs, err := env.MirrorRepoIDs() 814 if err != nil { 815 return nil, fmt.Errorf("MirrorRepoIDs: %w", err) 816 } 817 818 repos := make([]*repo_model.Repository, 0, len(repoIDs)) 819 if len(repoIDs) == 0 { 820 return repos, nil 821 } 822 823 return repos, db.GetEngine(env.ctx). 824 In("`repository`.id", repoIDs). 825 Find(&repos) 826 } 827 828 func (env *accessibleReposEnv) AddKeyword(keyword string) { 829 env.keyword = keyword 830 } 831 832 func (env *accessibleReposEnv) SetSort(orderBy db.SearchOrderBy) { 833 env.orderBy = orderBy 834 }