organizations.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. // Copyright 2022 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package db
  5. import (
  6. "context"
  7. "fmt"
  8. "os"
  9. "strings"
  10. "github.com/pkg/errors"
  11. "gorm.io/gorm"
  12. "gogs.io/gogs/internal/dbutil"
  13. "gogs.io/gogs/internal/errutil"
  14. "gogs.io/gogs/internal/repoutil"
  15. "gogs.io/gogs/internal/userutil"
  16. )
  17. // OrganizationsStore is the persistent interface for organizations.
  18. type OrganizationsStore interface {
  19. // Create creates a new organization with the initial owner and persists to
  20. // database. It returns ErrNameNotAllowed if the given name or pattern of the
  21. // name is not allowed as an organization name, or ErrOrganizationAlreadyExist
  22. // when a user or an organization with same name already exists.
  23. Create(ctx context.Context, name string, ownerID int64, opts CreateOrganizationOptions) (*Organization, error)
  24. // GetByName returns the organization with given name.
  25. GetByName(ctx context.Context, name string) (*Organization, error)
  26. // SearchByName returns a list of organizations whose username or full name
  27. // matches the given keyword case-insensitively. Results are paginated by given
  28. // page and page size, and sorted by the given order (e.g. "id DESC"). A total
  29. // count of all results is also returned. If the order is not given, it's up to
  30. // the database to decide.
  31. SearchByName(ctx context.Context, keyword string, page, pageSize int, orderBy string) ([]*Organization, int64, error)
  32. // List returns a list of organizations filtered by options.
  33. List(ctx context.Context, opts ListOrganizationsOptions) ([]*Organization, error)
  34. // CountByUser returns the number of organizations the user is a member of.
  35. CountByUser(ctx context.Context, userID int64) (int64, error)
  36. // Count returns the total number of organizations.
  37. Count(ctx context.Context) int64
  38. // DeleteByID deletes the given organization and all their resources. It returns
  39. // ErrOrganizationOwnRepos when the user still has repository ownership.
  40. DeleteByID(ctx context.Context, orgID int64) error
  41. // AddMember adds a new member to the given organization.
  42. AddMember(ctx context.Context, orgID, userID int64) error
  43. // RemoveMember removes a member from the given organization.
  44. RemoveMember(ctx context.Context, orgID, userID int64) error
  45. // HasMember returns whether the given user is a member of the organization
  46. // (first), and whether the organization membership is public (second).
  47. HasMember(ctx context.Context, orgID, userID int64) (bool, bool)
  48. // ListMembers returns all members of the given organization, and sorted by the
  49. // given order (e.g. "id ASC").
  50. ListMembers(ctx context.Context, orgID int64, opts ListOrgMembersOptions) ([]*User, error)
  51. // IsOwnedBy returns true if the given user is an owner of the organization.
  52. IsOwnedBy(ctx context.Context, orgID, userID int64) bool
  53. // SetMemberVisibility sets the visibility of the given user in the organization.
  54. SetMemberVisibility(ctx context.Context, orgID, userID int64, public bool) error
  55. // GetTeamByName returns the team with given name under the given organization.
  56. // It returns ErrTeamNotExist whe not found.
  57. GetTeamByName(ctx context.Context, orgID int64, name string) (*Team, error)
  58. // AccessibleRepositoriesByUser returns a range of repositories in the
  59. // organization that the user has access to and the total number of it. Results
  60. // are paginated by given page and page size, and OrderByUpdatedDesc is used.
  61. AccessibleRepositoriesByUser(ctx context.Context, orgID, userID int64, page, pageSize int, opts AccessibleRepositoriesByUserOptions) ([]*Repository, int64, error)
  62. // MirrorRepositoriesByUser returns a list of mirror repositories of the
  63. // organization which the user has access to.
  64. MirrorRepositoriesByUser(ctx context.Context, orgID, userID int64) ([]*Repository, error)
  65. }
  66. var Organizations OrganizationsStore
  67. var _ OrganizationsStore = (*organizations)(nil)
  68. type organizations struct {
  69. *gorm.DB
  70. }
  71. // NewOrganizationsStore returns a persistent interface for orgs with given
  72. // database connection.
  73. func NewOrganizationsStore(db *gorm.DB) OrganizationsStore {
  74. return &organizations{DB: db}
  75. }
  76. func (*organizations) recountMembers(tx *gorm.DB, orgID int64) error {
  77. /*
  78. Equivalent SQL for PostgreSQL:
  79. UPDATE "user"
  80. SET num_members = (
  81. SELECT COUNT(*) FROM org_user WHERE org_id = @orgID
  82. )
  83. WHERE id = @orgID
  84. */
  85. err := tx.Model(&User{}).
  86. Where("id = ?", orgID).
  87. Update(
  88. "num_members",
  89. tx.Model(&OrgUser{}).Select("COUNT(*)").Where("org_id = ?", orgID),
  90. ).
  91. Error
  92. if err != nil {
  93. return errors.Wrap(err, `update "user.num_members"`)
  94. }
  95. return nil
  96. }
  97. func (db *organizations) AddMember(ctx context.Context, orgID, userID int64) error {
  98. return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
  99. ou := &OrgUser{
  100. UserID: userID,
  101. OrgID: orgID,
  102. }
  103. result := tx.FirstOrCreate(ou, ou)
  104. if result.Error != nil {
  105. return errors.Wrap(result.Error, "upsert")
  106. } else if result.RowsAffected <= 0 {
  107. return nil // Relation already exists
  108. }
  109. return db.recountMembers(tx, orgID)
  110. })
  111. }
  112. type ErrLastOrgOwner struct {
  113. args map[string]any
  114. }
  115. func IsErrLastOrgOwner(err error) bool {
  116. return errors.As(err, &ErrLastOrgOwner{})
  117. }
  118. func (err ErrLastOrgOwner) Error() string {
  119. return fmt.Sprintf("user is the last owner of the organization: %v", err.args)
  120. }
  121. func (db *organizations) RemoveMember(ctx context.Context, orgID, userID int64) error {
  122. ou, err := db.getOrgUser(ctx, orgID, userID)
  123. if err != nil {
  124. if errors.Is(err, gorm.ErrRecordNotFound) {
  125. return nil // Not a member
  126. }
  127. return errors.Wrap(err, "check organization membership")
  128. }
  129. // Check if the member to remove is the last owner.
  130. if ou.IsOwner {
  131. t, err := db.GetTeamByName(ctx, orgID, TeamNameOwners)
  132. if err != nil {
  133. return errors.Wrap(err, "get owners team")
  134. } else if t.NumMembers == 1 {
  135. return ErrLastOrgOwner{args: map[string]any{"orgID": orgID, "userID": userID}}
  136. }
  137. }
  138. return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
  139. repoIDsConds := db.accessibleRepositoriesByUser(tx, orgID, userID, accessibleRepositoriesByUserOptions{}).Select("repository.id")
  140. err := tx.Where("user_id = ? AND repo_id IN (?)", userID, repoIDsConds).Delete(&Watch{}).Error
  141. if err != nil {
  142. return errors.Wrap(err, "unwatch repositories")
  143. }
  144. err = tx.Table("repository").
  145. Where("id IN (?)", repoIDsConds).
  146. UpdateColumn("num_watches", gorm.Expr("num_watches - 1")).
  147. Error
  148. if err != nil {
  149. return errors.Wrap(err, `decrease "repository.num_watches"`)
  150. }
  151. err = tx.Where("user_id = ? AND repo_id IN (?)", userID, repoIDsConds).Delete(&Access{}).Error
  152. if err != nil {
  153. return errors.Wrap(err, "delete repository accesses")
  154. }
  155. err = tx.Where("user_id = ? AND repo_id IN (?)", userID, repoIDsConds).Delete(&Collaboration{}).Error
  156. if err != nil {
  157. return errors.Wrap(err, "delete repository collaborations")
  158. }
  159. /*
  160. Equivalent SQL for PostgreSQL:
  161. UPDATE "team"
  162. SET num_members = num_members - 1
  163. WHERE id IN (
  164. SELECT team_id FROM "team_user"
  165. WHERE team_user.org_id = @orgID AND uid = @userID)
  166. )
  167. */
  168. err = tx.Table("team").
  169. Where(`id IN (?)`, tx.
  170. Select("team_id").
  171. Table("team_user").
  172. Where("org_id = ? AND uid = ?", orgID, userID),
  173. ).
  174. UpdateColumn("num_members", gorm.Expr("num_members - 1")).
  175. Error
  176. if err != nil {
  177. return errors.Wrap(err, `decrease "team.num_members"`)
  178. }
  179. err = tx.Where("uid = ? AND org_id = ?", userID, orgID).Delete(&TeamUser{}).Error
  180. if err != nil {
  181. return errors.Wrap(err, "delete team membership")
  182. }
  183. err = tx.Where("uid = ? AND org_id = ?", userID, orgID).Delete(&OrgUser{}).Error
  184. if err != nil {
  185. return errors.Wrap(err, "delete organization membership")
  186. }
  187. return db.recountMembers(tx, orgID)
  188. })
  189. }
  190. type OrderBy int
  191. const (
  192. OrderByIDAsc OrderBy = iota + 1
  193. OrderByUpdatedDesc
  194. )
  195. type accessibleRepositoriesByUserOptions struct {
  196. orderBy OrderBy
  197. page int
  198. pageSize int
  199. }
  200. func (*organizations) accessibleRepositoriesByUser(tx *gorm.DB, orgID, userID int64, opts accessibleRepositoriesByUserOptions) *gorm.DB {
  201. /*
  202. Equivalent SQL for PostgreSQL:
  203. <SELECT * FROM "repository">
  204. JOIN team_repo ON repository.id = team_repo.repo_id
  205. WHERE
  206. owner_id = @orgID
  207. AND (
  208. team_repo.team_id IN (
  209. SELECT team_id FROM "team_user"
  210. WHERE team_user.org_id = @orgID AND uid = @userID)
  211. )
  212. OR (repository.is_private = FALSE AND repository.is_unlisted = FALSE)
  213. )
  214. [ORDER BY updated_unix DESC]
  215. [LIMIT @limit OFFSET @offset]
  216. */
  217. conds := tx.
  218. Joins("JOIN team_repo ON repository.id = team_repo.repo_id").
  219. Where("owner_id = ? AND (?)", orgID, tx.
  220. Where("team_repo.team_id IN (?)", tx.
  221. Select("team_id").
  222. Table("team_user").
  223. Where("team_user.org_id = ? AND uid = ?", orgID, userID),
  224. ).
  225. Or("repository.is_private = ? AND repository.is_unlisted = ?", false, false),
  226. )
  227. if opts.orderBy == OrderByUpdatedDesc {
  228. conds.Order("updated_unix DESC")
  229. }
  230. if opts.page > 0 && opts.pageSize > 0 {
  231. conds.Limit(opts.pageSize).Offset((opts.page - 1) * opts.pageSize)
  232. }
  233. return conds
  234. }
  235. type AccessibleRepositoriesByUserOptions struct {
  236. // Whether to skip counting the total number of repositories.
  237. SkipCount bool
  238. }
  239. func (db *organizations) AccessibleRepositoriesByUser(ctx context.Context, orgID, userID int64, page, pageSize int, opts AccessibleRepositoriesByUserOptions) ([]*Repository, int64, error) {
  240. conds := db.accessibleRepositoriesByUser(
  241. db.DB,
  242. orgID,
  243. userID,
  244. accessibleRepositoriesByUserOptions{
  245. orderBy: OrderByUpdatedDesc,
  246. page: page,
  247. pageSize: pageSize,
  248. },
  249. ).WithContext(ctx)
  250. repos := make([]*Repository, 0, pageSize)
  251. err := conds.Find(&repos).Error
  252. if err != nil {
  253. return nil, 0, errors.Wrap(err, "list repositories")
  254. }
  255. if opts.SkipCount {
  256. return repos, 0, nil
  257. }
  258. var count int64
  259. err = conds.Model(&Repository{}).Count(&count).Error
  260. if err != nil {
  261. return nil, 0, errors.Wrap(err, "count repositories")
  262. }
  263. return repos, count, nil
  264. }
  265. func (db *organizations) MirrorRepositoriesByUser(ctx context.Context, orgID, userID int64) ([]*Repository, error) {
  266. /*
  267. Equivalent SQL for PostgreSQL:
  268. SELECT * FROM "repository"
  269. JOIN team_repo ON repository.id = team_repo.repo_id
  270. WHERE
  271. owner_id = @orgID
  272. AND repository.is_mirror = TRUE
  273. AND (
  274. team_repo.team_id IN (
  275. SELECT team_id FROM "team_user"
  276. WHERE team_user.org_id = @orgID AND uid = @userID)
  277. )
  278. OR repository.is_private = FALSE
  279. )
  280. ORDER BY updated_unix DESC
  281. */
  282. var repos []*Repository
  283. return repos, db.WithContext(ctx).
  284. Joins("JOIN team_repo ON repository.id = team_repo.repo_id").
  285. Where("owner_id = ? AND repository.is_mirror = ? AND (?)", orgID, true, db.
  286. Where("team_repo.team_id IN (?)", db.
  287. Select("team_id").
  288. Table("team_user").
  289. Where("team_user.org_id = ? AND uid = ?", orgID, userID),
  290. ).
  291. Or("repository.is_private = ?", false),
  292. ).
  293. Order("updated_unix DESC").
  294. Find(&repos).
  295. Error
  296. }
  297. func (db *organizations) getOrgUser(ctx context.Context, orgID, userID int64) (*OrgUser, error) {
  298. var ou OrgUser
  299. return &ou, db.WithContext(ctx).Where("org_id = ? AND uid = ?", orgID, userID).First(&ou).Error
  300. }
  301. func (db *organizations) IsOwnedBy(ctx context.Context, orgID, userID int64) bool {
  302. ou, err := db.getOrgUser(ctx, orgID, userID)
  303. return err == nil && ou.IsOwner
  304. }
  305. func (db *organizations) SetMemberVisibility(ctx context.Context, orgID, userID int64, public bool) error {
  306. return db.Table("org_user").Where("org_id = ? AND uid = ?", orgID, userID).UpdateColumn("is_public", public).Error
  307. }
  308. func (db *organizations) HasMember(ctx context.Context, orgID, userID int64) (bool, bool) {
  309. ou, err := db.getOrgUser(ctx, orgID, userID)
  310. return err == nil, ou != nil && ou.IsPublic
  311. }
  312. type ListOrgMembersOptions struct {
  313. // The maximum number of members to return.
  314. Limit int
  315. }
  316. func (db *organizations) ListMembers(ctx context.Context, orgID int64, opts ListOrgMembersOptions) ([]*User, error) {
  317. /*
  318. Equivalent SQL for PostgreSQL:
  319. SELECT * FROM "user"
  320. JOIN org_user ON org_user.uid = user.id
  321. WHERE
  322. org_user.org_id = @orgID
  323. ORDER BY user.id ASC
  324. [LIMIT @limit]
  325. */
  326. conds := db.WithContext(ctx).
  327. Joins(dbutil.Quote("JOIN org_user ON org_user.uid = %s.id", "user")).
  328. Where("org_user.org_id = ?", orgID).
  329. Order(dbutil.Quote("%s.id ASC", "user"))
  330. if opts.Limit > 0 {
  331. conds.Limit(opts.Limit)
  332. }
  333. var users []*User
  334. return users, conds.Find(&users).Error
  335. }
  336. type ListOrganizationsOptions struct {
  337. // Filter by the membership with the given user ID. It cannot be set when the
  338. // OwnerID is also set.
  339. MemberID int64
  340. // Filter by the ownership with the given user ID. It cannot be set when the
  341. // MemberID is also set.
  342. OwnerID int64
  343. // Whether to include private memberships.
  344. IncludePrivateMembers bool
  345. // Order by the given field and direction. Default is OrderByIDAsc.
  346. OrderBy OrderBy
  347. // 1-based page number.
  348. Page int
  349. // Number of results per page.
  350. PageSize int
  351. }
  352. func (db *organizations) List(ctx context.Context, opts ListOrganizationsOptions) ([]*Organization, error) {
  353. if opts.MemberID > 0 && opts.OwnerID > 0 {
  354. return nil, errors.New("cannot filter by both MemberID and OwnerID")
  355. }
  356. /*
  357. Equivalent SQL for PostgreSQL:
  358. SELECT * FROM "user"
  359. [JOIN org_user ON org_user.org_id = user.id]
  360. WHERE
  361. type = @type
  362. [AND org_user.uid = (@memberID | @ownerID)
  363. AND org_user.is_public = @includePrivateMembers
  364. AND org_user.is_owner = @ownerID > 0]
  365. ORDER BY (user.id ASC | user.updated_unix DESC)
  366. [LIMIT @limit OFFSET @offset]
  367. */
  368. conds := db.WithContext(ctx).Where("type = ?", UserTypeOrganization)
  369. if opts.MemberID > 0 || opts.OwnerID > 0 || !opts.IncludePrivateMembers {
  370. conds.Joins(dbutil.Quote("JOIN org_user ON org_user.org_id = %s.id", "user"))
  371. }
  372. if opts.MemberID > 0 {
  373. conds.Where("org_user.uid = ?", opts.MemberID)
  374. } else if opts.OwnerID > 0 {
  375. conds.Where("org_user.uid = ? AND org_user.is_owner = ?", opts.OwnerID, true)
  376. }
  377. if !opts.IncludePrivateMembers {
  378. conds.Where("org_user.is_public = ?", true)
  379. }
  380. if opts.OrderBy == OrderByUpdatedDesc {
  381. conds.Order(dbutil.Quote("%s.updated_unix DESC", "user"))
  382. } else {
  383. conds.Order(dbutil.Quote("%s.id ASC", "user"))
  384. }
  385. if opts.Page > 0 && opts.PageSize > 0 {
  386. conds.Limit(opts.PageSize).Offset((opts.Page - 1) * opts.PageSize)
  387. }
  388. var orgs []*Organization
  389. return orgs, conds.Find(&orgs).Error
  390. }
  391. type CreateOrganizationOptions struct {
  392. FullName string
  393. Email string
  394. Location string
  395. Website string
  396. Description string
  397. }
  398. type ErrOrganizationAlreadyExist struct {
  399. args errutil.Args
  400. }
  401. // IsErrOrganizationAlreadyExist returns true if the underlying error has the
  402. // type ErrOrganizationAlreadyExist.
  403. func IsErrOrganizationAlreadyExist(err error) bool {
  404. return errors.As(err, &ErrOrganizationAlreadyExist{})
  405. }
  406. func (err ErrOrganizationAlreadyExist) Error() string {
  407. return fmt.Sprintf("organization already exists: %v", err.args)
  408. }
  409. func (db *organizations) Create(ctx context.Context, name string, ownerID int64, opts CreateOrganizationOptions) (*Organization, error) {
  410. err := isUsernameAllowed(name)
  411. if err != nil {
  412. return nil, err
  413. }
  414. if NewUsersStore(db.DB).IsUsernameUsed(ctx, name, 0) {
  415. return nil, ErrOrganizationAlreadyExist{
  416. args: errutil.Args{
  417. "name": name,
  418. },
  419. }
  420. }
  421. org := &Organization{
  422. LowerName: strings.ToLower(name),
  423. Name: name,
  424. FullName: opts.FullName,
  425. Email: opts.Email,
  426. Type: UserTypeOrganization,
  427. Location: opts.Location,
  428. Website: opts.Website,
  429. MaxRepoCreation: -1,
  430. IsActive: true,
  431. UseCustomAvatar: true,
  432. Description: opts.Description,
  433. NumTeams: 1, // The default "owners" team
  434. NumMembers: 1, // The initial owner
  435. }
  436. org.Rands, err = userutil.RandomSalt()
  437. if err != nil {
  438. return nil, err
  439. }
  440. org.Salt, err = userutil.RandomSalt()
  441. if err != nil {
  442. return nil, err
  443. }
  444. return org, db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
  445. err := tx.Create(org).Error
  446. if err != nil {
  447. return errors.Wrap(err, "create organization")
  448. }
  449. err = tx.Create(&OrgUser{
  450. UserID: ownerID,
  451. OrgID: org.ID,
  452. IsOwner: true,
  453. NumTeams: 1,
  454. }).Error
  455. if err != nil {
  456. return errors.Wrap(err, "create org-user relation")
  457. }
  458. team := &Team{
  459. OrgID: org.ID,
  460. LowerName: strings.ToLower(TeamNameOwners),
  461. Name: TeamNameOwners,
  462. Authorize: AccessModeOwner,
  463. NumMembers: 1,
  464. }
  465. err = tx.Create(team).Error
  466. if err != nil {
  467. return errors.Wrap(err, "create owner team")
  468. }
  469. err = tx.Create(&TeamUser{
  470. UID: ownerID,
  471. OrgID: org.ID,
  472. TeamID: team.ID,
  473. }).Error
  474. if err != nil {
  475. return errors.Wrap(err, "create team-user relation")
  476. }
  477. err = userutil.GenerateRandomAvatar(org.ID, org.Name, org.Email)
  478. if err != nil {
  479. return errors.Wrap(err, "generate organization avatar")
  480. }
  481. err = os.MkdirAll(repoutil.UserPath(org.Name), os.ModePerm)
  482. if err != nil {
  483. return errors.Wrap(err, "create organization directory")
  484. }
  485. return nil
  486. })
  487. }
  488. var _ errutil.NotFound = (*ErrUserNotExist)(nil)
  489. type ErrOrganizationNotExist struct {
  490. args errutil.Args
  491. }
  492. // IsErrOrganizationNotExist returns true if the underlying error has the type
  493. // ErrOrganizationNotExist.
  494. func IsErrOrganizationNotExist(err error) bool {
  495. return errors.As(err, &ErrOrganizationNotExist{})
  496. }
  497. func (err ErrOrganizationNotExist) Error() string {
  498. return fmt.Sprintf("organization does not exist: %v", err.args)
  499. }
  500. func (ErrOrganizationNotExist) NotFound() bool {
  501. return true
  502. }
  503. func (db *organizations) GetByName(ctx context.Context, name string) (*Organization, error) {
  504. org, err := getUserByUsername(ctx, db.DB, UserTypeOrganization, name)
  505. if err != nil {
  506. if IsErrUserNotExist(err) {
  507. return nil, ErrOrganizationNotExist{args: map[string]any{"name": name}}
  508. }
  509. return nil, errors.Wrap(err, "get organization by name")
  510. }
  511. return org, nil
  512. }
  513. func (db *organizations) SearchByName(ctx context.Context, keyword string, page, pageSize int, orderBy string) ([]*Organization, int64, error) {
  514. return searchUserByName(ctx, db.DB, UserTypeOrganization, keyword, page, pageSize, orderBy)
  515. }
  516. func (db *organizations) CountByUser(ctx context.Context, userID int64) (int64, error) {
  517. var count int64
  518. return count, db.WithContext(ctx).Model(&OrgUser{}).Where("uid = ?", userID).Count(&count).Error
  519. }
  520. func (db *organizations) Count(ctx context.Context) int64 {
  521. var count int64
  522. db.WithContext(ctx).Model(&User{}).Where("type = ?", UserTypeOrganization).Count(&count)
  523. return count
  524. }
  525. type ErrOrganizationOwnRepos struct {
  526. args errutil.Args
  527. }
  528. // IsErrOrganizationOwnRepos returns true if the underlying error has the type
  529. // ErrOrganizationOwnRepos.
  530. func IsErrOrganizationOwnRepos(err error) bool {
  531. return errors.As(errors.Cause(err), &ErrOrganizationOwnRepos{})
  532. }
  533. func (err ErrOrganizationOwnRepos) Error() string {
  534. return fmt.Sprintf("organization still has repository ownership: %v", err.args)
  535. }
  536. func (db *organizations) DeleteByID(ctx context.Context, orgID int64) error {
  537. return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
  538. for _, t := range []any{&Team{}, &OrgUser{}, &TeamUser{}} {
  539. err := tx.Where("org_id = ?", orgID).Delete(t).Error
  540. if err != nil {
  541. return errors.Wrapf(err, "clean up table %T", t)
  542. }
  543. }
  544. err := NewUsersStore(tx).DeleteByID(ctx, orgID, false)
  545. if err != nil {
  546. return errors.Wrap(err, "delete organization")
  547. }
  548. return nil
  549. })
  550. }
  551. var _ errutil.NotFound = (*ErrTeamNotExist)(nil)
  552. type ErrTeamNotExist struct {
  553. args map[string]any
  554. }
  555. func IsErrTeamNotExist(err error) bool {
  556. return errors.As(err, &ErrTeamNotExist{})
  557. }
  558. func (err ErrTeamNotExist) Error() string {
  559. return fmt.Sprintf("team does not exist: %v", err.args)
  560. }
  561. func (ErrTeamNotExist) NotFound() bool {
  562. return true
  563. }
  564. func (db *organizations) GetTeamByName(ctx context.Context, orgID int64, name string) (*Team, error) {
  565. var team Team
  566. err := db.WithContext(ctx).Where("org_id = ? AND lower_name = ?", orgID, strings.ToLower(name)).First(&team).Error
  567. if err != nil {
  568. if errors.Is(err, gorm.ErrRecordNotFound) {
  569. return nil, ErrTeamNotExist{args: map[string]any{"orgID": orgID, "name": name}}
  570. }
  571. return nil, errors.Wrap(err, "get team by name")
  572. }
  573. return &team, nil
  574. }
  575. type Organization = User
  576. func (u *Organization) TableName() string {
  577. return "user"
  578. }
  579. // IsOwnedBy returns true if the given user is an owner of the organization.
  580. //
  581. // TODO(unknwon): This is also used in templates, which should be fixed by
  582. // having a dedicated type `template.Organization`.
  583. func (u *Organization) IsOwnedBy(userID int64) bool {
  584. return Organizations.IsOwnedBy(context.TODO(), u.ID, userID)
  585. }
  586. // OrgUser represents relations of organizations and their members.
  587. type OrgUser struct {
  588. ID int64 `gorm:"primaryKey"`
  589. UserID int64 `xorm:"uid INDEX UNIQUE(s)" gorm:"column:uid;uniqueIndex:org_user_user_org_unique;index;not null" json:"Uid"`
  590. OrgID int64 `xorm:"INDEX UNIQUE(s)" gorm:"uniqueIndex:org_user_user_org_unique;index;not null"`
  591. IsPublic bool `gorm:"not null;default:FALSE"`
  592. IsOwner bool `gorm:"not null;default:FALSE"`
  593. NumTeams int `gorm:"not null;default:0"`
  594. }