Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Federation: return useful statistic information for nodeinfo #19561

Merged
merged 10 commits into from
May 2, 2022
2 changes: 1 addition & 1 deletion cmd/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,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
}

Expand Down
3 changes: 3 additions & 0 deletions custom/conf/app.example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Expand Down
1 change: 1 addition & 0 deletions docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)

Expand Down
4 changes: 4 additions & 0 deletions integrations/api_nodeinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
7 changes: 7 additions & 0 deletions models/issue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
2 changes: 1 addition & 1 deletion models/statistic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
23 changes: 16 additions & 7 deletions models/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -744,16 +744,25 @@ 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 && 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
Expand Down
2 changes: 2 additions & 0 deletions modules/context/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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{},
},
Expand Down
6 changes: 4 additions & 2 deletions modules/setting/federation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
)

Expand Down
42 changes: 38 additions & 4 deletions routers/api/v1/misc/nodeinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ package misc

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
Expand All @@ -23,6 +28,37 @@ func NodeInfo(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/NodeInfo"

nodeInfoUsage := structs.NodeInfoUsage{}
if setting.Federation.ShareUserStatistics {
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{
Version: "2.1",
Software: structs.NodeInfoSoftware{
Expand All @@ -34,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: structs.NodeInfoUsageUsers{},
},
Usage: nodeInfoUsage,
}
ctx.JSON(http.StatusOK, nodeInfo)
}
2 changes: 1 addition & 1 deletion routers/web/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,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()
Expand Down