]> source.dussan.org Git - gitea.git/commitdiff
Auto-subscribe user to repository when they commit/tag to it (#7657)
authorguillep2k <18600385+guillep2k@users.noreply.github.com>
Sun, 10 Nov 2019 09:22:19 +0000 (06:22 -0300)
committerzeripath <art27@cantab.net>
Sun, 10 Nov 2019 09:22:19 +0000 (09:22 +0000)
* Add support for AUTO_WATCH_ON_CHANGES and AUTO_WATCH_ON_CLONE

* Update models/repo_watch.go

Co-Authored-By: Lauris BH <lauris@nix.lv>
* Round up changes suggested by lafriks

* Added changes suggested from automated tests

* Updated deleteUser to take RepoWatchModeDont into account, corrected inverted DefaultWatchOnClone and DefaultWatchOnChanges behaviour, updated and added tests.

* Reinsert import "github.com/Unknwon/com" on http.go

* Add migration for new column `watch`.`mode`

* Remove serv code

* Remove WATCH_ON_CLONE; use hooks, add integrations

* Renamed watch_test.go to repo_watch_test.go

* Correct fmt

* Add missing EOL

* Correct name of test function

* Reword cheat and ini descriptions

* Add update to migration to ensure column value

* Clarify comment

Co-Authored-By: zeripath <art27@cantab.net>
* Simplify if condition

14 files changed:
custom/conf/app.ini.sample
docs/content/doc/advanced/config-cheat-sheet.en-us.md
integrations/repo_watch_test.go [new file with mode: 0644]
models/consistency.go
models/fixtures/repository.yml
models/fixtures/watch.yml
models/migrations/migrations.go
models/migrations/v106.go [new file with mode: 0644]
models/repo.go
models/repo_watch.go
models/repo_watch_test.go
models/user.go
modules/repofiles/update.go
modules/setting/service.go

index 17fcc0de23fea941beaf03550df0fabd79e803d5..5e26171d9e1d524eac21e6e257829694b36e2677 100644 (file)
@@ -501,6 +501,9 @@ SHOW_REGISTRATION_BUTTON = true
 ; When adding a repo to a team or creating a new repo all team members will watch the
 ; repo automatically if enabled
 AUTO_WATCH_NEW_REPOS = true
+; Default value for AutoWatchOnChanges
+; Make the user watch a repository When they commit for the first time
+AUTO_WATCH_ON_CHANGES = false
 
 [webhook]
 ; Hook task queue length, increase if webhook shooting starts hanging
index 96b529c0bc1588dd0389adafce18d52b836de241..e5236205fe2dbcd8cbee8e533a43acecd55c9758 100644 (file)
@@ -303,6 +303,7 @@ relation to port exhaustion.
   on this instance.
 - `SHOW_REGISTRATION_BUTTON`: **! DISABLE\_REGISTRATION**: Show Registration Button
 - `AUTO_WATCH_NEW_REPOS`: **true**: Enable this to let all organisation users watch new repos when they are created
+- `AUTO_WATCH_ON_CHANGES`: **false**: Enable this to make users watch a repository after their first commit to it
 - `DEFAULT_ORG_VISIBILITY`: **public**: Set default visibility mode for organisations, either "public", "limited" or "private".
 - `DEFAULT_ORG_MEMBER_VISIBLE`: **false** True will make the membership of the users visible when added to the organisation.
 
diff --git a/integrations/repo_watch_test.go b/integrations/repo_watch_test.go
new file mode 100644 (file)
index 0000000..d96b014
--- /dev/null
@@ -0,0 +1,24 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package integrations
+
+import (
+       "net/url"
+       "testing"
+
+       "code.gitea.io/gitea/models"
+       "code.gitea.io/gitea/modules/setting"
+)
+
+func TestRepoWatch(t *testing.T) {
+       onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
+               // Test round-trip auto-watch
+               setting.Service.AutoWatchOnChanges = true
+               session := loginUser(t, "user2")
+               models.AssertNotExistsBean(t, &models.Watch{UserID: 2, RepoID: 3})
+               testEditFile(t, session, "user3", "repo3", "master", "README.md", "Hello, World (Edited for watch)\n")
+               models.AssertExistsAndLoadBean(t, &models.Watch{UserID: 2, RepoID: 3, Mode: models.RepoWatchModeAuto})
+       })
+}
index f9fa3028fd9c83abddf1d607b2d465d087481aee..62d1d2e874a6a5764ba44cba5edd9e8c6a526d81 100644 (file)
@@ -84,14 +84,17 @@ func (user *User) checkForConsistency(t *testing.T) {
 func (repo *Repository) checkForConsistency(t *testing.T) {
        assert.Equal(t, repo.LowerName, strings.ToLower(repo.Name), "repo: %+v", repo)
        assertCount(t, &Star{RepoID: repo.ID}, repo.NumStars)
-       assertCount(t, &Watch{RepoID: repo.ID}, repo.NumWatches)
        assertCount(t, &Milestone{RepoID: repo.ID}, repo.NumMilestones)
        assertCount(t, &Repository{ForkID: repo.ID}, repo.NumForks)
        if repo.IsFork {
                AssertExistsAndLoadBean(t, &Repository{ID: repo.ForkID})
        }
 
-       actual := getCount(t, x.Where("is_pull=?", false), &Issue{RepoID: repo.ID})
+       actual := getCount(t, x.Where("Mode<>?", RepoWatchModeDont), &Watch{RepoID: repo.ID})
+       assert.EqualValues(t, repo.NumWatches, actual,
+               "Unexpected number of watches for repo %+v", repo)
+
+       actual = getCount(t, x.Where("is_pull=?", false), &Issue{RepoID: repo.ID})
        assert.EqualValues(t, repo.NumIssues, actual,
                "Unexpected number of issues for repo %+v", repo)
 
index cf7d24c6cdb3c846f28c7718dc6c3746e3835a0c..32903723ec4bd1591e27c7c99badbfbcd8f4ce98 100644 (file)
@@ -10,7 +10,7 @@
   num_closed_pulls: 0
   num_milestones: 3
   num_closed_milestones: 1
-  num_watches: 3
+  num_watches: 4
   status: 0
 
 -
index 5cd3b55fc43e1bbf84c4c58c8cc0ac7a68dc03c9..c29f6bb65a8898d374e33aebaba6c2598ed94460 100644 (file)
@@ -2,13 +2,28 @@
   id: 1
   user_id: 1
   repo_id: 1
+  mode: 1 # normal
 
 -
   id: 2
   user_id: 4
   repo_id: 1
+  mode: 1 # normal
 
 -
   id: 3
   user_id: 9
   repo_id: 1
+  mode: 1 # normal
+
+-
+  id: 4
+  user_id: 8
+  repo_id: 1
+  mode: 2 # don't watch
+
+-
+  id: 5
+  user_id: 11
+  repo_id: 1
+  mode: 3 # auto 
index 5ed70dc4f5f4e27a9f614dee7522e6777f62b829..71ffe2edb3379dae5dd7d4d61b7ac0cd79e03d9a 100644 (file)
@@ -266,6 +266,8 @@ var migrations = []Migration{
        NewMigration("remove unnecessary columns from label", removeLabelUneededCols),
        // v105 -> v106
        NewMigration("add includes_all_repositories to teams", addTeamIncludesAllRepositories),
+       // v106 -> v107
+       NewMigration("add column `mode` to table watch", addModeColumnToWatch),
 }
 
 // Migrate database to current version
diff --git a/models/migrations/v106.go b/models/migrations/v106.go
new file mode 100644 (file)
index 0000000..201fc10
--- /dev/null
@@ -0,0 +1,26 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+       "xorm.io/xorm"
+)
+
+// RepoWatchMode specifies what kind of watch the user has on a repository
+type RepoWatchMode int8
+
+// Watch is connection request for receiving repository notification.
+type Watch struct {
+       ID   int64         `xorm:"pk autoincr"`
+       Mode RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"`
+}
+
+func addModeColumnToWatch(x *xorm.Engine) (err error) {
+       if err = x.Sync2(new(Watch)); err != nil {
+               return
+       }
+       _, err = x.Exec("UPDATE `watch` SET `mode` = 1")
+       return err
+}
index 812460e92ff17c63727a472eb1e1fa462f065b7f..90918025fb9a0c5787697d32830350b787c87847 100644 (file)
@@ -2410,8 +2410,8 @@ func CheckRepoStats() {
        checkers := []*repoChecker{
                // Repository.NumWatches
                {
-                       "SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id)",
-                       "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=?) WHERE id=?",
+                       "SELECT repo.id FROM `repository` repo WHERE repo.num_watches!=(SELECT COUNT(*) FROM `watch` WHERE repo_id=repo.id AND mode<>2)",
+                       "UPDATE `repository` SET num_watches=(SELECT COUNT(*) FROM `watch` WHERE repo_id=? AND mode<>2) WHERE id=?",
                        "repository count 'num_watches'",
                },
                // Repository.NumStars
index 53a34efdafea0b5616fbf95a00baf1efa769090e..cb864fb46dab0e86e53c8b88080f5beb682f9d34 100644 (file)
 
 package models
 
-import "fmt"
+import (
+       "fmt"
+
+       "code.gitea.io/gitea/modules/setting"
+)
+
+// RepoWatchMode specifies what kind of watch the user has on a repository
+type RepoWatchMode int8
+
+const (
+       // RepoWatchModeNone don't watch
+       RepoWatchModeNone RepoWatchMode = iota // 0
+       // RepoWatchModeNormal watch repository (from other sources)
+       RepoWatchModeNormal // 1
+       // RepoWatchModeDont explicit don't auto-watch
+       RepoWatchModeDont // 2
+       // RepoWatchModeAuto watch repository (from AutoWatchOnChanges)
+       RepoWatchModeAuto // 3
+)
 
 // Watch is connection request for receiving repository notification.
 type Watch struct {
-       ID     int64 `xorm:"pk autoincr"`
-       UserID int64 `xorm:"UNIQUE(watch)"`
-       RepoID int64 `xorm:"UNIQUE(watch)"`
+       ID     int64         `xorm:"pk autoincr"`
+       UserID int64         `xorm:"UNIQUE(watch)"`
+       RepoID int64         `xorm:"UNIQUE(watch)"`
+       Mode   RepoWatchMode `xorm:"SMALLINT NOT NULL DEFAULT 1"`
 }
 
-func isWatching(e Engine, userID, repoID int64) bool {
-       has, _ := e.Get(&Watch{UserID: userID, RepoID: repoID})
-       return has
+// getWatch gets what kind of subscription a user has on a given repository; returns dummy record if none found
+func getWatch(e Engine, userID, repoID int64) (Watch, error) {
+       watch := Watch{UserID: userID, RepoID: repoID}
+       has, err := e.Get(&watch)
+       if err != nil {
+               return watch, err
+       }
+       if !has {
+               watch.Mode = RepoWatchModeNone
+       }
+       return watch, nil
+}
+
+// Decodes watchability of RepoWatchMode
+func isWatchMode(mode RepoWatchMode) bool {
+       return mode != RepoWatchModeNone && mode != RepoWatchModeDont
 }
 
 // IsWatching checks if user has watched given repository.
 func IsWatching(userID, repoID int64) bool {
-       return isWatching(x, userID, repoID)
+       watch, err := getWatch(x, userID, repoID)
+       return err == nil && isWatchMode(watch.Mode)
 }
 
-func watchRepo(e Engine, userID, repoID int64, watch bool) (err error) {
-       if watch {
-               if isWatching(e, userID, repoID) {
-                       return nil
-               }
-               if _, err = e.Insert(&Watch{RepoID: repoID, UserID: userID}); err != nil {
+func watchRepoMode(e Engine, watch Watch, mode RepoWatchMode) (err error) {
+       if watch.Mode == mode {
+               return nil
+       }
+       if mode == RepoWatchModeAuto && (watch.Mode == RepoWatchModeDont || isWatchMode(watch.Mode)) {
+               // Don't auto watch if already watching or deliberately not watching
+               return nil
+       }
+
+       hadrec := watch.Mode != RepoWatchModeNone
+       needsrec := mode != RepoWatchModeNone
+       repodiff := 0
+
+       if isWatchMode(mode) && !isWatchMode(watch.Mode) {
+               repodiff = 1
+       } else if !isWatchMode(mode) && isWatchMode(watch.Mode) {
+               repodiff = -1
+       }
+
+       watch.Mode = mode
+
+       if !hadrec && needsrec {
+               watch.Mode = mode
+               if _, err = e.Insert(watch); err != nil {
                        return err
                }
-               _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoID)
-       } else {
-               if !isWatching(e, userID, repoID) {
-                       return nil
-               }
-               if _, err = e.Delete(&Watch{0, userID, repoID}); err != nil {
+       } else if needsrec {
+               watch.Mode = mode
+               if _, err := e.ID(watch.ID).AllCols().Update(watch); err != nil {
                        return err
                }
-               _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?", repoID)
+       } else if _, err = e.Delete(Watch{ID: watch.ID}); err != nil {
+               return err
+       }
+       if repodiff != 0 {
+               _, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + ? WHERE id = ?", repodiff, watch.RepoID)
+       }
+       return err
+}
+
+// WatchRepoMode watch repository in specific mode.
+func WatchRepoMode(userID, repoID int64, mode RepoWatchMode) (err error) {
+       var watch Watch
+       if watch, err = getWatch(x, userID, repoID); err != nil {
+               return err
+       }
+       return watchRepoMode(x, watch, mode)
+}
+
+func watchRepo(e Engine, userID, repoID int64, doWatch bool) (err error) {
+       var watch Watch
+       if watch, err = getWatch(e, userID, repoID); err != nil {
+               return err
+       }
+       if !doWatch && watch.Mode == RepoWatchModeAuto {
+               err = watchRepoMode(e, watch, RepoWatchModeDont)
+       } else if !doWatch {
+               err = watchRepoMode(e, watch, RepoWatchModeNone)
+       } else {
+               err = watchRepoMode(e, watch, RepoWatchModeNormal)
        }
        return err
 }
@@ -52,6 +128,7 @@ func WatchRepo(userID, repoID int64, watch bool) (err error) {
 func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
        watches := make([]*Watch, 0, 10)
        return watches, e.Where("`watch`.repo_id=?", repoID).
+               And("`watch`.mode<>?", RepoWatchModeDont).
                And("`user`.is_active=?", true).
                And("`user`.prohibit_login=?", false).
                Join("INNER", "`user`", "`user`.id = `watch`.user_id").
@@ -67,7 +144,8 @@ func GetWatchers(repoID int64) ([]*Watch, error) {
 func (repo *Repository) GetWatchers(page int) ([]*User, error) {
        users := make([]*User, 0, ItemsPerPage)
        sess := x.Where("watch.repo_id=?", repo.ID).
-               Join("LEFT", "watch", "`user`.id=`watch`.user_id")
+               Join("LEFT", "watch", "`user`.id=`watch`.user_id").
+               And("`watch`.mode<>?", RepoWatchModeDont)
        if page > 0 {
                sess = sess.Limit(ItemsPerPage, (page-1)*ItemsPerPage)
        }
@@ -137,3 +215,22 @@ func notifyWatchers(e Engine, act *Action) error {
 func NotifyWatchers(act *Action) error {
        return notifyWatchers(x, act)
 }
+
+func watchIfAuto(e Engine, userID, repoID int64, isWrite bool) error {
+       if !isWrite || !setting.Service.AutoWatchOnChanges {
+               return nil
+       }
+       watch, err := getWatch(e, userID, repoID)
+       if err != nil {
+               return err
+       }
+       if watch.Mode != RepoWatchModeNone {
+               return nil
+       }
+       return watchRepoMode(e, watch, RepoWatchModeAuto)
+}
+
+// WatchIfAuto subscribes to repo if AutoWatchOnChanges is set
+func WatchIfAuto(userID int64, repoID int64, isWrite bool) error {
+       return watchIfAuto(x, userID, repoID, isWrite)
+}
index 852f09f1c7e0cc0e2e24f67cef3f00080276a3bb..c3d40ec919822e78f9e2580a8d2c371c93839efb 100644 (file)
@@ -7,6 +7,8 @@ package models
 import (
        "testing"
 
+       "code.gitea.io/gitea/modules/setting"
+
        "github.com/stretchr/testify/assert"
 )
 
@@ -15,8 +17,10 @@ func TestIsWatching(t *testing.T) {
 
        assert.True(t, IsWatching(1, 1))
        assert.True(t, IsWatching(4, 1))
+       assert.True(t, IsWatching(11, 1))
 
        assert.False(t, IsWatching(1, 5))
+       assert.False(t, IsWatching(8, 1))
        assert.False(t, IsWatching(NonexistentID, NonexistentID))
 }
 
@@ -78,7 +82,7 @@ func TestNotifyWatchers(t *testing.T) {
        }
        assert.NoError(t, NotifyWatchers(action))
 
-       // One watchers are inactive, thus action is only created for user 8, 1, 4
+       // One watchers are inactive, thus action is only created for user 8, 1, 4, 11
        AssertExistsAndLoadBean(t, &Action{
                ActUserID: action.ActUserID,
                UserID:    8,
@@ -97,4 +101,88 @@ func TestNotifyWatchers(t *testing.T) {
                RepoID:    action.RepoID,
                OpType:    action.OpType,
        })
+       AssertExistsAndLoadBean(t, &Action{
+               ActUserID: action.ActUserID,
+               UserID:    11,
+               RepoID:    action.RepoID,
+               OpType:    action.OpType,
+       })
+}
+
+func TestWatchIfAuto(t *testing.T) {
+       assert.NoError(t, PrepareTestDatabase())
+
+       repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
+       watchers, err := repo.GetWatchers(1)
+       assert.NoError(t, err)
+       assert.Len(t, watchers, repo.NumWatches)
+
+       setting.Service.AutoWatchOnChanges = false
+
+       prevCount := repo.NumWatches
+
+       // Must not add watch
+       assert.NoError(t, WatchIfAuto(8, 1, true))
+       watchers, err = repo.GetWatchers(1)
+       assert.NoError(t, err)
+       assert.Len(t, watchers, prevCount)
+
+       // Should not add watch
+       assert.NoError(t, WatchIfAuto(10, 1, true))
+       watchers, err = repo.GetWatchers(1)
+       assert.NoError(t, err)
+       assert.Len(t, watchers, prevCount)
+
+       setting.Service.AutoWatchOnChanges = true
+
+       // Must not add watch
+       assert.NoError(t, WatchIfAuto(8, 1, true))
+       watchers, err = repo.GetWatchers(1)
+       assert.NoError(t, err)
+       assert.Len(t, watchers, prevCount)
+
+       // Should not add watch
+       assert.NoError(t, WatchIfAuto(12, 1, false))
+       watchers, err = repo.GetWatchers(1)
+       assert.NoError(t, err)
+       assert.Len(t, watchers, prevCount)
+
+       // Should add watch
+       assert.NoError(t, WatchIfAuto(12, 1, true))
+       watchers, err = repo.GetWatchers(1)
+       assert.NoError(t, err)
+       assert.Len(t, watchers, prevCount+1)
+
+       // Should remove watch, inhibit from adding auto
+       assert.NoError(t, WatchRepo(12, 1, false))
+       watchers, err = repo.GetWatchers(1)
+       assert.NoError(t, err)
+       assert.Len(t, watchers, prevCount)
+
+       // Must not add watch
+       assert.NoError(t, WatchIfAuto(12, 1, true))
+       watchers, err = repo.GetWatchers(1)
+       assert.NoError(t, err)
+       assert.Len(t, watchers, prevCount)
+}
+
+func TestWatchRepoMode(t *testing.T) {
+       assert.NoError(t, PrepareTestDatabase())
+
+       AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 0)
+
+       assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeAuto))
+       AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1)
+       AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeAuto}, 1)
+
+       assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeNormal))
+       AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1)
+       AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeNormal}, 1)
+
+       assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeDont))
+       AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 1)
+       AssertCount(t, &Watch{UserID: 12, RepoID: 1, Mode: RepoWatchModeDont}, 1)
+
+       assert.NoError(t, WatchRepoMode(12, 1, RepoWatchModeNone))
+       AssertCount(t, &Watch{UserID: 12, RepoID: 1}, 0)
 }
