Federation: return useful statistic information for nodeinfo (#19561)
Add statistic information for total user count, active user count, issue count and comment count for `/nodeinfo`
This commit is contained in:
parent
509d811243
commit
e2a3f3d259
11 changed files with 78 additions and 16 deletions
|
@ -556,7 +556,7 @@ func runCreateUser(c *cli.Context) error {
|
||||||
|
|
||||||
// If this is the first user being created.
|
// If this is the first user being created.
|
||||||
// Take it as the admin and don't force a password update.
|
// 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
|
changePassword = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2240,6 +2240,9 @@ PATH =
|
||||||
;;
|
;;
|
||||||
;; Enable/Disable federation capabilities
|
;; Enable/Disable federation capabilities
|
||||||
; ENABLED = true
|
; ENABLED = true
|
||||||
|
;;
|
||||||
|
;; Enable/Disable user statistics for nodeinfo if federation is enabled
|
||||||
|
; SHARE_USER_STATISTICS = true
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
|
@ -1085,6 +1085,7 @@ Task queue configuration has been moved to `queue.task`. However, the below conf
|
||||||
## Federation (`federation`)
|
## Federation (`federation`)
|
||||||
|
|
||||||
- `ENABLED`: **true**: Enable/Disable federation capabilities
|
- `ENABLED`: **true**: Enable/Disable federation capabilities
|
||||||
|
- `SHARE_USER_STATISTICS`: **true**: Enable/Disable user statistics for nodeinfo if federation is enabled
|
||||||
|
|
||||||
## Packages (`packages`)
|
## Packages (`packages`)
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,10 @@ func TestNodeinfo(t *testing.T) {
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
var nodeinfo api.NodeInfo
|
var nodeinfo api.NodeInfo
|
||||||
DecodeJSON(t, resp, &nodeinfo)
|
DecodeJSON(t, resp, &nodeinfo)
|
||||||
|
assert.True(t, nodeinfo.OpenRegistrations)
|
||||||
assert.Equal(t, "gitea", nodeinfo.Software.Name)
|
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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -590,3 +590,10 @@ func TestLoadTotalTrackedTime(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, int64(3682), milestone.TotalTrackedTime)
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ type IssueByRepositoryCount struct {
|
||||||
// GetStatistic returns the database statistics
|
// GetStatistic returns the database statistics
|
||||||
func GetStatistic() (stats Statistic) {
|
func GetStatistic() (stats Statistic) {
|
||||||
e := db.GetEngine(db.DefaultContext)
|
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.Org = organization.CountOrganizations()
|
||||||
stats.Counter.PublicKey, _ = e.Count(new(asymkey_model.PublicKey))
|
stats.Counter.PublicKey, _ = e.Count(new(asymkey_model.PublicKey))
|
||||||
stats.Counter.Repo = repo_model.CountRepositories(true)
|
stats.Counter.Repo = repo_model.CountRepositories(true)
|
||||||
|
|
|
@ -744,16 +744,25 @@ func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err e
|
||||||
return committer.Commit()
|
return committer.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func countUsers(e db.Engine) int64 {
|
// CountUserFilter represent optional filters for CountUsers
|
||||||
count, _ := e.
|
type CountUserFilter struct {
|
||||||
Where("type=0").
|
LastLoginSince *int64
|
||||||
Count(new(User))
|
|
||||||
return count
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountUsers returns number of users.
|
// CountUsers returns number of users.
|
||||||
func CountUsers() int64 {
|
func CountUsers(opts *CountUserFilter) int64 {
|
||||||
return countUsers(db.GetEngine(db.DefaultContext))
|
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
|
// GetVerifyUser get user by verify code
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/auth"
|
"code.gitea.io/gitea/models/auth"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
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/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
@ -247,6 +248,7 @@ func APIContexter() func(http.Handler) http.Handler {
|
||||||
Resp: NewResponse(w),
|
Resp: NewResponse(w),
|
||||||
Data: map[string]interface{}{},
|
Data: map[string]interface{}{},
|
||||||
Locale: locale,
|
Locale: locale,
|
||||||
|
Cache: cache.GetCache(),
|
||||||
Repo: &Repository{
|
Repo: &Repository{
|
||||||
PullRequest: &PullRequest{},
|
PullRequest: &PullRequest{},
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,9 +9,11 @@ import "code.gitea.io/gitea/modules/log"
|
||||||
// Federation settings
|
// Federation settings
|
||||||
var (
|
var (
|
||||||
Federation = struct {
|
Federation = struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
|
ShareUserStatistics bool
|
||||||
}{
|
}{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
|
ShareUserStatistics: true,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,17 @@ package misc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"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/context"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const cacheKeyNodeInfoUsage = "API_NodeInfoUsage"
|
||||||
|
|
||||||
// NodeInfo returns the NodeInfo for the Gitea instance to allow for federation
|
// NodeInfo returns the NodeInfo for the Gitea instance to allow for federation
|
||||||
func NodeInfo(ctx *context.APIContext) {
|
func NodeInfo(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /nodeinfo miscellaneous getNodeInfo
|
// swagger:operation GET /nodeinfo miscellaneous getNodeInfo
|
||||||
|
@ -23,6 +28,37 @@ func NodeInfo(ctx *context.APIContext) {
|
||||||
// "200":
|
// "200":
|
||||||
// "$ref": "#/responses/NodeInfo"
|
// "$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{
|
nodeInfo := &structs.NodeInfo{
|
||||||
Version: "2.1",
|
Version: "2.1",
|
||||||
Software: structs.NodeInfoSoftware{
|
Software: structs.NodeInfoSoftware{
|
||||||
|
@ -34,12 +70,10 @@ func NodeInfo(ctx *context.APIContext) {
|
||||||
Protocols: []string{"activitypub"},
|
Protocols: []string{"activitypub"},
|
||||||
Services: structs.NodeInfoServices{
|
Services: structs.NodeInfoServices{
|
||||||
Inbound: []string{},
|
Inbound: []string{},
|
||||||
Outbound: []string{},
|
Outbound: []string{"rss2.0"},
|
||||||
},
|
},
|
||||||
OpenRegistrations: setting.Service.ShowRegistrationButton,
|
OpenRegistrations: setting.Service.ShowRegistrationButton,
|
||||||
Usage: structs.NodeInfoUsage{
|
Usage: nodeInfoUsage,
|
||||||
Users: structs.NodeInfoUsageUsers{},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
ctx.JSON(http.StatusOK, nodeInfo)
|
ctx.JSON(http.StatusOK, nodeInfo)
|
||||||
}
|
}
|
||||||
|
|
|
@ -600,7 +600,7 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form interface{
|
||||||
// sends a confirmation email if required.
|
// sends a confirmation email if required.
|
||||||
func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) {
|
func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) {
|
||||||
// Auto-set admin for the only user.
|
// Auto-set admin for the only user.
|
||||||
if user_model.CountUsers() == 1 {
|
if user_model.CountUsers(nil) == 1 {
|
||||||
u.IsAdmin = true
|
u.IsAdmin = true
|
||||||
u.IsActive = true
|
u.IsActive = true
|
||||||
u.SetLastLogin()
|
u.SetLastLogin()
|
||||||
|
|
Reference in a new issue