From ca86dba4cf46f971c28d14806f8396cc718cabe1 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 29 Apr 2022 22:28:54 +0200 Subject: [PATCH 1/3] Federation: ShareUserStatistics --- cmd/admin.go | 2 +- models/statistic.go | 2 +- models/user/user.go | 25 ++++++++++++++++++------- modules/setting/federation.go | 6 ++++-- routers/api/v1/misc/nodeinfo.go | 14 +++++++++++++- routers/web/auth/auth.go | 2 +- 6 files changed, 38 insertions(+), 13 deletions(-) diff --git a/cmd/admin.go b/cmd/admin.go index e4a254c613909..9762ef51806f9 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -551,7 +551,7 @@ func runCreateUser(c *cli.Context) error { // If this is the first user being created. // Take it as the admin and don't force a password update. - if n := user_model.CountUsers(); n == 0 { + if n := user_model.CountUsers(nil); n == 0 { changePassword = false } diff --git a/models/statistic.go b/models/statistic.go index 87c1bd6d754eb..d858102be8e3e 100644 --- a/models/statistic.go +++ b/models/statistic.go @@ -49,7 +49,7 @@ type IssueByRepositoryCount struct { // GetStatistic returns the database statistics func GetStatistic() (stats Statistic) { e := db.GetEngine(db.DefaultContext) - stats.Counter.User = user_model.CountUsers() + stats.Counter.User = user_model.CountUsers(nil) stats.Counter.Org = organization.CountOrganizations() stats.Counter.PublicKey, _ = e.Count(new(asymkey_model.PublicKey)) stats.Counter.Repo = repo_model.CountRepositories(true) diff --git a/models/user/user.go b/models/user/user.go index c848895239180..96a5f74a57c66 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -711,16 +711,27 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e return committer.Commit() } -func countUsers(e db.Engine) int64 { - count, _ := e. - Where("type=0"). - Count(new(User)) - return count +// CountUserFilter represent optional filters for CountUsers +type CountUserFilter struct { + LastLoginSince *int64 } // CountUsers returns number of users. -func CountUsers() int64 { - return countUsers(db.GetEngine(db.DefaultContext)) +func CountUsers(opts *CountUserFilter) int64 { + return countUsers(db.DefaultContext, opts) +} + +func countUsers(ctx context.Context, opts *CountUserFilter) int64 { + sess := db.GetEngine(ctx).Where(builder.Eq{"type": "0"}) + + if opts != nil { + if opts.LastLoginSince != nil { + sess = sess.Where(builder.Gte{"last_login_unix": *opts.LastLoginSince}) + } + } + + count, _ := sess.Count(new(User)) + return count } // GetVerifyUser get user by verify code diff --git a/modules/setting/federation.go b/modules/setting/federation.go index c3000607894e7..fd39e5c7c2aff 100644 --- a/modules/setting/federation.go +++ b/modules/setting/federation.go @@ -9,9 +9,11 @@ import "code.gitea.io/gitea/modules/log" // Federation settings var ( Federation = struct { - Enabled bool + Enabled bool + ShareUserStatistics bool }{ - Enabled: true, + Enabled: true, + ShareUserStatistics: true, } ) diff --git a/routers/api/v1/misc/nodeinfo.go b/routers/api/v1/misc/nodeinfo.go index bc36fa1be1284..07efd7dfcf85d 100644 --- a/routers/api/v1/misc/nodeinfo.go +++ b/routers/api/v1/misc/nodeinfo.go @@ -6,7 +6,9 @@ package misc import ( "net/http" + "time" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" @@ -23,6 +25,16 @@ func NodeInfo(ctx *context.APIContext) { // "200": // "$ref": "#/responses/NodeInfo" + infoUsageUsers := structs.NodeInfoUsageUsers{} + if setting.Federation.ShareUserStatistics { + infoUsageUsers.Total = int(user_model.CountUsers(nil)) + now := time.Now() + timeOneMonthAgo := now.AddDate(0, -1, 0).Unix() + timeHaveYearAgo := now.AddDate(0, -6, 0).Unix() + infoUsageUsers.ActiveMonth = int(user_model.CountUsers(&user_model.CountUserFilter{LastLoginSince: &timeOneMonthAgo})) + infoUsageUsers.ActiveHalfyear = int(user_model.CountUsers(&user_model.CountUserFilter{LastLoginSince: &timeHaveYearAgo})) + } + nodeInfo := &structs.NodeInfo{ Version: "2.1", Software: structs.NodeInfoSoftware{ @@ -38,7 +50,7 @@ func NodeInfo(ctx *context.APIContext) { }, OpenRegistrations: setting.Service.ShowRegistrationButton, Usage: structs.NodeInfoUsage{ - Users: structs.NodeInfoUsageUsers{}, + Users: infoUsageUsers, }, } ctx.JSON(http.StatusOK, nodeInfo) diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index c82fde49eb2f4..ec6c2c151c0b6 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -602,7 +602,7 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{ // sends a confirmation email if required. func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) { // Auto-set admin for the only user. - if user_model.CountUsers() == 1 { + if user_model.CountUsers(nil) == 1 { u.IsAdmin = true u.IsActive = true u.SetLastLogin() From c1c26f86a6605ca661ca0f07d7085a393eb213fe Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sat, 30 Apr 2022 01:54:42 +0200 Subject: [PATCH 2/3] complete --- custom/conf/app.example.ini | 3 ++ .../doc/advanced/config-cheat-sheet.en-us.md | 1 + integrations/api_nodeinfo_test.go | 4 ++ models/issue_test.go | 7 +++ modules/context/api.go | 2 + routers/api/v1/misc/nodeinfo.go | 44 ++++++++++++++----- 6 files changed, 50 insertions(+), 11 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index cbfde9c8d8cc3..0d0d8097552df 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -2240,6 +2240,9 @@ PATH = ;; ;; Enable/Disable federation capabilities ; ENABLED = true +;; +;; Enable/Disable user statistics for nodeinfo if federation is enabled +; SHARE_USER_STATISTICS = true ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 84703aa56345d..5400f3208ea92 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -1085,6 +1085,7 @@ Task queue configuration has been moved to `queue.task`. However, the below conf ## Federation (`federation`) - `ENABLED`: **true**: Enable/Disable federation capabilities +- `SHARE_USER_STATISTICS`: **true**: Enable/Disable user statistics for nodeinfo if federation is enabled ## Packages (`packages`) diff --git a/integrations/api_nodeinfo_test.go b/integrations/api_nodeinfo_test.go index 1d25dc0269288..822dbf3f0e95d 100644 --- a/integrations/api_nodeinfo_test.go +++ b/integrations/api_nodeinfo_test.go @@ -26,6 +26,10 @@ func TestNodeinfo(t *testing.T) { resp := MakeRequest(t, req, http.StatusOK) var nodeinfo api.NodeInfo DecodeJSON(t, resp, &nodeinfo) + assert.True(t, nodeinfo.OpenRegistrations) assert.Equal(t, "gitea", nodeinfo.Software.Name) + assert.Equal(t, 23, nodeinfo.Usage.Users.Total) + assert.Equal(t, 15, nodeinfo.Usage.LocalPosts) + assert.Equal(t, 2, nodeinfo.Usage.LocalComments) }) } diff --git a/models/issue_test.go b/models/issue_test.go index 19e1295264f93..5382c04f43a0f 100644 --- a/models/issue_test.go +++ b/models/issue_test.go @@ -590,3 +590,10 @@ func TestLoadTotalTrackedTime(t *testing.T) { assert.Equal(t, int64(3682), milestone.TotalTrackedTime) } + +func TestCountIssues(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + count, err := CountIssues(&IssuesOptions{}) + assert.NoError(t, err) + assert.EqualValues(t, 15, count) +} diff --git a/modules/context/api.go b/modules/context/api.go index 7d281b998a7df..20a3e05177cc4 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models/auth" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -247,6 +248,7 @@ func APIContexter() func(http.Handler) http.Handler { Resp: NewResponse(w), Data: map[string]interface{}{}, Locale: locale, + Cache: cache.GetCache(), Repo: &Repository{ PullRequest: &PullRequest{}, }, diff --git a/routers/api/v1/misc/nodeinfo.go b/routers/api/v1/misc/nodeinfo.go index 07efd7dfcf85d..ce1f9ec0f76ed 100644 --- a/routers/api/v1/misc/nodeinfo.go +++ b/routers/api/v1/misc/nodeinfo.go @@ -8,12 +8,15 @@ import ( "net/http" "time" + "code.gitea.io/gitea/models" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" ) +const cacheKeyNodeInfoUsage = "API_NodeInfoUsage" + // NodeInfo returns the NodeInfo for the Gitea instance to allow for federation func NodeInfo(ctx *context.APIContext) { // swagger:operation GET /nodeinfo miscellaneous getNodeInfo @@ -25,14 +28,35 @@ func NodeInfo(ctx *context.APIContext) { // "200": // "$ref": "#/responses/NodeInfo" - infoUsageUsers := structs.NodeInfoUsageUsers{} + nodeInfoUsage := structs.NodeInfoUsage{} if setting.Federation.ShareUserStatistics { - infoUsageUsers.Total = int(user_model.CountUsers(nil)) - now := time.Now() - timeOneMonthAgo := now.AddDate(0, -1, 0).Unix() - timeHaveYearAgo := now.AddDate(0, -6, 0).Unix() - infoUsageUsers.ActiveMonth = int(user_model.CountUsers(&user_model.CountUserFilter{LastLoginSince: &timeOneMonthAgo})) - infoUsageUsers.ActiveHalfyear = int(user_model.CountUsers(&user_model.CountUserFilter{LastLoginSince: &timeHaveYearAgo})) + info, ok := ctx.Cache.Get(cacheKeyNodeInfoUsage).(structs.NodeInfoUsage) + if !ok { + usersTotal := int(user_model.CountUsers(nil)) + now := time.Now() + timeOneMonthAgo := now.AddDate(0, -1, 0).Unix() + timeHaveYearAgo := now.AddDate(0, -6, 0).Unix() + usersActiveMonth := int(user_model.CountUsers(&user_model.CountUserFilter{LastLoginSince: &timeOneMonthAgo})) + usersActiveHalfyear := int(user_model.CountUsers(&user_model.CountUserFilter{LastLoginSince: &timeHaveYearAgo})) + + allIssues, _ := models.CountIssues(&models.IssuesOptions{}) + allComments, _ := models.CountComments(&models.FindCommentsOptions{}) + + info = structs.NodeInfoUsage{ + Users: structs.NodeInfoUsageUsers{ + Total: usersTotal, + ActiveMonth: usersActiveMonth, + ActiveHalfyear: usersActiveHalfyear, + }, + LocalPosts: int(allIssues), + LocalComments: int(allComments), + } + if err := ctx.Cache.Put(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil { + ctx.InternalServerError(err) + return + } + } + nodeInfoUsage = info } nodeInfo := &structs.NodeInfo{ @@ -46,12 +70,10 @@ func NodeInfo(ctx *context.APIContext) { Protocols: []string{"activitypub"}, Services: structs.NodeInfoServices{ Inbound: []string{}, - Outbound: []string{}, + Outbound: []string{"rss2.0"}, }, OpenRegistrations: setting.Service.ShowRegistrationButton, - Usage: structs.NodeInfoUsage{ - Users: infoUsageUsers, - }, + Usage: nodeInfoUsage, } ctx.JSON(http.StatusOK, nodeInfo) } From ed74eeef85d753d2cafaaa440de66776c9ea1b9e Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Sun, 1 May 2022 15:57:18 +0200 Subject: [PATCH 3/3] Update models/user/user.go Co-authored-by: delvh --- models/user/user.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/models/user/user.go b/models/user/user.go index e621e6c742dd9..6aa63a0a567d4 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -757,10 +757,8 @@ func CountUsers(opts *CountUserFilter) int64 { func countUsers(ctx context.Context, opts *CountUserFilter) int64 { sess := db.GetEngine(ctx).Where(builder.Eq{"type": "0"}) - if opts != nil { - if opts.LastLoginSince != nil { - sess = sess.Where(builder.Gte{"last_login_unix": *opts.LastLoginSince}) - } + if opts != nil && opts.LastLoginSince != nil { + sess = sess.Where(builder.Gte{"last_login_unix": *opts.LastLoginSince}) } count, _ := sess.Count(new(User))