index 7aa1e143e835bfd4d7a14802760ca089e2d756f1..4a8c644ccdd67057e4581c4e79f6a588cf96380a 100644 (file)
@@ -1082,7 +1082,7 @@ func deleteUser(e *xorm.Session, u *User) error {
        // ***** START: Watch *****
        watchedRepoIDs := make([]int64, 0, 10)
        if err = e.Table("watch").Cols("watch.repo_id").
-               Where("watch.user_id = ?", u.ID).Find(&watchedRepoIDs); err != nil {
+               Where("watch.user_id = ?", u.ID).And("watch.mode <>?", RepoWatchModeDont).Find(&watchedRepoIDs); err != nil {
                return fmt.Errorf("get all watches: %v", err)
        }
        if _, err = e.Decr("num_watches").In("id", watchedRepoIDs).NoAutoTime().Update(new(Repository)); err != nil {
@@ -1543,6 +1543,7 @@ func GetStarredRepos(userID int64, private bool) ([]*Repository, error) {
 // GetWatchedRepos returns the repos watched by a particular user
 func GetWatchedRepos(userID int64, private bool) ([]*Repository, error) {
        sess := x.Where("watch.user_id=?", userID).
+               And("`watch`.mode<>?", RepoWatchModeDont).
                Join("LEFT", "watch", "`repository`.id=`watch`.repo_id")
        if !private {
                sess = sess.And("is_private=?", false)
index 8e057700ab0e4e7fc7dcb22a98e9c008f2c6bb04..5479616c4b81429edb3b8882ad2ac1f766879903 100644 (file)
@@ -504,5 +504,10 @@ func PushUpdate(repo *models.Repository, branch string, opts models.PushUpdateOp
        if opts.RefFullName == git.BranchPrefix+repo.DefaultBranch {
                models.UpdateRepoIndexer(repo)
        }
+
+       if err = models.WatchIfAuto(opts.PusherID, repo.ID, true); err != nil {
+               log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err)
+       }
+
        return nil
 }
index 93629100a2e87a4647f2b09e99b834481805f4bc..6cbee8234d2d10bc458cbfdf76a692be42347fdb 100644 (file)
@@ -44,6 +44,7 @@ var Service struct {
        NoReplyAddress                          string
        EnableUserHeatmap                       bool
        AutoWatchNewRepos                       bool
+       AutoWatchOnChanges                      bool
        DefaultOrgMemberVisible                 bool
 
        // OpenID settings
@@ -85,6 +86,7 @@ func newService() {
        Service.NoReplyAddress = sec.Key("NO_REPLY_ADDRESS").MustString("noreply.example.org")
        Service.EnableUserHeatmap = sec.Key("ENABLE_USER_HEATMAP").MustBool(true)
        Service.AutoWatchNewRepos = sec.Key("AUTO_WATCH_NEW_REPOS").MustBool(true)
+       Service.AutoWatchOnChanges = sec.Key("AUTO_WATCH_ON_CHANGES").MustBool(false)
        Service.DefaultOrgVisibility = sec.Key("DEFAULT_ORG_VISIBILITY").In("public", structs.ExtractKeysFromMapString(structs.VisibilityModes))
        Service.DefaultOrgVisibilityMode = structs.VisibilityModes[Service.DefaultOrgVisibility]
        Service.DefaultOrgMemberVisible = sec.Key("DEFAULT_ORG_MEMBER_VISIBLE").MustBool()