Skip to content

Commit e2a3f3d

Browse files
authored
Federation: return useful statistic information for nodeinfo (go-gitea#19561)
Add statistic information for total user count, active user count, issue count and comment count for `/nodeinfo`
1 parent 509d811 commit e2a3f3d

File tree

11 files changed

+78
-16
lines changed

11 files changed

+78
-16
lines changed

cmd/admin.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,7 @@ func runCreateUser(c *cli.Context) error {
556556

557557
// If this is the first user being created.
558558
// Take it as the admin and don't force a password update.
559-
if n := user_model.CountUsers(); n == 0 {
559+
if n := user_model.CountUsers(nil); n == 0 {
560560
changePassword = false
561561
}
562562

custom/conf/app.example.ini

+3
Original file line numberDiff line numberDiff line change
@@ -2240,6 +2240,9 @@ PATH =
22402240
;;
22412241
;; Enable/Disable federation capabilities
22422242
; ENABLED = true
2243+
;;
2244+
;; Enable/Disable user statistics for nodeinfo if federation is enabled
2245+
; SHARE_USER_STATISTICS = true
22432246

22442247
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
22452248
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

docs/content/doc/advanced/config-cheat-sheet.en-us.md

+1
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,7 @@ Task queue configuration has been moved to `queue.task`. However, the below conf
10851085
## Federation (`federation`)
10861086

10871087
- `ENABLED`: **true**: Enable/Disable federation capabilities
1088+
- `SHARE_USER_STATISTICS`: **true**: Enable/Disable user statistics for nodeinfo if federation is enabled
10881089

10891090
## Packages (`packages`)
10901091

integrations/api_nodeinfo_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ func TestNodeinfo(t *testing.T) {
2626
resp := MakeRequest(t, req, http.StatusOK)
2727
var nodeinfo api.NodeInfo
2828
DecodeJSON(t, resp, &nodeinfo)
29+
assert.True(t, nodeinfo.OpenRegistrations)
2930
assert.Equal(t, "gitea", nodeinfo.Software.Name)
31+
assert.Equal(t, 23, nodeinfo.Usage.Users.Total)
32+
assert.Equal(t, 15, nodeinfo.Usage.LocalPosts)
33+
assert.Equal(t, 2, nodeinfo.Usage.LocalComments)
3034
})
3135
}

models/issue_test.go

+7
Original file line numberDiff line numberDiff line change
@@ -590,3 +590,10 @@ func TestLoadTotalTrackedTime(t *testing.T) {
590590

591591
assert.Equal(t, int64(3682), milestone.TotalTrackedTime)
592592
}
593+
594+
func TestCountIssues(t *testing.T) {
595+
assert.NoError(t, unittest.PrepareTestDatabase())
596+
count, err := CountIssues(&IssuesOptions{})
597+
assert.NoError(t, err)
598+
assert.EqualValues(t, 15, count)
599+
}

models/statistic.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ type IssueByRepositoryCount struct {
4949
// GetStatistic returns the database statistics
5050
func GetStatistic() (stats Statistic) {
5151
e := db.GetEngine(db.DefaultContext)
52-
stats.Counter.User = user_model.CountUsers()
52+
stats.Counter.User = user_model.CountUsers(nil)
5353
stats.Counter.Org = organization.CountOrganizations()
5454
stats.Counter.PublicKey, _ = e.Count(new(asymkey_model.PublicKey))
5555
stats.Counter.Repo = repo_model.CountRepositories(true)

models/user/user.go

+16-7
Original file line numberDiff line numberDiff line change
@@ -744,16 +744,25 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
744744
return committer.Commit()
745745
}
746746

747-
func countUsers(e db.Engine) int64 {
748-
count, _ := e.
749-
Where("type=0").
750-
Count(new(User))
751-
return count
747+
// CountUserFilter represent optional filters for CountUsers
748+
type CountUserFilter struct {
749+
LastLoginSince *int64
752750
}
753751

754752
// CountUsers returns number of users.
755-
func CountUsers() int64 {
756-
return countUsers(db.GetEngine(db.DefaultContext))
753+
func CountUsers(opts *CountUserFilter) int64 {
754+
return countUsers(db.DefaultContext, opts)
755+
}
756+
757+
func countUsers(ctx context.Context, opts *CountUserFilter) int64 {
758+
sess := db.GetEngine(ctx).Where(builder.Eq{"type": "0"})
759+
760+
if opts != nil && opts.LastLoginSince != nil {
761+
sess = sess.Where(builder.Gte{"last_login_unix": *opts.LastLoginSince})
762+
}
763+
764+
count, _ := sess.Count(new(User))
765+
return count
757766
}
758767

759768
// GetVerifyUser get user by verify code

modules/context/api.go

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"code.gitea.io/gitea/models/auth"
1616
repo_model "code.gitea.io/gitea/models/repo"
17+
"code.gitea.io/gitea/modules/cache"
1718
"code.gitea.io/gitea/modules/git"
1819
"code.gitea.io/gitea/modules/log"
1920
"code.gitea.io/gitea/modules/setting"
@@ -247,6 +248,7 @@ func APIContexter() func(http.Handler) http.Handler {
247248
Resp: NewResponse(w),
248249
Data: map[string]interface{}{},
249250
Locale: locale,
251+
Cache: cache.GetCache(),
250252
Repo: &Repository{
251253
PullRequest: &PullRequest{},
252254
},

modules/setting/federation.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import "code.gitea.io/gitea/modules/log"
99
// Federation settings
1010
var (
1111
Federation = struct {
12-
Enabled bool
12+
Enabled bool
13+
ShareUserStatistics bool
1314
}{
14-
Enabled: true,
15+
Enabled: true,
16+
ShareUserStatistics: true,
1517
}
1618
)
1719

routers/api/v1/misc/nodeinfo.go

+38-4
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@ package misc
66

77
import (
88
"net/http"
9+
"time"
910

11+
"code.gitea.io/gitea/models"
12+
user_model "code.gitea.io/gitea/models/user"
1013
"code.gitea.io/gitea/modules/context"
1114
"code.gitea.io/gitea/modules/setting"
1215
"code.gitea.io/gitea/modules/structs"
1316
)
1417

18+
const cacheKeyNodeInfoUsage = "API_NodeInfoUsage"
19+
1520
// NodeInfo returns the NodeInfo for the Gitea instance to allow for federation
1621
func NodeInfo(ctx *context.APIContext) {
1722
// swagger:operation GET /nodeinfo miscellaneous getNodeInfo
@@ -23,6 +28,37 @@ func NodeInfo(ctx *context.APIContext) {
2328
// "200":
2429
// "$ref": "#/responses/NodeInfo"
2530

31+
nodeInfoUsage := structs.NodeInfoUsage{}
32+
if setting.Federation.ShareUserStatistics {
33+
info, ok := ctx.Cache.Get(cacheKeyNodeInfoUsage).(structs.NodeInfoUsage)
34+
if !ok {
35+
usersTotal := int(user_model.CountUsers(nil))
36+
now := time.Now()
37+
timeOneMonthAgo := now.AddDate(0, -1, 0).Unix()
38+
timeHaveYearAgo := now.AddDate(0, -6, 0).Unix()
39+
usersActiveMonth := int(user_model.CountUsers(&user_model.CountUserFilter{LastLoginSince: &timeOneMonthAgo}))
40+
usersActiveHalfyear := int(user_model.CountUsers(&user_model.CountUserFilter{LastLoginSince: &timeHaveYearAgo}))
41+
42+
allIssues, _ := models.CountIssues(&models.IssuesOptions{})
43+
allComments, _ := models.CountComments(&models.FindCommentsOptions{})
44+
45+
info = structs.NodeInfoUsage{
46+
Users: structs.NodeInfoUsageUsers{
47+
Total: usersTotal,
48+
ActiveMonth: usersActiveMonth,
49+
ActiveHalfyear: usersActiveHalfyear,
50+
},
51+
LocalPosts: int(allIssues),
52+
LocalComments: int(allComments),
53+
}
54+
if err := ctx.Cache.Put(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil {
55+
ctx.InternalServerError(err)
56+
return
57+
}
58+
}
59+
nodeInfoUsage = info
60+
}
61+
2662
nodeInfo := &structs.NodeInfo{
2763
Version: "2.1",
2864
Software: structs.NodeInfoSoftware{
@@ -34,12 +70,10 @@ func NodeInfo(ctx *context.APIContext) {
3470
Protocols: []string{"activitypub"},
3571
Services: structs.NodeInfoServices{
3672
Inbound: []string{},
37-
Outbound: []string{},
73+
Outbound: []string{"rss2.0"},
3874
},
3975
OpenRegistrations: setting.Service.ShowRegistrationButton,
40-
Usage: structs.NodeInfoUsage{
41-
Users: structs.NodeInfoUsageUsers{},
42-
},
76+
Usage: nodeInfoUsage,
4377
}
4478
ctx.JSON(http.StatusOK, nodeInfo)
4579
}

routers/web/auth/auth.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{
600600
// sends a confirmation email if required.
601601
func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) {
602602
// Auto-set admin for the only user.
603-
if user_model.CountUsers() == 1 {
603+
if user_model.CountUsers(nil) == 1 {
604604
u.IsAdmin = true
605605
u.IsActive = true
606606
u.SetLastLogin()

0 commit comments

Comments
 (0)